Skip to content
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

Add Serialize and Deserialize support #1089

Merged
merged 38 commits into from Nov 17, 2022
Merged
Show file tree
Hide file tree
Changes from 36 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
1d8534a
Add Serialize and Deserialize support
otoolep Sep 6, 2022
ecd2680
Merge pull request #1 from mattn/master
otoolep Sep 11, 2022
613b879
Add support for sqlite_omit_deserialize
otoolep Sep 11, 2022
7069591
Fix up imports
otoolep Sep 11, 2022
0cc635c
Add missing import
otoolep Sep 11, 2022
17653b6
go fmt fixes
otoolep Sep 11, 2022
b3777b7
Use existing build tag style
otoolep Sep 11, 2022
a594404
Merge remote-tracking branch 'origin' into add-serialize-deserialize
otoolep Sep 11, 2022
d4d2212
No Serialize with libsqlite tests
otoolep Sep 11, 2022
d607e75
More libsqlite3 test fixups
otoolep Sep 11, 2022
596960a
Update expected test output
otoolep Sep 13, 2022
21b8dd9
Fix up build tags
otoolep Sep 13, 2022
03ba6ac
Serialize() should return an explicit error
otoolep Sep 13, 2022
464b7f3
Add missing rows.Close()
otoolep Sep 13, 2022
7035040
More build tag fixes
otoolep Sep 13, 2022
ba8ffa0
Update build tags
otoolep Sep 13, 2022
270263f
Update README for build tags
otoolep Sep 13, 2022
491eab1
AND logic for build tags
otoolep Sep 13, 2022
ac1d03c
Remove blank line
otoolep Sep 13, 2022
c6d6296
More memory management fixes
otoolep Sep 13, 2022
99fe433
Update sqlite3_opt_serialize.go
otoolep Sep 14, 2022
7ed94a9
Update sqlite3_opt_serialize_omit.go
otoolep Sep 14, 2022
03bb574
More fixes
otoolep Sep 14, 2022
9dd368b
Merge branch 'add-serialize-deserialize' of github.com:otoolep/go-sql…
otoolep Sep 14, 2022
d2e464d
Remove unused const
otoolep Sep 14, 2022
469d82c
Update sqlite3_opt_serialize_omit.go
otoolep Sep 14, 2022
a404658
Fix up build tags
otoolep Sep 14, 2022
0740ee9
Fix error messages
otoolep Sep 14, 2022
4516d61
Remove unused import
otoolep Sep 14, 2022
a0dc1ef
Import "errors"
otoolep Sep 14, 2022
f1d96d7
go fmt
otoolep Sep 14, 2022
ec6e50d
Fix copy 'n' paste error
otoolep Sep 14, 2022
3bbd5c1
Address latest comments
otoolep Sep 19, 2022
96afe68
Correct typo
otoolep Sep 19, 2022
6b69194
Write Serialization test using database/sql
otoolep Oct 7, 2022
8873883
Use existing style for build tags
otoolep Oct 7, 2022
2f60ff2
Update test as per PR comments
otoolep Nov 8, 2022
9ec6bc6
Fix up Serialize unit test
otoolep Nov 8, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/docker.yaml
Expand Up @@ -19,4 +19,4 @@ jobs:
run: |
cd ./_example/simple
docker build -t simple .
docker run simple | grep 99\ こんにちわ世界099
docker run simple | grep 99\ こんにちは世界099
1 change: 1 addition & 0 deletions README.md
Expand Up @@ -165,6 +165,7 @@ go build --tags "icu json1 fts5 secure_delete"
| Allow URI Authority | sqlite_allow_uri_authority | URI filenames normally throws an error if the authority section is not either empty or "localhost".<br><br>However, if SQLite is compiled with the SQLITE_ALLOW_URI_AUTHORITY compile-time option, then the URI is converted into a Uniform Naming Convention (UNC) filename and passed down to the underlying operating system that way |
| App Armor | sqlite_app_armor | When defined, this C-preprocessor macro activates extra code that attempts to detect misuse of the SQLite API, such as passing in NULL pointers to required parameters or using objects after they have been destroyed. <br><br>App Armor is not available under `Windows`. |
| Disable Load Extensions | sqlite_omit_load_extension | Loading of external extensions is enabled by default.<br><br>To disable extension loading add the build tag `sqlite_omit_load_extension`. |
| Enable Serialization with `libsqlite3` | sqlite_serialize | Serialization and deserialization of a SQLite database is available by default, unless the build tag `libsqlite3` is set.<br><br>To enable this functionality even if `libsqlite3` is set, add the build tag `sqlite_serialize`. |
| Foreign Keys | sqlite_foreign_keys | This macro determines whether enforcement of foreign key constraints is enabled or disabled by default for new database connections.<br><br>Each database connection can always turn enforcement of foreign key constraints on and off and run-time using the foreign_keys pragma.<br><br>Enforcement of foreign key constraints is normally off by default, but if this compile-time parameter is set to 1, enforcement of foreign key constraints will be on by default |
| Full Auto Vacuum | sqlite_vacuum_full | Set the default auto vacuum to full |
| Incremental Auto Vacuum | sqlite_vacuum_incr | Set the default auto vacuum to incremental |
Expand Down
82 changes: 82 additions & 0 deletions sqlite3_opt_serialize.go
@@ -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) {
otoolep marked this conversation as resolved.
Show resolved Hide resolved
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) {
otoolep marked this conversation as resolved.
Show resolved Hide resolved
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)
otoolep marked this conversation as resolved.
Show resolved Hide resolved
}
return nil
}
20 changes: 20 additions & 0 deletions sqlite3_opt_serialize_omit.go
@@ -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")
}
101 changes: 101 additions & 0 deletions sqlite3_opt_serialize_test.go
@@ -0,0 +1,101 @@
// +build !libsqlite3 sqlite_serialize

package sqlite3

import (
"database/sql"
"os"
"testing"
)

func TestSerializeDeserialize(t *testing.T) {
// The driver's connection will be needed in order to serialization and deserialization
driverName := "TestSerializeDeserialize"
driverConns := []*SQLiteConn{}
sql.Register(driverName, &SQLiteDriver{
ConnectHook: func(conn *SQLiteConn) error {
driverConns = append(driverConns, conn)
otoolep marked this conversation as resolved.
Show resolved Hide resolved
return nil
},
})

// 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)
}

// Check the driver connections.
if len(driverConns) != 2 {
t.Fatalf("Expected 2 driver connections, but found %v.", len(driverConns))
}
srcDbDriverConn := driverConns[0]
if srcDbDriverConn == nil {
t.Fatal("The source database driver connection is nil.")
}
destDbDriverConn := driverConns[1]
if destDbDriverConn == nil {
t.Fatal("The destination database driver connection is nil.")
}

// 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 source database
b, err := srcDbDriverConn.Serialize("")
if err != nil {
t.Fatalf("Failed to serialize source database: %s", err)
}

// 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
if err := destDbDriverConn.Deserialize(b, ""); err != nil {
t.Fatal("Failed to deserialize to destination database", err)
}
otoolep marked this conversation as resolved.
Show resolved Hide resolved

// 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")
}
}