Files
sqlite3/vtab.go
2023-11-17 02:23:10 +00:00

323 lines
8.5 KiB
Go

package sqlite3
import (
"context"
"reflect"
"github.com/ncruces/go-sqlite3/internal/util"
"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 {
var flags int
const (
VTAB_CREATOR = 0x01
VTAB_DESTROYER = 0x02
VTAB_UPDATER = 0x04
VTAB_RENAMER = 0x08
VTAB_OVERLOADER = 0x10
VTAB_CHECKER = 0x20
VTAB_TX = 0x40
VTAB_SAVEPOINTER = 0x80
)
create, ok := reflect.TypeOf(module).MethodByName("Create")
connect, _ := reflect.TypeOf(module).MethodByName("Connect")
if ok && create.Type == connect.Type {
flags |= VTAB_CREATOR
}
vtab := connect.Type.Out(0)
if implements[VTabDestroyer](vtab) {
flags |= VTAB_DESTROYER
}
if implements[VTabUpdater](vtab) {
flags |= VTAB_UPDATER
}
if implements[VTabRenamer](vtab) {
flags |= VTAB_RENAMER
}
if implements[VTabOverloader](vtab) {
flags |= VTAB_OVERLOADER
}
if implements[VTabChecker](vtab) {
flags |= VTAB_CHECKER
}
if implements[VTabTx](vtab) {
flags |= VTAB_TX
}
if implements[VTabSavepointer](vtab) {
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),
uint64(namePtr), uint64(flags), uint64(modulePtr))
return conn.error(r)
}
func implements[T any](typ reflect.Type) bool {
var ptr *T
return typ.Implements(reflect.TypeOf(ptr).Elem())
}
func (c *Conn) DeclareVtab(sql string) error {
defer c.arena.reset()
sqlPtr := c.arena.string(sql)
r := c.call(c.api.declareVTab, uint64(c.handle), uint64(sqlPtr))
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)
}
// 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)
}
// A VTab describes a particular instance of the virtual table.
//
// 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.
type VTabDestroyer interface {
VTab
// https://sqlite.org/vtab.html#sqlite3_module.xDestroy
Destroy() error
}
// A VTabUpdater allows a virtual table to be updated.
type VTabUpdater interface {
VTab
// https://sqlite.org/vtab.html#xupdate
Update(arg ...Value) (rowid int64, err error)
}
// A VTabRenamer allows a virtual table to be renamed.
type VTabRenamer interface {
VTab
// https://sqlite.org/vtab.html#xrename
Rename(new string) error
}
// A VTabOverloader allows a virtual table to overload
// SQL functions.
type VTabOverloader interface {
VTab
// https://sqlite.org/vtab.html#xfindfunction
FindFunction(arg int, name string) (func(ctx Context, arg ...Value), IndexConstraintOp)
}
// A VTabChecker allows a virtual table to report errors
// to the PRAGMA integrity_check PRAGMA quick_check commands.
type VTabChecker interface {
VTab
// https://sqlite.org/vtab.html#xintegrity
Integrity(schema, table string, flags int) error
}
// A VTabTx allows a virtual table to implement
// transactions with two-phase commit.
type VTabTx interface {
VTab
// https://sqlite.org/vtab.html#xBegin
Begin() error
// https://sqlite.org/vtab.html#xsync
Sync() error
// https://sqlite.org/vtab.html#xcommit
Commit() error
// https://sqlite.org/vtab.html#xrollback
Rollback() error
}
// A VTabSavepointer allows a virtual table to implement
// nested transactions.
//
// https://sqlite.org/vtab.html#xsavepoint
type VTabSavepointer interface {
VTabTx
Savepoint(id int) error
Release(id int) error
RollbackTo(id int) error
}
// A VTabCursor describes cursors that point
// into the virtual table and are used
// to loop through the virtual table.
//
// http://sqlite.org/c3ref/vtab_cursor.html
type VTabCursor interface {
// https://sqlite.org/vtab.html#xclose
Close() error
// https://sqlite.org/vtab.html#xfilter
Filter(idxNum int, idxStr string, arg ...Value) error
// https://sqlite.org/vtab.html#xnext
Next() error
// https://sqlite.org/vtab.html#xeof
EOF() bool
// https://sqlite.org/vtab.html#xcolumn
Column(ctx *Context, n int) error
// https://sqlite.org/vtab.html#xrowid
RowID() (int64, error)
}
// An IndexInfo describes virtual table indexing information.
//
// https://sqlite.org/c3ref/index_info.html
type IndexInfo struct {
/* Inputs */
Constraint []struct {
Column int
Op IndexConstraintOp
Usable bool
}
OrderBy []struct {
Column int
Desc bool
}
/* Outputs */
ConstraintUsage []struct {
ArgvIndex int
Omit bool
}
IdxNum int
IdxStr string
IdxFlags IndexScanFlag
OrderByConsumed bool
EstimatedCost float64
EstimatedRows int64
ColumnsUsed int64
}
// IndexConstraintOp is a virtual table constraint operator code.
//
// https://sqlite.org/c3ref/c_index_constraint_eq.html
type IndexConstraintOp uint8
const (
Eq IndexConstraintOp = 2
Gt IndexConstraintOp = 4
Le IndexConstraintOp = 8
Lt IndexConstraintOp = 16
Ge IndexConstraintOp = 32
Match IndexConstraintOp = 64
Like IndexConstraintOp = 65 /* 3.10.0 and later */
Glob IndexConstraintOp = 66 /* 3.10.0 and later */
Regexp IndexConstraintOp = 67 /* 3.10.0 and later */
Ne IndexConstraintOp = 68 /* 3.21.0 and later */
IsNot IndexConstraintOp = 69 /* 3.21.0 and later */
IsNotNull IndexConstraintOp = 70 /* 3.21.0 and later */
IsNull IndexConstraintOp = 71 /* 3.21.0 and later */
Is IndexConstraintOp = 72 /* 3.21.0 and later */
Limit IndexConstraintOp = 73 /* 3.38.0 and later */
Offset IndexConstraintOp = 74 /* 3.38.0 and later */
Function IndexConstraintOp = 150 /* 3.25.0 and later */
)
// IndexScanFlag is a virtual table scan flag.
//
// https://www.sqlite.org/c3ref/c_index_scan_unique.html
type IndexScanFlag uint8
const (
Unique IndexScanFlag = 1
)
func vtabConnectCallback(ctx context.Context, mod api.Module, pMod, argc, argv, ppVTab, pzErr uint32) uint32 {
const handleOffset = 4
handle := util.ReadUint32(mod, pMod-handleOffset)
module := util.GetHandle(ctx, handle)
db := ctx.Value(connKey{}).(*Conn)
arg := make([]reflect.Value, 1+argc)
arg[0] = reflect.ValueOf(db)
for i := uint32(0); i < argc; i++ {
ptr := util.ReadUint32(mod, argv+i*ptrlen)
arg[i+1] = reflect.ValueOf(util.ReadString(mod, ptr, _MAX_STRING))
}
res := reflect.ValueOf(module).MethodByName("Connect").Call(arg)
err, _ := res[1].Interface().(error)
if err == nil {
handle := util.AddHandle(ctx, res[0].Interface())
ptr := util.ReadUint32(mod, ppVTab)
util.WriteUint32(mod, ptr-handleOffset, handle)
return _OK
}
// TODO: error message
return errorCode(err, ERROR)
}
func vtabBestIndexCallback(ctx context.Context, mod api.Module, pVTab, pIdxInfo uint32) uint32 {
const handleOffset = 4
handle := util.ReadUint32(mod, pVTab-handleOffset)
vtab := util.GetHandle(ctx, handle).(VTab)
_ = vtab
return 1
}
func vtabDisconnectCallback(ctx context.Context, mod api.Module, pVTab uint32) uint32 {
return 1
}
func vtabIntegrityCallback(ctx context.Context, mod api.Module, pVTab, zSchema, zTabName, mFlags, pzErr uint32) uint32 {
return 1
}
func vtabCallbackI(ctx context.Context, mod api.Module, _ uint32) uint32 {
return 1
}
func vtabCallbackII(ctx context.Context, mod api.Module, _, _ uint32) uint32 {
return 1
}
func vtabCallbackIIII(ctx context.Context, mod api.Module, _, _, _, _ uint32) uint32 {
return 1
}
func cursorOpenCallback(ctx context.Context, mod api.Module, pVTab, ppCur uint32) uint32 {
return 1
}
func cursorFilterCallback(ctx context.Context, mod api.Module, pCur, idxNum, idxStr, argc, argv uint32) uint32 {
return 1
}
func cursorColumnCallback(ctx context.Context, mod api.Module, pCur, pCtx, n uint32) uint32 {
return 1
}
func cursorRowidCallback(ctx context.Context, mod api.Module, pCur, pRowid uint32) uint32 {
return 1
}
func cursorCallbackI(ctx context.Context, mod api.Module, _ uint32) uint32 {
return 1
}