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 }