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 32 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
84 changes: 84 additions & 0 deletions sqlite3_opt_serialize.go
@@ -0,0 +1,84 @@
// +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. If deserialization fails, error will contain the return code
otoolep marked this conversation as resolved.
Show resolved Hide resolved
// 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))

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 != 0 {
otoolep marked this conversation as resolved.
Show resolved Hide resolved
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")
}
120 changes: 120 additions & 0 deletions sqlite3_opt_serialize_test.go
@@ -0,0 +1,120 @@
// +build !libsqlite3 sqlite_serialize

package sqlite3

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

func TestSerialize(t *testing.T) {
otoolep marked this conversation as resolved.
Show resolved Hide resolved
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)
b, err := sqlite3conn.Serialize("")
if err != nil {
t.Fatalf("failed to serialize database: %s", err)
}
if err := os.WriteFile(tempFilename, b, 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 := os.ReadFile(tempFilename)
otoolep marked this conversation as resolved.
Show resolved Hide resolved
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)
}
otoolep marked this conversation as resolved.
Show resolved Hide resolved
defer rows.Close()
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)
}
}