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