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