mirror of
https://github.com/ncruces/go-sqlite3.git
synced 2026-01-11 21:49:13 +00:00
Virtual table API.
This commit is contained in:
Binary file not shown.
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
8
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
|
||||
|
||||
62
vtab.go
62
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)
|
||||
}
|
||||
|
||||
|
||||
15
vtab_test.go
15
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 {
|
||||
|
||||
Reference in New Issue
Block a user