Fork github.com/mattn/go-sqlite3 with adjustment for go1.16.2
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

294 lines
9.7 KiB

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