From 823c1f64c0914590df83ed8e84d765c557549dd2 Mon Sep 17 00:00:00 2001 From: George Dunlap Date: Sat, 8 Feb 2020 21:29:32 +0000 Subject: [PATCH 1/2] SQLiteConn: Add 'Raw' method to give access to underlying sqlite3 context structure This is necessary, for instance, to allow users of the package to make their own CGo callbacks, and have the underlying sqlite3 library call those C functions directly. Note that the intention from the sqlite3 library seems to be that there should be a measure of binary compatibility between versions: even extensions compiled against an older different version of sqlite3 should be able to call sqlite3_create_function with no problems. So this should work even if users of the package have a slightly different version of the sqlite3.h header. Note that the code itself was written by @rittneje. I tested it and wrote the comments. Signed-off-by: George Dunlap v2: Just copy @rittneje's code exactly: - Grab the lock while the function is running - Replace the error code with our own if it's of type ErrNo --- doc.go | 7 +++++++ sqlite3.go | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/doc.go b/doc.go index a3bcebbc..f521872d 100644 --- a/doc.go +++ b/doc.go @@ -130,5 +130,12 @@ You can then use the custom driver by passing its name to sql.Open. } See the documentation of RegisterFunc for more details. + +# Cgo SQLite3 Extensions + +Go callbacks are convenient, but the runtime overhead of reflecting +incoming types can be significant. For performance-critical +functions, Cgo functions can also be defined. See SQLiteConn's Raw +method for an example. */ package sqlite3 diff --git a/sqlite3.go b/sqlite3.go index a967cab0..fb8c7d95 100644 --- a/sqlite3.go +++ b/sqlite3.go @@ -809,6 +809,53 @@ func (c *SQLiteConn) RegisterAggregator(name string, impl any, pure bool) error return nil } +// Raw provides access to the underlying raw C sqlite context pointer +// by casting the `raw` argument from `unsafe.Pointer` to +// `*C.sqlite3`. This is can be used, for example, to add your own C +// functions directly (which, due to fewer runtime reflection checks, +// typically run an order of magnitude faster). For example: +// +// /* +// #include +// ... +// void myFunc(sqlite3_context *context, int argc, sqlite3_value **argv) { +// // Function definition +// } +// +// int myfunc_setup(sqlite3 *db) { +// return sqlite3_create_function(db, "myFunc", ...); +// } +// */ +// import "C" +// +// d := &sqlite3.SQLiteDriver{ +// ConnectHook: func(c *SQLiteConn) error { +// return c.Raw(func(raw unsafe.Pointer) error { +// db := (*C.sqlite3)(raw) +// if rv := C.myfunc_setup(db); rv != C.SQLITE_OK { +// return sqlite3.ErrNo(rv) +// } +// return nil +// } +// }, +// } +// +// Note that as of 1.13, go doesn't correctly handle passing C +// function pointers back to C functions, so C.sqlite3_create_function +// can't be called from Go directly. See +// https://github.com/golang/go/issues/19835 for more details. +func (c *SQLiteConn) Raw(f func(unsafe.Pointer) error) error { + c.mu.Lock() + defer c.mu.Unlock() + if err := f(unsafe.Pointer(c.db)); err != nil { + if _, ok := err.(ErrNo); ok { + return c.lastError() + } + return err + } + return nil +} + // AutoCommit return which currently auto commit or not. func (c *SQLiteConn) AutoCommit() bool { c.mu.Lock() From 35a21f8c64bab8ede1243879342281a4b89b2459 Mon Sep 17 00:00:00 2001 From: George Dunlap Date: Wed, 22 Jul 2020 21:52:17 +0100 Subject: [PATCH 2/2] Add test for sqliteconn.Raw() Note that you can't call cgo from within a test file; so in order to test the functionality, create new package, .../internal/sqlite3test, to define the callback. Then in the main sqlite3 package, add a test file which includes this package and calls the callback. As with the previous commit, the idea and the basic code was written by @rittneje; I massaged it so that it compiled and passed with Go 1.9, and wrote this commit message and the comments. NB that Golang 1.9 doesn't have sql.OpenDB, so we have to register a new database driver. Signed-off-by: George Dunlap v4: - Refactor to get rid of sql.OpenDB call, which only appeared in Go 1.10 v3: - New --- internal/sqlite3test/raw.go | 35 +++++++++++++++++++++++++++++++++++ raw_test.go | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+) create mode 100644 internal/sqlite3test/raw.go create mode 100644 raw_test.go diff --git a/internal/sqlite3test/raw.go b/internal/sqlite3test/raw.go new file mode 100644 index 00000000..aed2e697 --- /dev/null +++ b/internal/sqlite3test/raw.go @@ -0,0 +1,35 @@ +package sqlite3test + +/* +#cgo CFLAGS: -DUSE_LIBSQLITE3 +#cgo linux LDFLAGS: -lsqlite3 +#cgo darwin LDFLAGS: -L/usr/local/opt/sqlite/lib -lsqlite3 +#cgo darwin CFLAGS: -I/usr/local/opt/sqlite/include +#cgo openbsd LDFLAGS: -lsqlite3 +#cgo solaris LDFLAGS: -lsqlite3 +#include +#include + +static void one(sqlite3_context* ctx, int n, sqlite3_value** vals) { + sqlite3_result_int(ctx, 1); +} + +static inline int _create_function(sqlite3* c) { + return sqlite3_create_function(c, "one", 0, SQLITE_UTF8|SQLITE_DETERMINISTIC, NULL, one, NULL, NULL); +} +*/ +import "C" +import ( + sqlite3 "github.com/mattn/go-sqlite3" + "unsafe" +) + +func RegisterFunction(conn *sqlite3.SQLiteConn) error { + return conn.Raw(func(raw unsafe.Pointer) error { + rawConn := (*C.sqlite3)(raw) + if ret := C._create_function(rawConn); ret != C.SQLITE_OK { + return sqlite3.ErrNo(ret) + } + return nil + }) +} diff --git a/raw_test.go b/raw_test.go new file mode 100644 index 00000000..9ac4cbf4 --- /dev/null +++ b/raw_test.go @@ -0,0 +1,37 @@ +package sqlite3_test + +import ( + "database/sql" + "testing" + + sqlite3 "github.com/mattn/go-sqlite3" + "github.com/mattn/go-sqlite3/internal/sqlite3test" +) + +func TestRaw(t *testing.T) { + sql.Register("sqlite3_rawtest", &sqlite3.SQLiteDriver{ + ConnectHook: func(c *sqlite3.SQLiteConn) error { + return sqlite3test.RegisterFunction(c) + }, + }) + + db, err := sql.Open("sqlite3_rawtest", "...") + if err != nil { + t.Fatal(err) + } + + defer db.Close() + + if err := db.Ping(); err != nil { + t.Fatal(err) + } + + var result int + if err := db.QueryRow(`SELECT one()`).Scan(&result); err != nil { + t.Fatal(err) + } + + if result != 1 { + t.Errorf("expected custom one() function to return 1, but got %d", result) + } +}