From 83c15f2ddc62dd569bace5693b7c3c0dd6cad17f Mon Sep 17 00:00:00 2001 From: Nuno Cruces Date: Wed, 22 Nov 2023 13:11:23 +0000 Subject: [PATCH] Virtual table API. --- embed/sqlite3.wasm | Bin 1470599 -> 1470633 bytes ext/array/array.go | 17 +++++-------- sqlite.go | 4 +-- sqlite3/vtab.c | 17 ++++++++----- tests/conn_test.go | 4 +-- tx.go | 8 +++--- vtab.go | 62 ++++++++++++++++++--------------------------- vtab_test.go | 15 ++++------- 8 files changed, 53 insertions(+), 74 deletions(-) diff --git a/embed/sqlite3.wasm b/embed/sqlite3.wasm index 446660c21ed8d048c8d3bcac605c31365800cb50..124a8be01c6ef3d992e9f2eefa406fe7b769e22e 100755 GIT binary patch delta 281 zcmZpF7`gIcQ9w6ogVm=_|2Vwyr76f7;AQs-fO;Y4$7)vV)BlGmh1tK%1Z(PJGI^CgA#9l** zjm1HM#qlnq850M$0+S#@1JmTs3@nt26N%1ME#U(}gmD}S&#WbY>AgobG delta 244 zcmZ4aFtYt&Q9w6ogVm=_|2Vwyr76f7;AQs-fO;Y4$7|Ua3MyBbL3q)p2H=M^IG2OXP#739J zL4n2bE~DcuMg=BEra~oFX)`7XpwK-=$2*J)ARY%dP==)tsE~R3@=*lMsByBbJ^YUsjTs6rWs_ Onpl#$y&+UgQyKtXpiY_q diff --git a/ext/array/array.go b/ext/array/array.go index 68625ff..d340aac 100644 --- a/ext/array/array.go +++ b/ext/array/array.go @@ -8,26 +8,21 @@ import ( "github.com/ncruces/go-sqlite3" ) -// Register registers the single-argument array table-valued SQL function. +// Register registers the array single-argument, table-valued SQL function. // The argument must be an [sqlite3.Pointer] to a Go slice or array // of ints, floats, bools, strings or blobs. // // https://sqlite.org/carray.html func Register(db *sqlite3.Conn) { - sqlite3.CreateModule(db, "array", array{}) + sqlite3.CreateModule[array](db, "array", nil, + func(db *sqlite3.Conn, arg ...string) (array, error) { + err := db.DeclareVtab(`CREATE TABLE x(value, array HIDDEN)`) + return array{}, err + }) } type array struct{} -func (array) Connect(c *sqlite3.Conn, arg ...string) (_ array, err error) { - err = c.DeclareVtab(`CREATE TABLE x(value, array HIDDEN)`) - return -} - -func (array) Disconnect() error { - return nil -} - func (array) BestIndex(idx *sqlite3.IndexInfo) error { for i, cst := range idx.Constraint { if cst.Column == 1 && cst.Op == sqlite3.INDEX_CONSTRAINT_EQ && cst.Usable { diff --git a/sqlite.go b/sqlite.go index 183f124..4f667ca 100644 --- a/sqlite.go +++ b/sqlite.go @@ -425,8 +425,8 @@ func exportCallbacks(env wazero.HostModuleBuilder) wazero.HostModuleBuilder { util.ExportFuncVI(env, "go_value", valueCallback) util.ExportFuncVIII(env, "go_inverse", inverseCallback) util.ExportFuncIIIIII(env, "go_compare", compareCallback) - util.ExportFuncIIIIII(env, "go_vtab_create", vtabReflectCallback("Create")) - util.ExportFuncIIIIII(env, "go_vtab_connect", vtabReflectCallback("Connect")) + util.ExportFuncIIIIII(env, "go_vtab_create", vtabModuleCallback(0)) + util.ExportFuncIIIIII(env, "go_vtab_connect", vtabModuleCallback(1)) util.ExportFuncII(env, "go_vtab_disconnect", vtabDisconnectCallback) util.ExportFuncII(env, "go_vtab_destroy", vtabDestroyCallback) util.ExportFuncIII(env, "go_vtab_best_index", vtabBestIndexCallback) diff --git a/sqlite3/vtab.c b/sqlite3/vtab.c index f04f998..56e9529 100644 --- a/sqlite3/vtab.c +++ b/sqlite3/vtab.c @@ -178,13 +178,10 @@ int sqlite3_create_module_go(sqlite3 *db, const char *zName, int flags, .xRowid = go_cur_rowid, }; if (flags & SQLITE_VTAB_CREATOR_GO) { - if (flags & SQLITE_VTAB_DESTROYER_GO) { - mod->base.xCreate = go_vtab_create_wrapper; - mod->base.xDestroy = go_vtab_destroy_wrapper; - } else { - mod->base.xCreate = mod->base.xConnect; - mod->base.xDestroy = mod->base.xDisconnect; - } + mod->base.xCreate = go_vtab_create_wrapper; + } + if (flags & SQLITE_VTAB_DESTROYER_GO) { + mod->base.xDestroy = go_vtab_destroy_wrapper; } if (flags & SQLITE_VTAB_UPDATER_GO) { mod->base.xUpdate = go_vtab_update; @@ -209,6 +206,12 @@ int sqlite3_create_module_go(sqlite3 *db, const char *zName, int flags, mod->base.xRelease = go_vtab_release; mod->base.xRollbackTo = go_vtab_rollback_to; } + if (mod->base.xCreate && !mod->base.xDestroy) { + mod->base.xDestroy = mod->base.xDisconnect; + } + if (mod->base.xDestroy && !mod->base.xCreate) { + mod->base.xCreate = mod->base.xConnect; + } return sqlite3_create_module_v2(db, zName, &mod->base, mod, go_mod_destroy); } diff --git a/tests/conn_test.go b/tests/conn_test.go index 8d5c143..cece594 100644 --- a/tests/conn_test.go +++ b/tests/conn_test.go @@ -75,8 +75,8 @@ func TestConn_Open_modeof(t *testing.T) { } func TestConn_Close(t *testing.T) { - var conn *sqlite3.Conn - conn.Close() + var db *sqlite3.Conn + db.Close() } func TestConn_Close_BUSY(t *testing.T) { diff --git a/tx.go b/tx.go index dc26655..f9996e4 100644 --- a/tx.go +++ b/tx.go @@ -56,8 +56,8 @@ func (c *Conn) BeginExclusive() (Tx, error) { // // This is meant to be deferred: // -// func doWork(conn *sqlite3.Conn) (err error) { -// tx := conn.Begin() +// func doWork(db *sqlite3.Conn) (err error) { +// tx := db.Begin() // defer tx.End(&err) // // // ... do work in the transaction @@ -156,8 +156,8 @@ func saveptName() (name string) { // // This is meant to be deferred: // -// func doWork(conn *sqlite3.Conn) (err error) { -// savept := conn.Savepoint() +// func doWork(db *sqlite3.Conn) (err error) { +// savept := db.Savepoint() // defer savept.Release(&err) // // // ... do work in the transaction diff --git a/vtab.go b/vtab.go index bbc11c5..788cbde 100644 --- a/vtab.go +++ b/vtab.go @@ -8,8 +8,11 @@ import ( "github.com/tetratelabs/wazero/api" ) -// CreateModule register a new virtual table module name. -func CreateModule[T VTab](conn *Conn, name string, module Module[T]) error { +// CreateModule registers a new virtual table module name. +// If create is nil, the virtual table is eponymous. +// +// https://sqlite.org/c3ref/create_module.html +func CreateModule[T VTab](db *Conn, name string, create, connect VTabConstructor[T]) error { var flags int const ( @@ -23,13 +26,11 @@ func CreateModule[T VTab](conn *Conn, name string, module Module[T]) error { VTAB_SAVEPOINTER = 0x80 ) - create, ok := reflect.TypeOf(module).MethodByName("Create") - connect, _ := reflect.TypeOf(module).MethodByName("Connect") - if ok && create.Type == connect.Type { + if create != nil { flags |= VTAB_CREATOR } - vtab := connect.Type.Out(0) + vtab := reflect.TypeOf(connect).Out(0) if implements[VTabDestroyer](vtab) { flags |= VTAB_DESTROYER } @@ -52,12 +53,12 @@ func CreateModule[T VTab](conn *Conn, name string, module Module[T]) error { flags |= VTAB_SAVEPOINTER } - defer conn.arena.reset() - namePtr := conn.arena.string(name) - modulePtr := util.AddHandle(conn.ctx, module) - r := conn.call(conn.api.createModule, uint64(conn.handle), + defer db.arena.reset() + namePtr := db.arena.string(name) + modulePtr := util.AddHandle(db.ctx, module[T]{create, connect}) + r := db.call(db.api.createModule, uint64(db.handle), uint64(namePtr), uint64(flags), uint64(modulePtr)) - return conn.error(r) + return db.error(r) } func implements[T any](typ reflect.Type) bool { @@ -72,37 +73,23 @@ func (c *Conn) DeclareVtab(sql string) error { return c.error(r) } -// A Module defines the implementation of a virtual table. -// A Module that doesn't implement [ModuleCreator] provides -// eponymous-only virtual tables or table-valued functions. -// -// https://sqlite.org/c3ref/module.html -type Module[T VTab] interface { - // https://sqlite.org/vtab.html#xconnect - Connect(c *Conn, arg ...string) (T, error) -} +// VTabConstructor is a virtual table constructor function. +type VTabConstructor[T VTab] func(db *Conn, arg ...string) (T, error) -// A ModuleCreator allows virtual tables to be created. -// A persistent virtual table must implement [VTabDestroyer]. -type ModuleCreator[T VTab] interface { - Module[T] - // https://sqlite.org/vtab.html#xcreate - Create(c *Conn, arg ...string) (T, error) -} +type module[T VTab] [2]VTabConstructor[T] // A VTab describes a particular instance of the virtual table. +// A VTab may optionally implement [io.Closer] to free resources. // // https://sqlite.org/c3ref/vtab.html type VTab interface { // https://sqlite.org/vtab.html#xbestindex BestIndex(*IndexInfo) error - // https://sqlite.org/vtab.html#xdisconnect - Disconnect() error // https://sqlite.org/vtab.html#xopen Open() (VTabCursor, error) } -// A VTabDestroyer allows a persistent virtual table to be destroyed. +// A VTabDestroyer allows a virtual table to drop persistent state. type VTabDestroyer interface { VTab // https://sqlite.org/vtab.html#sqlite3_module.xDestroy @@ -123,8 +110,7 @@ type VTabRenamer interface { Rename(new string) error } -// A VTabOverloader allows a virtual table to overload -// SQL functions. +// A VTabOverloader allows a virtual table to overload SQL functions. type VTabOverloader interface { VTab // https://sqlite.org/vtab.html#xfindfunction @@ -347,7 +333,7 @@ const ( INDEX_SCAN_UNIQUE IndexScanFlag = 1 ) -func vtabReflectCallback(name string) func(_ context.Context, _ api.Module, _, _, _, _, _ uint32) uint32 { +func vtabModuleCallback(i int) func(_ context.Context, _ api.Module, _, _, _, _, _ uint32) uint32 { return func(ctx context.Context, mod api.Module, pMod, argc, argv, ppVTab, pzErr uint32) uint32 { arg := make([]reflect.Value, 1+argc) arg[0] = reflect.ValueOf(ctx.Value(connKey{})) @@ -358,7 +344,7 @@ func vtabReflectCallback(name string) func(_ context.Context, _ api.Module, _, _ } module := vtabGetHandle(ctx, mod, pMod) - res := reflect.ValueOf(module).MethodByName(name).Call(arg) + res := reflect.ValueOf(module).Index(i).Call(arg) err, _ := res[1].Interface().(error) if err == nil { vtabPutHandle(ctx, mod, ppVTab, res[0].Interface()) @@ -369,16 +355,16 @@ func vtabReflectCallback(name string) func(_ context.Context, _ api.Module, _, _ } func vtabDisconnectCallback(ctx context.Context, mod api.Module, pVTab uint32) uint32 { - vtab := vtabGetHandle(ctx, mod, pVTab).(VTab) - err := vtab.Disconnect() - vtabDelHandle(ctx, mod, pVTab) + err := vtabDelHandle(ctx, mod, pVTab) return vtabError(ctx, mod, 0, _PTR_ERROR, err) } func vtabDestroyCallback(ctx context.Context, mod api.Module, pVTab uint32) uint32 { vtab := vtabGetHandle(ctx, mod, pVTab).(VTabDestroyer) err := vtab.Destroy() - vtabDelHandle(ctx, mod, pVTab) + if cerr := vtabDelHandle(ctx, mod, pVTab); err == nil { + err = cerr + } return vtabError(ctx, mod, 0, _PTR_ERROR, err) } diff --git a/vtab_test.go b/vtab_test.go index 9207503..51412e8 100644 --- a/vtab_test.go +++ b/vtab_test.go @@ -15,7 +15,11 @@ func ExampleCreateModule() { } defer db.Close() - err = sqlite3.CreateModule(db, "generate_series", seriesTable{}) + err = sqlite3.CreateModule[seriesTable](db, "generate_series", nil, + func(db *sqlite3.Conn, arg ...string) (seriesTable, error) { + err := db.DeclareVtab(`CREATE TABLE x(value, start HIDDEN, stop HIDDEN, step HIDDEN)`) + return seriesTable{}, err + }) if err != nil { log.Fatal(err) } @@ -40,15 +44,6 @@ func ExampleCreateModule() { type seriesTable struct{} -func (seriesTable) Connect(c *sqlite3.Conn, arg ...string) (_ seriesTable, err error) { - err = c.DeclareVtab(`CREATE TABLE x(value, start HIDDEN, stop HIDDEN, step HIDDEN)`) - return -} - -func (seriesTable) Disconnect() error { - return nil -} - func (seriesTable) BestIndex(idx *sqlite3.IndexInfo) error { for i, cst := range idx.Constraint { switch cst.Column {