github.com/CanonicalLtd/go-sqlite3@v1.6.0/backup_test.go (about)

     1  // Use of this source code is governed by an MIT-style
     2  // license that can be found in the LICENSE file.
     3  
     4  package sqlite3
     5  
     6  import (
     7  	"database/sql"
     8  	"fmt"
     9  	"os"
    10  	"testing"
    11  	"time"
    12  )
    13  
    14  // The number of rows of test data to create in the source database.
    15  // Can be used to control how many pages are available to be backed up.
    16  const testRowCount = 100
    17  
    18  // The maximum number of seconds after which the page-by-page backup is considered to have taken too long.
    19  const usePagePerStepsTimeoutSeconds = 30
    20  
    21  // Test the backup functionality.
    22  func testBackup(t *testing.T, testRowCount int, usePerPageSteps bool) {
    23  	// This function will be called multiple times.
    24  	// It uses sql.Register(), which requires the name parameter value to be unique.
    25  	// There does not currently appear to be a way to unregister a registered driver, however.
    26  	// So generate a database driver name that will likely be unique.
    27  	var driverName = fmt.Sprintf("sqlite3_testBackup_%v_%v_%v", testRowCount, usePerPageSteps, time.Now().UnixNano())
    28  
    29  	// The driver's connection will be needed in order to perform the backup.
    30  	driverConns := []*SQLiteConn{}
    31  	sql.Register(driverName, &SQLiteDriver{
    32  		ConnectHook: func(conn *SQLiteConn) error {
    33  			driverConns = append(driverConns, conn)
    34  			return nil
    35  		},
    36  	})
    37  
    38  	// Connect to the source database.
    39  	srcTempFilename := TempFilename(t)
    40  	defer os.Remove(srcTempFilename)
    41  	srcDb, err := sql.Open(driverName, srcTempFilename)
    42  	if err != nil {
    43  		t.Fatal("Failed to open the source database:", err)
    44  	}
    45  	defer srcDb.Close()
    46  	err = srcDb.Ping()
    47  	if err != nil {
    48  		t.Fatal("Failed to connect to the source database:", err)
    49  	}
    50  
    51  	// Connect to the destination database.
    52  	destTempFilename := TempFilename(t)
    53  	defer os.Remove(destTempFilename)
    54  	destDb, err := sql.Open(driverName, destTempFilename)
    55  	if err != nil {
    56  		t.Fatal("Failed to open the destination database:", err)
    57  	}
    58  	defer destDb.Close()
    59  	err = destDb.Ping()
    60  	if err != nil {
    61  		t.Fatal("Failed to connect to the destination database:", err)
    62  	}
    63  
    64  	// Check the driver connections.
    65  	if len(driverConns) != 2 {
    66  		t.Fatalf("Expected 2 driver connections, but found %v.", len(driverConns))
    67  	}
    68  	srcDbDriverConn := driverConns[0]
    69  	if srcDbDriverConn == nil {
    70  		t.Fatal("The source database driver connection is nil.")
    71  	}
    72  	destDbDriverConn := driverConns[1]
    73  	if destDbDriverConn == nil {
    74  		t.Fatal("The destination database driver connection is nil.")
    75  	}
    76  
    77  	// Generate some test data for the given ID.
    78  	var generateTestData = func(id int) string {
    79  		return fmt.Sprintf("test-%v", id)
    80  	}
    81  
    82  	// Populate the source database with a test table containing some test data.
    83  	tx, err := srcDb.Begin()
    84  	if err != nil {
    85  		t.Fatal("Failed to begin a transaction when populating the source database:", err)
    86  	}
    87  	_, err = srcDb.Exec("CREATE TABLE test (id INTEGER PRIMARY KEY, value TEXT)")
    88  	if err != nil {
    89  		tx.Rollback()
    90  		t.Fatal("Failed to create the source database \"test\" table:", err)
    91  	}
    92  	for id := 0; id < testRowCount; id++ {
    93  		_, err = srcDb.Exec("INSERT INTO test (id, value) VALUES (?, ?)", id, generateTestData(id))
    94  		if err != nil {
    95  			tx.Rollback()
    96  			t.Fatal("Failed to insert a row into the source database \"test\" table:", err)
    97  		}
    98  	}
    99  	err = tx.Commit()
   100  	if err != nil {
   101  		t.Fatal("Failed to populate the source database:", err)
   102  	}
   103  
   104  	// Confirm that the destination database is initially empty.
   105  	var destTableCount int
   106  	err = destDb.QueryRow("SELECT COUNT(*) FROM sqlite_master WHERE type = 'table'").Scan(&destTableCount)
   107  	if err != nil {
   108  		t.Fatal("Failed to check the destination table count:", err)
   109  	}
   110  	if destTableCount != 0 {
   111  		t.Fatalf("The destination database is not empty; %v table(s) found.", destTableCount)
   112  	}
   113  
   114  	// Prepare to perform the backup.
   115  	backup, err := destDbDriverConn.Backup("main", srcDbDriverConn, "main")
   116  	if err != nil {
   117  		t.Fatal("Failed to initialize the backup:", err)
   118  	}
   119  
   120  	// Allow the initial page count and remaining values to be retrieved.
   121  	// According to <https://www.sqlite.org/c3ref/backup_finish.html>, the page count and remaining values are "... only updated by sqlite3_backup_step()."
   122  	isDone, err := backup.Step(0)
   123  	if err != nil {
   124  		t.Fatal("Unable to perform an initial 0-page backup step:", err)
   125  	}
   126  	if isDone {
   127  		t.Fatal("Backup is unexpectedly done.")
   128  	}
   129  
   130  	// Check that the page count and remaining values are reasonable.
   131  	initialPageCount := backup.PageCount()
   132  	if initialPageCount <= 0 {
   133  		t.Fatalf("Unexpected initial page count value: %v", initialPageCount)
   134  	}
   135  	initialRemaining := backup.Remaining()
   136  	if initialRemaining <= 0 {
   137  		t.Fatalf("Unexpected initial remaining value: %v", initialRemaining)
   138  	}
   139  	if initialRemaining != initialPageCount {
   140  		t.Fatalf("Initial remaining value differs from the initial page count value; remaining: %v; page count: %v", initialRemaining, initialPageCount)
   141  	}
   142  
   143  	// Perform the backup.
   144  	if usePerPageSteps {
   145  		var startTime = time.Now().Unix()
   146  
   147  		// Test backing-up using a page-by-page approach.
   148  		var latestRemaining = initialRemaining
   149  		for {
   150  			// Perform the backup step.
   151  			isDone, err = backup.Step(1)
   152  			if err != nil {
   153  				t.Fatal("Failed to perform a backup step:", err)
   154  			}
   155  
   156  			// The page count should remain unchanged from its initial value.
   157  			currentPageCount := backup.PageCount()
   158  			if currentPageCount != initialPageCount {
   159  				t.Fatalf("Current page count differs from the initial page count; initial page count: %v; current page count: %v", initialPageCount, currentPageCount)
   160  			}
   161  
   162  			// There should now be one less page remaining.
   163  			currentRemaining := backup.Remaining()
   164  			expectedRemaining := latestRemaining - 1
   165  			if currentRemaining != expectedRemaining {
   166  				t.Fatalf("Unexpected remaining value; expected remaining value: %v; actual remaining value: %v", expectedRemaining, currentRemaining)
   167  			}
   168  			latestRemaining = currentRemaining
   169  
   170  			if isDone {
   171  				break
   172  			}
   173  
   174  			// Limit the runtime of the backup attempt.
   175  			if (time.Now().Unix() - startTime) > usePagePerStepsTimeoutSeconds {
   176  				t.Fatal("Backup is taking longer than expected.")
   177  			}
   178  		}
   179  	} else {
   180  		// Test the copying of all remaining pages.
   181  		isDone, err = backup.Step(-1)
   182  		if err != nil {
   183  			t.Fatal("Failed to perform a backup step:", err)
   184  		}
   185  		if !isDone {
   186  			t.Fatal("Backup is unexpectedly not done.")
   187  		}
   188  	}
   189  
   190  	// Check that the page count and remaining values are reasonable.
   191  	finalPageCount := backup.PageCount()
   192  	if finalPageCount != initialPageCount {
   193  		t.Fatalf("Final page count differs from the initial page count; initial page count: %v; final page count: %v", initialPageCount, finalPageCount)
   194  	}
   195  	finalRemaining := backup.Remaining()
   196  	if finalRemaining != 0 {
   197  		t.Fatalf("Unexpected remaining value: %v", finalRemaining)
   198  	}
   199  
   200  	// Finish the backup.
   201  	err = backup.Finish()
   202  	if err != nil {
   203  		t.Fatal("Failed to finish backup:", err)
   204  	}
   205  
   206  	// Confirm that the "test" table now exists in the destination database.
   207  	var doesTestTableExist bool
   208  	err = destDb.QueryRow("SELECT EXISTS (SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'test' LIMIT 1) AS test_table_exists").Scan(&doesTestTableExist)
   209  	if err != nil {
   210  		t.Fatal("Failed to check if the \"test\" table exists in the destination database:", err)
   211  	}
   212  	if !doesTestTableExist {
   213  		t.Fatal("The \"test\" table could not be found in the destination database.")
   214  	}
   215  
   216  	// Confirm that the number of rows in the destination database's "test" table matches that of the source table.
   217  	var actualTestTableRowCount int
   218  	err = destDb.QueryRow("SELECT COUNT(*) FROM test").Scan(&actualTestTableRowCount)
   219  	if err != nil {
   220  		t.Fatal("Failed to determine the rowcount of the \"test\" table in the destination database:", err)
   221  	}
   222  	if testRowCount != actualTestTableRowCount {
   223  		t.Fatalf("Unexpected destination \"test\" table row count; expected: %v; found: %v", testRowCount, actualTestTableRowCount)
   224  	}
   225  
   226  	// Check each of the rows in the destination database.
   227  	for id := 0; id < testRowCount; id++ {
   228  		var checkedValue string
   229  		err = destDb.QueryRow("SELECT value FROM test WHERE id = ?", id).Scan(&checkedValue)
   230  		if err != nil {
   231  			t.Fatal("Failed to query the \"test\" table in the destination database:", err)
   232  		}
   233  
   234  		var expectedValue = generateTestData(id)
   235  		if checkedValue != expectedValue {
   236  			t.Fatalf("Unexpected value in the \"test\" table in the destination database; expected value: %v; actual value: %v", expectedValue, checkedValue)
   237  		}
   238  	}
   239  }
   240  
   241  func TestBackupStepByStep(t *testing.T) {
   242  	testBackup(t, testRowCount, true)
   243  }
   244  
   245  func TestBackupAllRemainingPages(t *testing.T) {
   246  	testBackup(t, testRowCount, false)
   247  }
   248  
   249  // Test the error reporting when preparing to perform a backup.
   250  func TestBackupError(t *testing.T) {
   251  	const driverName = "sqlite3_TestBackupError"
   252  
   253  	// The driver's connection will be needed in order to perform the backup.
   254  	var dbDriverConn *SQLiteConn
   255  	sql.Register(driverName, &SQLiteDriver{
   256  		ConnectHook: func(conn *SQLiteConn) error {
   257  			dbDriverConn = conn
   258  			return nil
   259  		},
   260  	})
   261  
   262  	// Connect to the database.
   263  	dbTempFilename := TempFilename(t)
   264  	defer os.Remove(dbTempFilename)
   265  	db, err := sql.Open(driverName, dbTempFilename)
   266  	if err != nil {
   267  		t.Fatal("Failed to open the database:", err)
   268  	}
   269  	defer db.Close()
   270  	db.Ping()
   271  
   272  	// Need the driver connection in order to perform the backup.
   273  	if dbDriverConn == nil {
   274  		t.Fatal("Failed to get the driver connection.")
   275  	}
   276  
   277  	// Prepare to perform the backup.
   278  	// Intentionally using the same connection for both the source and destination databases, to trigger an error result.
   279  	backup, err := dbDriverConn.Backup("main", dbDriverConn, "main")
   280  	if err == nil {
   281  		t.Fatal("Failed to get the expected error result.")
   282  	}
   283  	const expectedError = "source and destination must be distinct"
   284  	if err.Error() != expectedError {
   285  		t.Fatalf("Unexpected error message; expected value: \"%v\"; actual value: \"%v\"", expectedError, err.Error())
   286  	}
   287  	if backup != nil {
   288  		t.Fatal("Failed to get the expected nil backup result.")
   289  	}
   290  }