2018-06-20 17:33:38 +03:00
// Copyright (C) 2018 The Go-SQLite3 Authors.
//
2016-08-21 01:57:12 +03:00
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.
2018-06-20 17:33:38 +03:00
// +build cgo
2018-06-24 23:57:57 +03:00
// +build go1.8
2018-06-20 17:33:38 +03:00
2016-08-21 01:57:12 +03:00
package sqlite3
import (
"database/sql"
"fmt"
"os"
"testing"
"time"
)
// The number of rows of test data to create in the source database.
// Can be used to control how many pages are available to be backed up.
2016-09-07 18:15:10 +03:00
const testRowCount = 100
2016-08-21 01:57:12 +03:00
// The maximum number of seconds after which the page-by-page backup is considered to have taken too long.
const usePagePerStepsTimeoutSeconds = 30
// Test the backup functionality.
func testBackup ( t * testing . T , testRowCount int , usePerPageSteps bool ) {
// This function will be called multiple times.
// It uses sql.Register(), which requires the name parameter value to be unique.
// There does not currently appear to be a way to unregister a registered driver, however.
// So generate a database driver name that will likely be unique.
var driverName = fmt . Sprintf ( "sqlite3_testBackup_%v_%v_%v" , testRowCount , usePerPageSteps , time . Now ( ) . UnixNano ( ) )
// The driver's connection will be needed in order to perform the backup.
driverConns := [ ] * SQLiteConn { }
sql . Register ( driverName , & SQLiteDriver {
ConnectHook : func ( conn * SQLiteConn ) error {
driverConns = append ( driverConns , conn )
return nil
} ,
} )
// Connect to the source database.
srcTempFilename := TempFilename ( t )
defer os . Remove ( srcTempFilename )
srcDb , err := sql . Open ( driverName , srcTempFilename )
if err != nil {
2018-07-22 23:32:19 +03:00
t . Fatal ( "failed to open the source database:" , err )
2016-08-21 01:57:12 +03:00
}
defer srcDb . Close ( )
err = srcDb . Ping ( )
if err != nil {
2018-07-22 23:32:19 +03:00
t . Fatal ( "failed to connect to the source database:" , err )
2016-08-21 01:57:12 +03:00
}
// Connect to the destination database.
destTempFilename := TempFilename ( t )
defer os . Remove ( destTempFilename )
destDb , err := sql . Open ( driverName , destTempFilename )
if err != nil {
2018-07-22 23:32:19 +03:00
t . Fatal ( "failed to open the destination database:" , err )
2016-08-21 01:57:12 +03:00
}
defer destDb . Close ( )
err = destDb . Ping ( )
if err != nil {
2018-07-22 23:32:19 +03:00
t . Fatal ( "failed to connect to the destination database:" , err )
2016-08-21 01:57:12 +03:00
}
// Check the driver connections.
if len ( driverConns ) != 2 {
2018-07-22 23:32:19 +03:00
t . Fatalf ( "expected 2 driver connections, but found %v." , len ( driverConns ) )
2016-08-21 01:57:12 +03:00
}
srcDbDriverConn := driverConns [ 0 ]
if srcDbDriverConn == nil {
2018-07-22 23:32:19 +03:00
t . Fatal ( "the source database driver connection is nil." )
2016-08-21 01:57:12 +03:00
}
destDbDriverConn := driverConns [ 1 ]
if destDbDriverConn == nil {
2018-07-22 23:32:19 +03:00
t . Fatal ( "the destination database driver connection is nil." )
2016-08-21 01:57:12 +03:00
}
// Generate some test data for the given ID.
var generateTestData = func ( id int ) string {
return fmt . Sprintf ( "test-%v" , id )
}
// Populate the source database with a test table containing some test data.
tx , err := srcDb . Begin ( )
if err != nil {
2018-07-22 23:32:19 +03:00
t . Fatal ( "failed to begin a transaction when populating the source database:" , err )
2016-08-21 01:57:12 +03:00
}
_ , err = srcDb . Exec ( "CREATE TABLE test (id INTEGER PRIMARY KEY, value TEXT)" )
if err != nil {
tx . Rollback ( )
2018-07-22 23:32:19 +03:00
t . Fatal ( "failed to create the source database \"test\" table:" , err )
2016-08-21 01:57:12 +03:00
}
for id := 0 ; id < testRowCount ; id ++ {
_ , err = srcDb . Exec ( "INSERT INTO test (id, value) VALUES (?, ?)" , id , generateTestData ( id ) )
if err != nil {
tx . Rollback ( )
2018-07-22 23:32:19 +03:00
t . Fatal ( "failed to insert a row into the source database \"test\" table:" , err )
2016-08-21 01:57:12 +03:00
}
}
err = tx . Commit ( )
if err != nil {
2018-07-22 23:32:19 +03:00
t . Fatal ( "failed to populate the source database:" , err )
2016-08-21 01:57:12 +03:00
}
// Confirm that the destination database is initially empty.
var destTableCount int
err = destDb . QueryRow ( "SELECT COUNT(*) FROM sqlite_master WHERE type = 'table'" ) . Scan ( & destTableCount )
if err != nil {
2018-07-22 23:32:19 +03:00
t . Fatal ( "failed to check the destination table count:" , err )
2016-08-21 01:57:12 +03:00
}
if destTableCount != 0 {
2018-07-22 23:32:19 +03:00
t . Fatalf ( "the destination database is not empty; %v table(s) found." , destTableCount )
2016-08-21 01:57:12 +03:00
}
// Prepare to perform the backup.
backup , err := destDbDriverConn . Backup ( "main" , srcDbDriverConn , "main" )
if err != nil {
2018-07-22 23:32:19 +03:00
t . Fatal ( "failed to initialize the backup:" , err )
2016-08-21 01:57:12 +03:00
}
// Allow the initial page count and remaining values to be retrieved.
// According to <https://www.sqlite.org/c3ref/backup_finish.html>, the page count and remaining values are "... only updated by sqlite3_backup_step()."
isDone , err := backup . Step ( 0 )
if err != nil {
t . Fatal ( "Unable to perform an initial 0-page backup step:" , err )
}
if isDone {
2018-07-22 23:32:19 +03:00
t . Fatal ( "backup is unexpectedly done." )
2016-08-21 01:57:12 +03:00
}
// Check that the page count and remaining values are reasonable.
initialPageCount := backup . PageCount ( )
if initialPageCount <= 0 {
2018-07-22 23:32:19 +03:00
t . Fatalf ( "unexpected initial page count value: %v" , initialPageCount )
2016-08-21 01:57:12 +03:00
}
initialRemaining := backup . Remaining ( )
if initialRemaining <= 0 {
2018-07-22 23:32:19 +03:00
t . Fatalf ( "unexpected initial remaining value: %v" , initialRemaining )
2016-08-21 01:57:12 +03:00
}
if initialRemaining != initialPageCount {
2018-07-22 23:32:19 +03:00
t . Fatalf ( "initial remaining value differs from the initial page count value; remaining: %v; page count: %v" , initialRemaining , initialPageCount )
2016-08-21 01:57:12 +03:00
}
// Perform the backup.
if usePerPageSteps {
var startTime = time . Now ( ) . Unix ( )
// Test backing-up using a page-by-page approach.
var latestRemaining = initialRemaining
for {
// Perform the backup step.
isDone , err = backup . Step ( 1 )
if err != nil {
2018-07-22 23:32:19 +03:00
t . Fatal ( "failed to perform a backup step:" , err )
2016-08-21 01:57:12 +03:00
}
// The page count should remain unchanged from its initial value.
currentPageCount := backup . PageCount ( )
if currentPageCount != initialPageCount {
2018-07-22 23:32:19 +03:00
t . Fatalf ( "current page count differs from the initial page count; initial page count: %v; current page count: %v" , initialPageCount , currentPageCount )
2016-08-21 01:57:12 +03:00
}
// There should now be one less page remaining.
currentRemaining := backup . Remaining ( )
expectedRemaining := latestRemaining - 1
if currentRemaining != expectedRemaining {
2018-07-22 23:32:19 +03:00
t . Fatalf ( "unexpected remaining value; expected remaining value: %v; actual remaining value: %v" , expectedRemaining , currentRemaining )
2016-08-21 01:57:12 +03:00
}
latestRemaining = currentRemaining
if isDone {
break
}
// Limit the runtime of the backup attempt.
if ( time . Now ( ) . Unix ( ) - startTime ) > usePagePerStepsTimeoutSeconds {
2018-07-22 23:32:19 +03:00
t . Fatal ( "backup is taking longer than expected." )
2016-08-21 01:57:12 +03:00
}
}
} else {
// Test the copying of all remaining pages.
isDone , err = backup . Step ( - 1 )
if err != nil {
2018-07-22 23:32:19 +03:00
t . Fatal ( "failed to perform a backup step:" , err )
2016-08-21 01:57:12 +03:00
}
if ! isDone {
2018-07-22 23:32:19 +03:00
t . Fatal ( "backup is unexpectedly not done." )
2016-08-21 01:57:12 +03:00
}
}
// Check that the page count and remaining values are reasonable.
finalPageCount := backup . PageCount ( )
if finalPageCount != initialPageCount {
2018-07-22 23:32:19 +03:00
t . Fatalf ( "final page count differs from the initial page count; initial page count: %v; final page count: %v" , initialPageCount , finalPageCount )
2016-08-21 01:57:12 +03:00
}
finalRemaining := backup . Remaining ( )
if finalRemaining != 0 {
2018-07-22 23:32:19 +03:00
t . Fatalf ( "unexpected remaining value: %v" , finalRemaining )
2016-08-21 01:57:12 +03:00
}
// Finish the backup.
err = backup . Finish ( )
if err != nil {
2018-07-22 23:32:19 +03:00
t . Fatal ( "failed to finish backup:" , err )
2016-08-21 01:57:12 +03:00
}
// Confirm that the "test" table now exists in the destination database.
var doesTestTableExist bool
err = destDb . QueryRow ( "SELECT EXISTS (SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'test' LIMIT 1) AS test_table_exists" ) . Scan ( & doesTestTableExist )
if err != nil {
2018-07-22 23:32:19 +03:00
t . Fatal ( "failed to check if the \"test\" table exists in the destination database:" , err )
2016-08-21 01:57:12 +03:00
}
if ! doesTestTableExist {
2018-07-22 23:32:19 +03:00
t . Fatal ( "the \"test\" table could not be found in the destination database." )
2016-08-21 01:57:12 +03:00
}
// Confirm that the number of rows in the destination database's "test" table matches that of the source table.
var actualTestTableRowCount int
err = destDb . QueryRow ( "SELECT COUNT(*) FROM test" ) . Scan ( & actualTestTableRowCount )
if err != nil {
2018-07-22 23:32:19 +03:00
t . Fatal ( "failed to determine the rowcount of the \"test\" table in the destination database:" , err )
2016-08-21 01:57:12 +03:00
}
if testRowCount != actualTestTableRowCount {
2018-07-22 23:32:19 +03:00
t . Fatalf ( "unexpected destination \"test\" table row count; expected: %v; found: %v" , testRowCount , actualTestTableRowCount )
2016-08-21 01:57:12 +03:00
}
// Check each of the rows in the destination database.
for id := 0 ; id < testRowCount ; id ++ {
var checkedValue string
err = destDb . QueryRow ( "SELECT value FROM test WHERE id = ?" , id ) . Scan ( & checkedValue )
if err != nil {
2018-07-22 23:32:19 +03:00
t . Fatal ( "failed to query the \"test\" table in the destination database:" , err )
2016-08-21 01:57:12 +03:00
}
var expectedValue = generateTestData ( id )
if checkedValue != expectedValue {
2018-07-22 23:32:19 +03:00
t . Fatalf ( "unexpected value in the \"test\" table in the destination database; expected value: %v; actual value: %v" , expectedValue , checkedValue )
2016-08-21 01:57:12 +03:00
}
}
}
func TestBackupStepByStep ( t * testing . T ) {
testBackup ( t , testRowCount , true )
}
func TestBackupAllRemainingPages ( t * testing . T ) {
testBackup ( t , testRowCount , false )
}
2016-09-23 15:41:32 +03:00
// Test the error reporting when preparing to perform a backup.
func TestBackupError ( t * testing . T ) {
2018-06-26 17:33:28 +03:00
driverName := "sqlite3_TestBackupError"
2016-09-23 15:41:32 +03:00
// The driver's connection will be needed in order to perform the backup.
2018-06-24 23:57:57 +03:00
var dbDriverConn [ ] * SQLiteConn
2016-09-23 15:41:32 +03:00
sql . Register ( driverName , & SQLiteDriver {
ConnectHook : func ( conn * SQLiteConn ) error {
2018-06-24 23:57:57 +03:00
dbDriverConn = append ( dbDriverConn , conn )
2016-09-23 15:41:32 +03:00
return nil
} ,
} )
// Connect to the database.
dbTempFilename := TempFilename ( t )
defer os . Remove ( dbTempFilename )
2018-06-24 23:57:57 +03:00
srcDb , err := sql . Open ( driverName , dbTempFilename )
2016-09-23 15:41:32 +03:00
if err != nil {
2018-07-22 23:32:19 +03:00
t . Fatal ( "failed to open the database:" , err )
2016-09-23 15:41:32 +03:00
}
2018-06-24 23:57:57 +03:00
defer srcDb . Close ( )
srcDb . Ping ( )
srcDriverConn := dbDriverConn [ 0 ]
2016-09-23 15:41:32 +03:00
// Need the driver connection in order to perform the backup.
2018-06-24 23:57:57 +03:00
if srcDriverConn == nil {
2018-07-22 23:32:19 +03:00
t . Fatal ( "failed to get the driver connection." )
2016-09-23 15:41:32 +03:00
}
// Prepare to perform the backup.
// Intentionally using the same connection for both the source and destination databases, to trigger an error result.
2018-06-24 23:57:57 +03:00
backup , err := srcDriverConn . Backup ( "main" , srcDriverConn , "main" )
2016-09-23 15:41:32 +03:00
if err == nil {
2018-07-22 23:32:19 +03:00
t . Fatal ( "failed to get the expected error result." )
2016-09-23 15:41:32 +03:00
}
const expectedError = "source and destination must be distinct"
if err . Error ( ) != expectedError {
2018-07-22 23:32:19 +03:00
t . Fatalf ( "unexpected error message; expected value: \"%v\"; actual value: \"%v\"" , expectedError , err . Error ( ) )
2016-09-23 15:41:32 +03:00
}
if backup != nil {
t . Fatal ( "Failed to get the expected nil backup result." )
}
2018-06-24 23:57:57 +03:00
// Generate some test data for the given ID.
var generateTestData = func ( id int ) string {
return fmt . Sprintf ( "test-%v" , id )
}
// Populate the source database with a test table containing some test data.
tx , err := srcDb . Begin ( )
if err != nil {
2018-07-22 23:32:19 +03:00
t . Fatal ( "failed to begin a transaction when populating the source database:" , err )
2018-06-24 23:57:57 +03:00
}
_ , err = srcDb . Exec ( "CREATE TABLE test (id INTEGER PRIMARY KEY, value TEXT)" )
if err != nil {
tx . Rollback ( )
2018-07-22 23:32:19 +03:00
t . Fatal ( "failed to create the source database \"test\" table:" , err )
2018-06-24 23:57:57 +03:00
}
for id := 0 ; id < testRowCount ; id ++ {
_ , err = srcDb . Exec ( "INSERT INTO test (id, value) VALUES (?, ?)" , id , generateTestData ( id ) )
if err != nil {
tx . Rollback ( )
2018-07-22 23:32:19 +03:00
t . Fatal ( "failed to insert a row into the source database \"test\" table:" , err )
2018-06-24 23:57:57 +03:00
}
}
err = tx . Commit ( )
if err != nil {
2018-07-22 23:32:19 +03:00
t . Fatal ( "failed to populate the source database:" , err )
2018-06-24 23:57:57 +03:00
}
destTempFilename := TempFilename ( t )
defer os . Remove ( destTempFilename )
2018-06-26 17:33:28 +03:00
// Create Database
destDb , err := sql . Open ( driverName , fmt . Sprintf ( "file:%s" , destTempFilename ) )
if err != nil {
2018-07-22 23:32:19 +03:00
t . Fatal ( "failed to open the database:" , err )
2018-06-26 17:33:28 +03:00
}
destDb . Ping ( )
_ , err = destDb . Exec ( "SELECT 1;" ) // Create Database
if err != nil {
t . Fatal ( err )
}
destDb . Close ( )
2018-06-24 23:57:57 +03:00
2018-06-26 17:33:28 +03:00
var destDriverConn * SQLiteConn
driverName = "sqlite3_dest_readonly"
sql . Register ( driverName , & SQLiteDriver {
ConnectHook : func ( conn * SQLiteConn ) error {
destDriverConn = conn
return nil
} ,
} )
// Re-Open as ReadOnly
destDb , err = sql . Open ( driverName , fmt . Sprintf ( "file:%s?mode=ro" , destTempFilename ) )
if err != nil {
2018-07-22 23:32:19 +03:00
t . Fatal ( "failed to open the database:" , err )
2018-06-24 23:57:57 +03:00
}
2018-06-26 17:33:28 +03:00
destDb . Ping ( )
defer destDb . Close ( )
2018-06-24 23:57:57 +03:00
2018-06-26 17:33:28 +03:00
// Need the driver connection in order to perform the backup.
if destDriverConn == nil {
2018-07-22 23:32:19 +03:00
t . Fatal ( "failed to get the driver connection." )
2018-06-26 17:33:28 +03:00
}
2018-06-24 23:57:57 +03:00
2018-06-26 17:33:28 +03:00
backup , err = destDriverConn . Backup ( "main" , srcDriverConn , "main" )
2018-06-24 23:57:57 +03:00
_ , err = backup . Step ( 0 )
if err == nil || err . ( Error ) . Code != ErrReadonly {
2018-07-22 23:32:19 +03:00
t . Fatalf ( "expected read-only error; received: (%d) %s" , err . ( Error ) . Code , err . ( Error ) . Error ( ) )
2018-06-24 23:57:57 +03:00
}
err = backup . Close ( )
if err == nil || err . ( Error ) . Code != ErrReadonly {
2018-07-22 23:32:19 +03:00
t . Fatalf ( "expected read-only error; received: (%d) %s" , err . ( Error ) . Code , err . ( Error ) . Error ( ) )
2018-06-24 23:57:57 +03:00
}
2016-09-23 15:41:32 +03:00
}