Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
35 changes: 35 additions & 0 deletions internal/sqlite3test/raw.go
Original file line number Diff line number Diff line change
@@ -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 <stdlib.h>
#include <sqlite3.h>

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
})
}
37 changes: 37 additions & 0 deletions raw_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
}
47 changes: 47 additions & 0 deletions sqlite3.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 <sqlite3.h>
// ...
// 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()
Expand Down