diff --git a/backup.go b/backup.go new file mode 100644 index 0000000..7b53bd9 --- /dev/null +++ b/backup.go @@ -0,0 +1,132 @@ +// Copyright (c) 2018 David Crawshaw +// +// Permission to use, copy, modify, and distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +package sqlite + +// #include +// #include +import "C" +import ( + "runtime" + "unsafe" +) + +// A Backup copies data between two databases. +// +// It is used to backup file based or in-memory databases. +// +// Equivalent to the sqlite3_backup* C object. +// +// https://www.sqlite.org/c3ref/backup_finish.html +type Backup struct { + ptr *C.sqlite3_backup +} + +// BackupToDB creates a complete backup of the srcDB on the src Conn to a new +// database Conn at dstPath. The resulting dst connection is returned. This +// will block until the entire backup is complete. +// +// If srcDB is "", then a default of "main" is used. +// +// This is very similar to the first example function implemented on the +// following page. +// https://www.sqlite.org/backup.html +func (src *Conn) BackupToDB(srcDB, dstPath string) (dst *Conn, err error) { + if dst, err = OpenConn(dstPath, 0); err != nil { + return + } + defer func() { + if err != nil { + dst.Close() + } + }() + b, err := src.BackupInit(srcDB, "", dst) + if err != nil { + return + } + defer b.Finish() + err = b.Step(-1) + return +} + +// BackupInit initializes a new Backup object to copy from src to dst. +// +// If srcDB or dstDB is "", then a default of "main" is used. +// +// https://www.sqlite.org/c3ref/backup_finish.html#sqlite3backupinit +func (src *Conn) BackupInit(srcDB, dstDB string, dst *Conn) (*Backup, error) { + var srcCDB, dstCDB *C.char + defer setCDB(dstDB, &dstCDB)() + defer setCDB(srcDB, &srcCDB)() + b := &Backup{} + b.ptr = C.sqlite3_backup_init(dst.conn, dstCDB, src.conn, srcCDB) + if b.ptr == nil { + res := C.sqlite3_errcode(dst.conn) + return nil, dst.extreserr("Conn.BackupInit", "", res) + } + runtime.SetFinalizer(b, func(b *Backup) { + if b.ptr != nil { + panic("open *sqlite.Backup garbage collected, call Finish method") + } + }) + + return b, nil +} +func setCDB(db string, cdb **C.char) func() { + if db == "" || db == "main" { + *cdb = cmain + return func() {} + } + *cdb = C.CString(db) + return func() { C.free(unsafe.Pointer(cdb)) } +} + +// Step is called one or more times to transfer nPage pages at a time between +// databases. +// +// Use -1 to transfer the entire database at once. +// +// https://www.sqlite.org/c3ref/backup_finish.html#sqlite3backupstep +func (b *Backup) Step(nPage int) error { + res := C.sqlite3_backup_step(b.ptr, C.int(nPage)) + if res != C.SQLITE_DONE { + return reserr("Backup.Step", "", "", res) + } + return nil +} + +// Finish is called to clean up the resources allocated by BackupInit. +// +// https://www.sqlite.org/c3ref/backup_finish.html#sqlite3backupfinish +func (b *Backup) Finish() error { + res := C.sqlite3_backup_finish(b.ptr) + b.ptr = nil + return reserr("Backup.Finish", "", "", res) +} + +// Remaining returns the number of pages still to be backed up at the +// conclusion of the most recent b.Step(). +// +// https://www.sqlite.org/c3ref/backup_finish.html#sqlite3backupremaining +func (b *Backup) Remaining() int { + return int(C.sqlite3_backup_remaining(b.ptr)) +} + +// PageCount returns the total number of pages in the source database at the +// conclusion of the most recent b.Step(). +// +// https://www.sqlite.org/c3ref/backup_finish.html#sqlite3backuppagecount +func (b *Backup) PageCount() int { + return int(C.sqlite3_backup_pagecount(b.ptr)) +} diff --git a/backup_test.go b/backup_test.go new file mode 100644 index 0000000..b349d71 --- /dev/null +++ b/backup_test.go @@ -0,0 +1,64 @@ +// Copyright (c) 2018 David Crawshaw +// +// Permission to use, copy, modify, and distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +package sqlite_test + +import ( + "testing" + + "crawshaw.io/sqlite" + "crawshaw.io/sqlite/sqlitex" +) + +func initSrc(t *testing.T) *sqlite.Conn { + conn, err := sqlite.OpenConn(`:memory:`, 0) + if err != nil { + t.Fatal(err) + } + if err := sqlitex.ExecScript(conn, `CREATE TABLE t (c1 PRIMARY KEY, c2, c3); + INSERT INTO t (c1, c2, c3) VALUES (1, 2, 3); + INSERT INTO t (c1, c2, c3) VALUES (2, 4, 5);`); err != nil { + conn.Close() + t.Fatal(err) + } + return conn +} + +func TestBackup(t *testing.T) { + conn := initSrc(t) + defer conn.Close() + + copyConn, err := conn.BackupToDB("", ":memory:") + if err != nil { + t.Fatal(err) + } + defer copyConn.Close() + + count, err := sqlitex.ResultInt(copyConn.Prep(`SELECT count(*) FROM t;`)) + if err != nil { + t.Fatal(err) + } + if count != 2 { + t.Fatalf("expected 2 rows but found %v", count) + } + c2, err := sqlitex.ResultInt(copyConn.Prep(`SELECT c2 FROM t WHERE c1 = 1;`)) + if c2 != 2 { + t.Fatalf("expected row1 c2 to be 2 but found %v", c2) + } + + c2, err = sqlitex.ResultInt(copyConn.Prep(`SELECT c2 FROM t WHERE c1 = 2;`)) + if c2 != 4 { + t.Fatalf("expected row2 c2 to be 4 but found %v", c2) + } +}