-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Serialize and Deserialize support (#1089)
Add support for Serialize and Deserialize, which wrap sqlite3_serialize and sqlite3_deserialize.
- Loading branch information
Showing
4 changed files
with
202 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
// +build !libsqlite3 sqlite_serialize | ||
|
||
package sqlite3 | ||
|
||
/* | ||
#ifndef USE_LIBSQLITE3 | ||
#include <sqlite3-binding.h> | ||
#else | ||
#include <sqlite3.h> | ||
#endif | ||
#include <stdlib.h> | ||
#include <stdint.h> | ||
*/ | ||
import "C" | ||
|
||
import ( | ||
"fmt" | ||
"math" | ||
"reflect" | ||
"unsafe" | ||
) | ||
|
||
// Serialize returns a byte slice that is a serialization of the database. | ||
// | ||
// See https://www.sqlite.org/c3ref/serialize.html | ||
func (c *SQLiteConn) Serialize(schema string) ([]byte, error) { | ||
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, fmt.Errorf("serialize failed") | ||
} | ||
defer C.sqlite3_free(unsafe.Pointer(ptr)) | ||
|
||
if sz > C.sqlite3_int64(math.MaxInt) { | ||
return nil, fmt.Errorf("serialized database is too large (%d bytes)", sz) | ||
} | ||
|
||
cBuf := *(*[]byte)(unsafe.Pointer(&reflect.SliceHeader{ | ||
Data: uintptr(unsafe.Pointer(ptr)), | ||
Len: int(sz), | ||
Cap: int(sz), | ||
})) | ||
|
||
res := make([]byte, int(sz)) | ||
copy(res, cBuf) | ||
return res, nil | ||
} | ||
|
||
// 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. | ||
// | ||
// 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)) | ||
|
||
tmpBuf := (*C.uchar)(C.sqlite3_malloc64(C.sqlite3_uint64(len(b)))) | ||
cBuf := *(*[]byte)(unsafe.Pointer(&reflect.SliceHeader{ | ||
Data: uintptr(unsafe.Pointer(tmpBuf)), | ||
Len: len(b), | ||
Cap: len(b), | ||
})) | ||
copy(cBuf, b) | ||
|
||
rc := C.sqlite3_deserialize(c.db, zSchema, tmpBuf, C.sqlite3_int64(len(b)), | ||
C.sqlite3_int64(len(b)), C.SQLITE_DESERIALIZE_FREEONCLOSE) | ||
if rc != C.SQLITE_OK { | ||
return fmt.Errorf("deserialize failed with return %v", rc) | ||
} | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
// +build libsqlite3,!sqlite_serialize | ||
|
||
package sqlite3 | ||
|
||
import ( | ||
"errors" | ||
) | ||
|
||
/* | ||
#cgo CFLAGS: -DSQLITE_OMIT_DESERIALIZE | ||
*/ | ||
import "C" | ||
|
||
func (c *SQLiteConn) Serialize(schema string) ([]byte, error) { | ||
return nil, errors.New("sqlite3: Serialize requires the sqlite_serialize build tag when using the libsqlite3 build tag") | ||
} | ||
|
||
func (c *SQLiteConn) Deserialize(b []byte, schema string) error { | ||
return errors.New("sqlite3: Deserialize requires the sqlite_serialize build tag when using the libsqlite3 build tag") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
// +build !libsqlite3 sqlite_serialize | ||
|
||
package sqlite3 | ||
|
||
import ( | ||
"context" | ||
"database/sql" | ||
"os" | ||
"testing" | ||
) | ||
|
||
func TestSerializeDeserialize(t *testing.T) { | ||
// Connect to the source database. | ||
srcTempFilename := TempFilename(t) | ||
defer os.Remove(srcTempFilename) | ||
srcDb, err := sql.Open(driverName, srcTempFilename) | ||
if err != nil { | ||
t.Fatal("Failed to open the source database:", err) | ||
} | ||
defer srcDb.Close() | ||
err = srcDb.Ping() | ||
if err != nil { | ||
t.Fatal("Failed to connect to the source database:", err) | ||
} | ||
|
||
// Connect to the destination database. | ||
destTempFilename := TempFilename(t) | ||
defer os.Remove(destTempFilename) | ||
destDb, err := sql.Open(driverName, destTempFilename) | ||
if err != nil { | ||
t.Fatal("Failed to open the destination database:", err) | ||
} | ||
defer destDb.Close() | ||
err = destDb.Ping() | ||
if err != nil { | ||
t.Fatal("Failed to connect to the destination database:", err) | ||
} | ||
|
||
// Write data to source database. | ||
_, err = srcDb.Exec(`CREATE TABLE foo (name string)`) | ||
if err != nil { | ||
t.Fatal("Failed to create table in source database:", err) | ||
} | ||
_, err = srcDb.Exec(`INSERT INTO foo(name) VALUES("alice")`) | ||
if err != nil { | ||
t.Fatal("Failed to insert data into source database", err) | ||
} | ||
|
||
// Serialize the source database | ||
srcConn, err := srcDb.Conn(context.Background()) | ||
if err != nil { | ||
t.Fatal("Failed to get connection to source database:", err) | ||
} | ||
defer srcConn.Close() | ||
|
||
var serialized []byte | ||
if err := srcConn.Raw(func(raw interface{}) error { | ||
var err error | ||
serialized, err = raw.(*SQLiteConn).Serialize("") | ||
return err | ||
}); err != nil { | ||
t.Fatal("Failed to serialize source database:", err) | ||
} | ||
srcConn.Close() | ||
|
||
// 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 { | ||
t.Fatal("Failed to check the destination table count:", err) | ||
} | ||
if destTableCount != 0 { | ||
t.Fatalf("The destination database is not empty; %v table(s) found.", destTableCount) | ||
} | ||
|
||
// Deserialize to destination database | ||
destConn, err := destDb.Conn(context.Background()) | ||
if err != nil { | ||
t.Fatal("Failed to get connection to destination database:", err) | ||
} | ||
defer destConn.Close() | ||
|
||
if err := destConn.Raw(func(raw interface{}) error { | ||
return raw.(*SQLiteConn).Deserialize(serialized, "") | ||
}); err != nil { | ||
t.Fatal("Failed to deserialize source database:", err) | ||
} | ||
destConn.Close() | ||
|
||
// Confirm that destination database has been loaded correctly. | ||
var destRowCount int | ||
err = destDb.QueryRow(`SELECT COUNT(*) FROM foo`).Scan(&destRowCount) | ||
if err != nil { | ||
t.Fatal("Failed to count rows in destination database table", err) | ||
} | ||
if destRowCount != 1 { | ||
t.Fatalf("Destination table does not have the expected records") | ||
} | ||
} |