Skip to content

Commit d1ac355

Browse files
JathonJathon
authored andcommitted
上传代码
1 parent 68c9184 commit d1ac355

File tree

115 files changed

+562942
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

115 files changed

+562942
-0
lines changed

backup.go

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
// Copyright (C) 2019 Yasuhiro Matsumoto <[email protected]>.
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+
package sqlite3
7+
8+
/*
9+
#ifndef USE_LIBSQLITE3
10+
#include "sqlite3-binding.h"
11+
#else
12+
#include <sqlite3.h>
13+
#endif
14+
#include <stdlib.h>
15+
*/
16+
import "C"
17+
import (
18+
"runtime"
19+
"unsafe"
20+
)
21+
22+
// SQLiteBackup implement interface of Backup.
23+
type SQLiteBackup struct {
24+
b *C.sqlite3_backup
25+
}
26+
27+
// Backup make backup from src to dest.
28+
func (destConn *SQLiteConn) Backup(dest string, srcConn *SQLiteConn, src string) (*SQLiteBackup, error) {
29+
destptr := C.CString(dest)
30+
defer C.free(unsafe.Pointer(destptr))
31+
srcptr := C.CString(src)
32+
defer C.free(unsafe.Pointer(srcptr))
33+
34+
if b := C.sqlite3_backup_init(destConn.db, destptr, srcConn.db, srcptr); b != nil {
35+
bb := &SQLiteBackup{b: b}
36+
runtime.SetFinalizer(bb, (*SQLiteBackup).Finish)
37+
return bb, nil
38+
}
39+
return nil, destConn.lastError()
40+
}
41+
42+
// Step to backs up for one step. Calls the underlying `sqlite3_backup_step`
43+
// function. This function returns a boolean indicating if the backup is done
44+
// and an error signalling any other error. Done is returned if the underlying
45+
// C function returns SQLITE_DONE (Code 101)
46+
func (b *SQLiteBackup) Step(p int) (bool, error) {
47+
ret := C.sqlite3_backup_step(b.b, C.int(p))
48+
if ret == C.SQLITE_DONE {
49+
return true, nil
50+
} else if ret != 0 && ret != C.SQLITE_LOCKED && ret != C.SQLITE_BUSY {
51+
return false, Error{Code: ErrNo(ret)}
52+
}
53+
return false, nil
54+
}
55+
56+
// Remaining return whether have the rest for backup.
57+
func (b *SQLiteBackup) Remaining() int {
58+
return int(C.sqlite3_backup_remaining(b.b))
59+
}
60+
61+
// PageCount return count of pages.
62+
func (b *SQLiteBackup) PageCount() int {
63+
return int(C.sqlite3_backup_pagecount(b.b))
64+
}
65+
66+
// Finish close backup.
67+
func (b *SQLiteBackup) Finish() error {
68+
return b.Close()
69+
}
70+
71+
// Close close backup.
72+
func (b *SQLiteBackup) Close() error {
73+
ret := C.sqlite3_backup_finish(b.b)
74+
75+
// sqlite3_backup_finish() never fails, it just returns the
76+
// error code from previous operations, so clean up before
77+
// checking and returning an error
78+
b.b = nil
79+
runtime.SetFinalizer(b, nil)
80+
81+
if ret != 0 {
82+
return Error{Code: ErrNo(ret)}
83+
}
84+
return nil
85+
}

backup_test.go

Lines changed: 294 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,294 @@
1+
// Copyright (C) 2019 Yasuhiro Matsumoto <[email protected]>.
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+
8+
package sqlite3
9+
10+
import (
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.
20+
const testRowCount = 100
21+
22+
// The maximum number of seconds after which the page-by-page backup is considered to have taken too long.
23+
const usePagePerStepsTimeoutSeconds = 30
24+
25+
// Test the backup functionality.
26+
func 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+
245+
func TestBackupStepByStep(t *testing.T) {
246+
testBackup(t, testRowCount, true)
247+
}
248+
249+
func TestBackupAllRemainingPages(t *testing.T) {
250+
testBackup(t, testRowCount, false)
251+
}
252+
253+
// Test the error reporting when preparing to perform a backup.
254+
func 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+
}

0 commit comments

Comments
 (0)