backup_test.go 9.7 KB

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