Skip to content

Support serialize and deserialize #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jan 29, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions sqlite3.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package sqlite3
#cgo CFLAGS: -DSQLITE_ENABLE_FTS3_PARENTHESIS
#cgo CFLAGS: -DSQLITE_TRACE_SIZE_LIMIT=15
#cgo CFLAGS: -DSQLITE_OMIT_DEPRECATED
#cgo CFLAGS: -DSQLITE_ENABLE_DESERIALIZE
#cgo CFLAGS: -DSQLITE_DEFAULT_WAL_SYNCHRONOUS=1
#cgo CFLAGS: -DSQLITE_ENABLE_UPDATE_DELETE_LIMIT
#cgo CFLAGS: -Wno-deprecated-declarations
Expand Down Expand Up @@ -905,6 +906,49 @@ func (c *SQLiteConn) begin(ctx context.Context) (driver.Tx, error) {
return &SQLiteTx{c}, nil
}

// Serialize returns a byte slice that is a serialization of the database.
// If the database fails to serialize, a nil slice will be returned.
//
// See https://www.sqlite.org/c3ref/serialize.html
func (c *SQLiteConn) Serialize(schema string) []byte {
if schema == "" {
schema = "main"
}
var zSchema *C.char
zSchema = C.CString(schema)
defer C.free(unsafe.Pointer(zSchema))

var sz C.sqlite3_int64
ptr := C.sqlite3_serialize(c.db, zSchema, &sz, 0)
if ptr == nil {
return nil
}
return C.GoBytes(unsafe.Pointer(ptr), C.int(sz))
}

// Deserialize causes the connection to disconnect from the current database
// and then re-open as an in-memory database based on the contents of the
// byte slice. If deserelization fails, error will contain the return code
// of the underlying SQLite API call.
//
// See https://www.sqlite.org/c3ref/deserialize.html
func (c *SQLiteConn) Deserialize(b []byte, schema string) error {
if schema == "" {
schema = "main"
}
var zSchema *C.char
zSchema = C.CString(schema)
defer C.free(unsafe.Pointer(zSchema))

rc := C.sqlite3_deserialize(c.db, zSchema,
(*C.uint8_t)(unsafe.Pointer(&b[0])),
C.sqlite3_int64(len(b)), C.sqlite3_int64(len(b)), 0)
if rc != 0 {
return fmt.Errorf("deserialize failed with return %v", rc)
}
return nil
}

// Open database and return a new connection.
//
// A pragma can take either zero or one argument.
Expand Down
105 changes: 105 additions & 0 deletions sqlite3_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -888,6 +888,111 @@ func TestTransaction(t *testing.T) {
}
}

func TestSerialize(t *testing.T) {
d := SQLiteDriver{}

srcConn, err := d.Open(":memory:")
if err != nil {
t.Fatal("failed to get database connection:", err)
}
defer srcConn.Close()
sqlite3conn := srcConn.(*SQLiteConn)

_, err = sqlite3conn.Exec(`CREATE TABLE foo (name string)`, nil)
if err != nil {
t.Fatal("failed to create table:", err)
}
_, err = sqlite3conn.Exec(`INSERT INTO foo(name) VALUES("alice")`, nil)
if err != nil {
t.Fatal("failed to insert record:", err)
}

// Serialize the database to a file
tempFilename := TempFilename(t)
defer os.Remove(tempFilename)
if err := ioutil.WriteFile(tempFilename, sqlite3conn.Serialize(""), 0644); err != nil {
t.Fatalf("failed to write serialized database to disk")
}

// Open the SQLite3 file, and test that contents are as expected.
db, err := sql.Open("sqlite3", tempFilename)
if err != nil {
t.Fatal("failed to open database:", err)
}
defer db.Close()

rows, err := db.Query(`SELECT * FROM foo`)
if err != nil {
t.Fatal("failed to query database:", err)
}
defer rows.Close()

rows.Next()

var name string
rows.Scan(&name)
if exp, got := name, "alice"; exp != got {
t.Errorf("Expected %s for fetched result, but got %s:", exp, got)
}
}

func TestDeserialize(t *testing.T) {
var sqlite3conn *SQLiteConn
d := SQLiteDriver{}
tempFilename := TempFilename(t)
defer os.Remove(tempFilename)

// Create source database on disk.
conn, err := d.Open(tempFilename)
if err != nil {
t.Fatal("failed to open on-disk database:", err)
}
defer conn.Close()
sqlite3conn = conn.(*SQLiteConn)
_, err = sqlite3conn.Exec(`CREATE TABLE foo (name string)`, nil)
if err != nil {
t.Fatal("failed to create table:", err)
}
_, err = sqlite3conn.Exec(`INSERT INTO foo(name) VALUES("alice")`, nil)
if err != nil {
t.Fatal("failed to insert record:", err)
}
conn.Close()

// Read database file bytes from disk.
b, err := ioutil.ReadFile(tempFilename)
if err != nil {
t.Fatal("failed to read database file on disk", err)
}

// Deserialize file contents into memory.
conn, err = d.Open(":memory:")
if err != nil {
t.Fatal("failed to open in-memory database:", err)
}
sqlite3conn = conn.(*SQLiteConn)
defer conn.Close()
if err := sqlite3conn.Deserialize(b, ""); err != nil {
t.Fatal("failed to deserialize database", err)
}

// Check database contents are as expected.
rows, err := sqlite3conn.Query(`SELECT * FROM foo`, nil)
if err != nil {
t.Fatal("failed to query database:", err)
}
if len(rows.Columns()) != 1 {
t.Fatal("incorrect number of columns returned:", len(rows.Columns()))
}
values := make([]driver.Value, 1)
rows.Next(values)
if v, ok := values[0].(string); !ok {
t.Fatalf("wrong type for value: %T", v)
} else if v != "alice" {
t.Fatal("wrong value returned", v)
}
}

func TestWAL(t *testing.T) {
tempFilename := TempFilename(t)
defer os.Remove(tempFilename)
Expand Down