Skip to content

Commit

Permalink
Add Backup type for the Online Backup API
Browse files Browse the repository at this point in the history
Implements sqlite3_backup type as Backup and the necessary methods.

Conn.BackupInit()
Backup.Step()
Backup.PageCount()
Backup.Remaining()
Backup.Finish()

Also add the Conn.BackupToDB() helper function for creating a backup at
a given path.
  • Loading branch information
AdamSLevy committed Sep 7, 2019
1 parent 6cbd592 commit e49cdaf
Show file tree
Hide file tree
Showing 2 changed files with 196 additions and 0 deletions.
132 changes: 132 additions & 0 deletions backup.go
@@ -0,0 +1,132 @@
// Copyright (c) 2018 David Crawshaw <david@zentus.com>
//
// 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 <sqlite3.h>
// #include <stdlib.h>
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))
}
64 changes: 64 additions & 0 deletions backup_test.go
@@ -0,0 +1,64 @@
// Copyright (c) 2018 David Crawshaw <david@zentus.com>
//
// 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)
}
}

0 comments on commit e49cdaf

Please sign in to comment.