Virtual table API.

This commit is contained in:
Nuno Cruces
2023-11-22 13:11:23 +00:00
parent 97d4248176
commit 83c15f2ddc
8 changed files with 53 additions and 74 deletions

Binary file not shown.

View File

@@ -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 {

View File

@@ -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)

View File

@@ -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);
}

View File

@@ -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) {

8
tx.go
View File

@@ -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

62
vtab.go
View File

@@ -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)
}

View File

@@ -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 {