2024-05-27 17:46:15 +02:00
|
|
|
package sqlite3
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"reflect"
|
|
|
|
|
|
|
|
"github.com/ncruces/go-sqlite3/internal/util"
|
|
|
|
"github.com/tetratelabs/wazero/api"
|
|
|
|
)
|
|
|
|
|
|
|
|
// 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 (
|
2024-06-22 11:59:29 +02:00
|
|
|
VTAB_CREATOR = 0x001
|
|
|
|
VTAB_DESTROYER = 0x002
|
|
|
|
VTAB_UPDATER = 0x004
|
|
|
|
VTAB_RENAMER = 0x008
|
|
|
|
VTAB_OVERLOADER = 0x010
|
|
|
|
VTAB_CHECKER = 0x020
|
|
|
|
VTAB_TXN = 0x040
|
|
|
|
VTAB_SAVEPOINTER = 0x080
|
|
|
|
VTAB_SHADOWTABS = 0x100
|
2024-05-27 17:46:15 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
if create != nil {
|
|
|
|
flags |= VTAB_CREATOR
|
|
|
|
}
|
|
|
|
|
|
|
|
vtab := reflect.TypeOf(connect).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[VTabTxn](vtab) {
|
|
|
|
flags |= VTAB_TXN
|
|
|
|
}
|
|
|
|
if implements[VTabSavepointer](vtab) {
|
|
|
|
flags |= VTAB_SAVEPOINTER
|
|
|
|
}
|
2024-06-22 11:59:29 +02:00
|
|
|
if implements[VTabShadowTabler](vtab) {
|
|
|
|
flags |= VTAB_SHADOWTABS
|
|
|
|
}
|
2024-05-27 17:46:15 +02:00
|
|
|
|
|
|
|
defer db.arena.mark()()
|
|
|
|
namePtr := db.arena.string(name)
|
|
|
|
modulePtr := util.AddHandle(db.ctx, module[T]{create, connect})
|
|
|
|
r := db.call("sqlite3_create_module_go", uint64(db.handle),
|
|
|
|
uint64(namePtr), uint64(flags), uint64(modulePtr))
|
|
|
|
return db.error(r)
|
|
|
|
}
|
|
|
|
|
|
|
|
func implements[T any](typ reflect.Type) bool {
|
|
|
|
var ptr *T
|
|
|
|
return typ.Implements(reflect.TypeOf(ptr).Elem())
|
|
|
|
}
|
|
|
|
|
|
|
|
// DeclareVTab declares the schema of a virtual table.
|
|
|
|
//
|
|
|
|
// https://sqlite.org/c3ref/declare_vtab.html
|
|
|
|
func (c *Conn) DeclareVTab(sql string) error {
|
|
|
|
defer c.arena.mark()()
|
|
|
|
sqlPtr := c.arena.string(sql)
|
|
|
|
r := c.call("sqlite3_declare_vtab", uint64(c.handle), uint64(sqlPtr))
|
|
|
|
return c.error(r)
|
|
|
|
}
|
|
|
|
|
|
|
|
// VTabConflictMode is a virtual table conflict resolution mode.
|
|
|
|
//
|
|
|
|
// https://sqlite.org/c3ref/c_fail.html
|
|
|
|
type VTabConflictMode uint8
|
|
|
|
|
|
|
|
const (
|
|
|
|
VTAB_ROLLBACK VTabConflictMode = 1
|
|
|
|
VTAB_IGNORE VTabConflictMode = 2
|
|
|
|
VTAB_FAIL VTabConflictMode = 3
|
|
|
|
VTAB_ABORT VTabConflictMode = 4
|
|
|
|
VTAB_REPLACE VTabConflictMode = 5
|
|
|
|
)
|
|
|
|
|
|
|
|
// VTabOnConflict determines the virtual table conflict policy.
|
|
|
|
//
|
|
|
|
// https://sqlite.org/c3ref/vtab_on_conflict.html
|
|
|
|
func (c *Conn) VTabOnConflict() VTabConflictMode {
|
|
|
|
r := c.call("sqlite3_vtab_on_conflict", uint64(c.handle))
|
|
|
|
return VTabConflictMode(r)
|
|
|
|
}
|
|
|
|
|
|
|
|
// VTabConfigOption is a virtual table configuration option.
|
|
|
|
//
|
|
|
|
// https://sqlite.org/c3ref/c_vtab_constraint_support.html
|
|
|
|
type VTabConfigOption uint8
|
|
|
|
|
|
|
|
const (
|
|
|
|
VTAB_CONSTRAINT_SUPPORT VTabConfigOption = 1
|
|
|
|
VTAB_INNOCUOUS VTabConfigOption = 2
|
|
|
|
VTAB_DIRECTONLY VTabConfigOption = 3
|
|
|
|
VTAB_USES_ALL_SCHEMAS VTabConfigOption = 4
|
|
|
|
)
|
|
|
|
|
|
|
|
// VTabConfig configures various facets of the virtual table interface.
|
|
|
|
//
|
|
|
|
// https://sqlite.org/c3ref/vtab_config.html
|
|
|
|
func (c *Conn) VTabConfig(op VTabConfigOption, args ...any) error {
|
|
|
|
var i uint64
|
|
|
|
if op == VTAB_CONSTRAINT_SUPPORT && len(args) > 0 {
|
|
|
|
if b, ok := args[0].(bool); ok && b {
|
|
|
|
i = 1
|
|
|
|
}
|
|
|
|
}
|
|
|
|
r := c.call("sqlite3_vtab_config_go", uint64(c.handle), uint64(op), i)
|
|
|
|
return c.error(r)
|
|
|
|
}
|
|
|
|
|
|
|
|
// VTabConstructor is a virtual table constructor function.
|
|
|
|
type VTabConstructor[T VTab] func(db *Conn, module, schema, table string, arg ...string) (T, error)
|
|
|
|
|
|
|
|
type module[T VTab] [2]VTabConstructor[T]
|
|
|
|
|
|
|
|
type vtabConstructor int
|
|
|
|
|
|
|
|
const (
|
|
|
|
xCreate vtabConstructor = 0
|
|
|
|
xConnect vtabConstructor = 1
|
|
|
|
)
|
|
|
|
|
|
|
|
// 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#xopen
|
|
|
|
Open() (VTabCursor, error)
|
|
|
|
}
|
|
|
|
|
|
|
|
// A VTabDestroyer allows a virtual table to drop persistent state.
|
|
|
|
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) (ScalarFunction, IndexConstraintOp)
|
|
|
|
}
|
|
|
|
|
2024-06-22 11:59:29 +02:00
|
|
|
// A VTabShadowTabler allows a virtual table to protect the content
|
|
|
|
// of shadow tables from being corrupted by hostile SQL.
|
|
|
|
//
|
|
|
|
// Implementing this interface signals that a virtual table named
|
|
|
|
// "mumble" reserves all table names starting with "mumble_".
|
|
|
|
type VTabShadowTabler interface {
|
|
|
|
VTab
|
|
|
|
// https://sqlite.org/vtab.html#the_xshadowname_method
|
|
|
|
ShadowTables()
|
|
|
|
}
|
|
|
|
|
2024-05-27 17:46:15 +02:00
|
|
|
// A VTabChecker allows a virtual table to report errors
|
|
|
|
// to the PRAGMA integrity_check and PRAGMA quick_check commands.
|
|
|
|
//
|
|
|
|
// Integrity should return an error if it finds problems in the content of the virtual table,
|
|
|
|
// but should avoid returning a (wrapped) [Error], [ErrorCode] or [ExtendedErrorCode],
|
|
|
|
// as those indicate the Integrity method itself encountered problems
|
|
|
|
// while trying to evaluate the virtual table content.
|
|
|
|
type VTabChecker interface {
|
|
|
|
VTab
|
|
|
|
// https://sqlite.org/vtab.html#xintegrity
|
|
|
|
Integrity(schema, table string, flags int) error
|
|
|
|
}
|
|
|
|
|
|
|
|
// A VTabTxn allows a virtual table to implement
|
|
|
|
// transactions with two-phase commit.
|
|
|
|
//
|
|
|
|
// Anything that is required as part of a commit that may fail
|
|
|
|
// should be performed in the Sync() callback.
|
|
|
|
// Current versions of SQLite ignore any errors
|
|
|
|
// returned by Commit() and Rollback().
|
|
|
|
type VTabTxn 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 {
|
|
|
|
VTabTxn
|
|
|
|
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.
|
|
|
|
// A VTabCursor may optionally implement
|
|
|
|
// [io.Closer] to free resources.
|
|
|
|
//
|
|
|
|
// http://sqlite.org/c3ref/vtab_cursor.html
|
|
|
|
type VTabCursor interface {
|
|
|
|
// 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 []IndexConstraint
|
|
|
|
OrderBy []IndexOrderBy
|
|
|
|
ColumnsUsed int64
|
|
|
|
// Outputs
|
|
|
|
ConstraintUsage []IndexConstraintUsage
|
|
|
|
IdxNum int
|
|
|
|
IdxStr string
|
|
|
|
IdxFlags IndexScanFlag
|
|
|
|
OrderByConsumed bool
|
|
|
|
EstimatedCost float64
|
|
|
|
EstimatedRows int64
|
|
|
|
// Internal
|
|
|
|
c *Conn
|
|
|
|
handle uint32
|
|
|
|
}
|
|
|
|
|
|
|
|
// An IndexConstraint describes virtual table indexing constraint information.
|
|
|
|
//
|
|
|
|
// https://sqlite.org/c3ref/index_info.html
|
|
|
|
type IndexConstraint struct {
|
|
|
|
Column int
|
|
|
|
Op IndexConstraintOp
|
|
|
|
Usable bool
|
|
|
|
}
|
|
|
|
|
|
|
|
// An IndexOrderBy describes virtual table indexing order by information.
|
|
|
|
//
|
|
|
|
// https://sqlite.org/c3ref/index_info.html
|
|
|
|
type IndexOrderBy struct {
|
|
|
|
Column int
|
|
|
|
Desc bool
|
|
|
|
}
|
|
|
|
|
|
|
|
// An IndexConstraintUsage describes how virtual table indexing constraints will be used.
|
|
|
|
//
|
|
|
|
// https://sqlite.org/c3ref/index_info.html
|
|
|
|
type IndexConstraintUsage struct {
|
|
|
|
ArgvIndex int
|
|
|
|
Omit bool
|
|
|
|
}
|
|
|
|
|
|
|
|
// RHSValue returns the value of the right-hand operand of a constraint
|
|
|
|
// if the right-hand operand is known.
|
|
|
|
//
|
|
|
|
// https://sqlite.org/c3ref/vtab_rhs_value.html
|
|
|
|
func (idx *IndexInfo) RHSValue(column int) (Value, error) {
|
|
|
|
defer idx.c.arena.mark()()
|
|
|
|
valPtr := idx.c.arena.new(ptrlen)
|
|
|
|
r := idx.c.call("sqlite3_vtab_rhs_value", uint64(idx.handle),
|
|
|
|
uint64(column), uint64(valPtr))
|
|
|
|
if err := idx.c.error(r); err != nil {
|
|
|
|
return Value{}, err
|
|
|
|
}
|
|
|
|
return Value{
|
|
|
|
c: idx.c,
|
|
|
|
handle: util.ReadUint32(idx.c.mod, valPtr),
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Collation returns the name of the collation for a virtual table constraint.
|
|
|
|
//
|
|
|
|
// https://sqlite.org/c3ref/vtab_collation.html
|
|
|
|
func (idx *IndexInfo) Collation(column int) string {
|
|
|
|
r := idx.c.call("sqlite3_vtab_collation", uint64(idx.handle),
|
|
|
|
uint64(column))
|
|
|
|
return util.ReadString(idx.c.mod, uint32(r), _MAX_NAME)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Distinct determines if a virtual table query is DISTINCT.
|
|
|
|
//
|
|
|
|
// https://sqlite.org/c3ref/vtab_distinct.html
|
|
|
|
func (idx *IndexInfo) Distinct() int {
|
|
|
|
r := idx.c.call("sqlite3_vtab_distinct", uint64(idx.handle))
|
|
|
|
return int(r)
|
|
|
|
}
|
|
|
|
|
|
|
|
// In identifies and handles IN constraints.
|
|
|
|
//
|
|
|
|
// https://sqlite.org/c3ref/vtab_in.html
|
|
|
|
func (idx *IndexInfo) In(column, handle int) bool {
|
|
|
|
r := idx.c.call("sqlite3_vtab_in", uint64(idx.handle),
|
|
|
|
uint64(column), uint64(handle))
|
|
|
|
return r != 0
|
|
|
|
}
|
|
|
|
|
|
|
|
func (idx *IndexInfo) load() {
|
|
|
|
// https://sqlite.org/c3ref/index_info.html
|
|
|
|
mod := idx.c.mod
|
|
|
|
ptr := idx.handle
|
|
|
|
|
|
|
|
idx.Constraint = make([]IndexConstraint, util.ReadUint32(mod, ptr+0))
|
|
|
|
idx.ConstraintUsage = make([]IndexConstraintUsage, util.ReadUint32(mod, ptr+0))
|
|
|
|
idx.OrderBy = make([]IndexOrderBy, util.ReadUint32(mod, ptr+8))
|
|
|
|
|
|
|
|
constraintPtr := util.ReadUint32(mod, ptr+4)
|
|
|
|
for i := range idx.Constraint {
|
|
|
|
idx.Constraint[i] = IndexConstraint{
|
|
|
|
Column: int(int32(util.ReadUint32(mod, constraintPtr+0))),
|
|
|
|
Op: IndexConstraintOp(util.ReadUint8(mod, constraintPtr+4)),
|
|
|
|
Usable: util.ReadUint8(mod, constraintPtr+5) != 0,
|
|
|
|
}
|
|
|
|
constraintPtr += 12
|
|
|
|
}
|
|
|
|
|
|
|
|
orderByPtr := util.ReadUint32(mod, ptr+12)
|
|
|
|
for i := range idx.OrderBy {
|
|
|
|
idx.OrderBy[i] = IndexOrderBy{
|
|
|
|
Column: int(int32(util.ReadUint32(mod, orderByPtr+0))),
|
|
|
|
Desc: util.ReadUint8(mod, orderByPtr+4) != 0,
|
|
|
|
}
|
|
|
|
orderByPtr += 8
|
|
|
|
}
|
|
|
|
|
|
|
|
idx.EstimatedCost = util.ReadFloat64(mod, ptr+40)
|
|
|
|
idx.EstimatedRows = int64(util.ReadUint64(mod, ptr+48))
|
|
|
|
idx.ColumnsUsed = int64(util.ReadUint64(mod, ptr+64))
|
|
|
|
}
|
|
|
|
|
|
|
|
func (idx *IndexInfo) save() {
|
|
|
|
// https://sqlite.org/c3ref/index_info.html
|
|
|
|
mod := idx.c.mod
|
|
|
|
ptr := idx.handle
|
|
|
|
|
|
|
|
usagePtr := util.ReadUint32(mod, ptr+16)
|
|
|
|
for _, usage := range idx.ConstraintUsage {
|
|
|
|
util.WriteUint32(mod, usagePtr+0, uint32(usage.ArgvIndex))
|
|
|
|
if usage.Omit {
|
|
|
|
util.WriteUint8(mod, usagePtr+4, 1)
|
|
|
|
}
|
|
|
|
usagePtr += 8
|
|
|
|
}
|
|
|
|
|
|
|
|
util.WriteUint32(mod, ptr+20, uint32(idx.IdxNum))
|
|
|
|
if idx.IdxStr != "" {
|
|
|
|
util.WriteUint32(mod, ptr+24, idx.c.newString(idx.IdxStr))
|
|
|
|
util.WriteUint32(mod, ptr+28, 1) // needToFreeIdxStr
|
|
|
|
}
|
|
|
|
if idx.OrderByConsumed {
|
|
|
|
util.WriteUint32(mod, ptr+32, 1)
|
|
|
|
}
|
|
|
|
util.WriteFloat64(mod, ptr+40, idx.EstimatedCost)
|
|
|
|
util.WriteUint64(mod, ptr+48, uint64(idx.EstimatedRows))
|
|
|
|
util.WriteUint32(mod, ptr+56, uint32(idx.IdxFlags))
|
|
|
|
}
|
|
|
|
|
|
|
|
// IndexConstraintOp is a virtual table constraint operator code.
|
|
|
|
//
|
|
|
|
// https://sqlite.org/c3ref/c_index_constraint_eq.html
|
|
|
|
type IndexConstraintOp uint8
|
|
|
|
|
|
|
|
const (
|
|
|
|
INDEX_CONSTRAINT_EQ IndexConstraintOp = 2
|
|
|
|
INDEX_CONSTRAINT_GT IndexConstraintOp = 4
|
|
|
|
INDEX_CONSTRAINT_LE IndexConstraintOp = 8
|
|
|
|
INDEX_CONSTRAINT_LT IndexConstraintOp = 16
|
|
|
|
INDEX_CONSTRAINT_GE IndexConstraintOp = 32
|
|
|
|
INDEX_CONSTRAINT_MATCH IndexConstraintOp = 64
|
|
|
|
INDEX_CONSTRAINT_LIKE IndexConstraintOp = 65
|
|
|
|
INDEX_CONSTRAINT_GLOB IndexConstraintOp = 66
|
|
|
|
INDEX_CONSTRAINT_REGEXP IndexConstraintOp = 67
|
|
|
|
INDEX_CONSTRAINT_NE IndexConstraintOp = 68
|
|
|
|
INDEX_CONSTRAINT_ISNOT IndexConstraintOp = 69
|
|
|
|
INDEX_CONSTRAINT_ISNOTNULL IndexConstraintOp = 70
|
|
|
|
INDEX_CONSTRAINT_ISNULL IndexConstraintOp = 71
|
|
|
|
INDEX_CONSTRAINT_IS IndexConstraintOp = 72
|
|
|
|
INDEX_CONSTRAINT_LIMIT IndexConstraintOp = 73
|
|
|
|
INDEX_CONSTRAINT_OFFSET IndexConstraintOp = 74
|
|
|
|
INDEX_CONSTRAINT_FUNCTION IndexConstraintOp = 150
|
|
|
|
)
|
|
|
|
|
|
|
|
// IndexScanFlag is a virtual table scan flag.
|
|
|
|
//
|
|
|
|
// https://sqlite.org/c3ref/c_index_scan_unique.html
|
|
|
|
type IndexScanFlag uint32
|
|
|
|
|
|
|
|
const (
|
|
|
|
INDEX_SCAN_UNIQUE IndexScanFlag = 1
|
|
|
|
)
|
|
|
|
|
|
|
|
func vtabModuleCallback(i vtabConstructor) func(_ context.Context, _ api.Module, _, _, _, _, _ uint32) uint32 {
|
|
|
|
return func(ctx context.Context, mod api.Module, pMod, nArg, pArg, ppVTab, pzErr uint32) uint32 {
|
|
|
|
arg := make([]reflect.Value, 1+nArg)
|
|
|
|
arg[0] = reflect.ValueOf(ctx.Value(connKey{}))
|
|
|
|
|
|
|
|
for i := uint32(0); i < nArg; i++ {
|
|
|
|
ptr := util.ReadUint32(mod, pArg+i*ptrlen)
|
|
|
|
arg[i+1] = reflect.ValueOf(util.ReadString(mod, ptr, _MAX_SQL_LENGTH))
|
|
|
|
}
|
|
|
|
|
|
|
|
module := vtabGetHandle(ctx, mod, pMod)
|
|
|
|
res := reflect.ValueOf(module).Index(int(i)).Call(arg)
|
|
|
|
err, _ := res[1].Interface().(error)
|
|
|
|
if err == nil {
|
|
|
|
vtabPutHandle(ctx, mod, ppVTab, res[0].Interface())
|
|
|
|
}
|
|
|
|
|
|
|
|
return vtabError(ctx, mod, pzErr, _PTR_ERROR, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func vtabDisconnectCallback(ctx context.Context, mod api.Module, pVTab uint32) uint32 {
|
|
|
|
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()
|
|
|
|
if cerr := vtabDelHandle(ctx, mod, pVTab); err == nil {
|
|
|
|
err = cerr
|
|
|
|
}
|
|
|
|
return vtabError(ctx, mod, 0, _PTR_ERROR, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
func vtabBestIndexCallback(ctx context.Context, mod api.Module, pVTab, pIdxInfo uint32) uint32 {
|
|
|
|
var info IndexInfo
|
|
|
|
info.handle = pIdxInfo
|
|
|
|
info.c = ctx.Value(connKey{}).(*Conn)
|
|
|
|
info.load()
|
|
|
|
|
|
|
|
vtab := vtabGetHandle(ctx, mod, pVTab).(VTab)
|
|
|
|
err := vtab.BestIndex(&info)
|
|
|
|
|
|
|
|
info.save()
|
|
|
|
return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
func vtabUpdateCallback(ctx context.Context, mod api.Module, pVTab, nArg, pArg, pRowID uint32) uint32 {
|
|
|
|
vtab := vtabGetHandle(ctx, mod, pVTab).(VTabUpdater)
|
|
|
|
|
|
|
|
db := ctx.Value(connKey{}).(*Conn)
|
|
|
|
args := make([]Value, nArg)
|
|
|
|
callbackArgs(db, args, pArg)
|
|
|
|
rowID, err := vtab.Update(args...)
|
|
|
|
if err == nil {
|
|
|
|
util.WriteUint64(mod, pRowID, uint64(rowID))
|
|
|
|
}
|
|
|
|
|
|
|
|
return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
func vtabRenameCallback(ctx context.Context, mod api.Module, pVTab, zNew uint32) uint32 {
|
|
|
|
vtab := vtabGetHandle(ctx, mod, pVTab).(VTabRenamer)
|
|
|
|
err := vtab.Rename(util.ReadString(mod, zNew, _MAX_NAME))
|
|
|
|
return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
func vtabFindFuncCallback(ctx context.Context, mod api.Module, pVTab uint32, nArg int32, zName, pxFunc uint32) uint32 {
|
|
|
|
vtab := vtabGetHandle(ctx, mod, pVTab).(VTabOverloader)
|
|
|
|
f, op := vtab.FindFunction(int(nArg), util.ReadString(mod, zName, _MAX_NAME))
|
|
|
|
if op != 0 {
|
|
|
|
var wrapper uint32
|
|
|
|
wrapper = util.AddHandle(ctx, func(c Context, arg ...Value) {
|
|
|
|
defer util.DelHandle(ctx, wrapper)
|
|
|
|
f(c, arg...)
|
|
|
|
})
|
|
|
|
util.WriteUint32(mod, pxFunc, wrapper)
|
|
|
|
}
|
|
|
|
return uint32(op)
|
|
|
|
}
|
|
|
|
|
|
|
|
func vtabIntegrityCallback(ctx context.Context, mod api.Module, pVTab, zSchema, zTabName, mFlags, pzErr uint32) uint32 {
|
|
|
|
vtab := vtabGetHandle(ctx, mod, pVTab).(VTabChecker)
|
|
|
|
schema := util.ReadString(mod, zSchema, _MAX_NAME)
|
|
|
|
table := util.ReadString(mod, zTabName, _MAX_NAME)
|
|
|
|
err := vtab.Integrity(schema, table, int(mFlags))
|
|
|
|
// xIntegrity should return OK - even if it finds problems in the content of the virtual table.
|
|
|
|
// https://sqlite.org/vtab.html#xintegrity
|
|
|
|
vtabError(ctx, mod, pzErr, _PTR_ERROR, err)
|
|
|
|
_, code := errorCode(err, _OK)
|
|
|
|
return code
|
|
|
|
}
|
|
|
|
|
|
|
|
func vtabBeginCallback(ctx context.Context, mod api.Module, pVTab uint32) uint32 {
|
|
|
|
vtab := vtabGetHandle(ctx, mod, pVTab).(VTabTxn)
|
|
|
|
err := vtab.Begin()
|
|
|
|
return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
func vtabSyncCallback(ctx context.Context, mod api.Module, pVTab uint32) uint32 {
|
|
|
|
vtab := vtabGetHandle(ctx, mod, pVTab).(VTabTxn)
|
|
|
|
err := vtab.Sync()
|
|
|
|
return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
func vtabCommitCallback(ctx context.Context, mod api.Module, pVTab uint32) uint32 {
|
|
|
|
vtab := vtabGetHandle(ctx, mod, pVTab).(VTabTxn)
|
|
|
|
err := vtab.Commit()
|
|
|
|
return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
func vtabRollbackCallback(ctx context.Context, mod api.Module, pVTab uint32) uint32 {
|
|
|
|
vtab := vtabGetHandle(ctx, mod, pVTab).(VTabTxn)
|
|
|
|
err := vtab.Rollback()
|
|
|
|
return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
func vtabSavepointCallback(ctx context.Context, mod api.Module, pVTab uint32, id int32) uint32 {
|
|
|
|
vtab := vtabGetHandle(ctx, mod, pVTab).(VTabSavepointer)
|
|
|
|
err := vtab.Savepoint(int(id))
|
|
|
|
return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
func vtabReleaseCallback(ctx context.Context, mod api.Module, pVTab uint32, id int32) uint32 {
|
|
|
|
vtab := vtabGetHandle(ctx, mod, pVTab).(VTabSavepointer)
|
|
|
|
err := vtab.Release(int(id))
|
|
|
|
return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
func vtabRollbackToCallback(ctx context.Context, mod api.Module, pVTab uint32, id int32) uint32 {
|
|
|
|
vtab := vtabGetHandle(ctx, mod, pVTab).(VTabSavepointer)
|
|
|
|
err := vtab.RollbackTo(int(id))
|
|
|
|
return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
func cursorOpenCallback(ctx context.Context, mod api.Module, pVTab, ppCur uint32) uint32 {
|
|
|
|
vtab := vtabGetHandle(ctx, mod, pVTab).(VTab)
|
|
|
|
|
|
|
|
cursor, err := vtab.Open()
|
|
|
|
if err == nil {
|
|
|
|
vtabPutHandle(ctx, mod, ppCur, cursor)
|
|
|
|
}
|
|
|
|
|
|
|
|
return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
func cursorCloseCallback(ctx context.Context, mod api.Module, pCur uint32) uint32 {
|
|
|
|
err := vtabDelHandle(ctx, mod, pCur)
|
|
|
|
return vtabError(ctx, mod, 0, _VTAB_ERROR, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
func cursorFilterCallback(ctx context.Context, mod api.Module, pCur uint32, idxNum int32, idxStr, nArg, pArg uint32) uint32 {
|
|
|
|
cursor := vtabGetHandle(ctx, mod, pCur).(VTabCursor)
|
|
|
|
db := ctx.Value(connKey{}).(*Conn)
|
|
|
|
args := make([]Value, nArg)
|
|
|
|
callbackArgs(db, args, pArg)
|
|
|
|
var idxName string
|
|
|
|
if idxStr != 0 {
|
|
|
|
idxName = util.ReadString(mod, idxStr, _MAX_LENGTH)
|
|
|
|
}
|
|
|
|
err := cursor.Filter(int(idxNum), idxName, args...)
|
|
|
|
return vtabError(ctx, mod, pCur, _CURSOR_ERROR, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
func cursorEOFCallback(ctx context.Context, mod api.Module, pCur uint32) uint32 {
|
|
|
|
cursor := vtabGetHandle(ctx, mod, pCur).(VTabCursor)
|
|
|
|
if cursor.EOF() {
|
|
|
|
return 1
|
|
|
|
}
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
|
|
|
|
func cursorNextCallback(ctx context.Context, mod api.Module, pCur uint32) uint32 {
|
|
|
|
cursor := vtabGetHandle(ctx, mod, pCur).(VTabCursor)
|
|
|
|
err := cursor.Next()
|
|
|
|
return vtabError(ctx, mod, pCur, _CURSOR_ERROR, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
func cursorColumnCallback(ctx context.Context, mod api.Module, pCur, pCtx uint32, n int32) uint32 {
|
|
|
|
cursor := vtabGetHandle(ctx, mod, pCur).(VTabCursor)
|
|
|
|
db := ctx.Value(connKey{}).(*Conn)
|
|
|
|
err := cursor.Column(&Context{db, pCtx}, int(n))
|
|
|
|
return vtabError(ctx, mod, pCur, _CURSOR_ERROR, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
func cursorRowIDCallback(ctx context.Context, mod api.Module, pCur, pRowID uint32) uint32 {
|
|
|
|
cursor := vtabGetHandle(ctx, mod, pCur).(VTabCursor)
|
|
|
|
|
|
|
|
rowID, err := cursor.RowID()
|
|
|
|
if err == nil {
|
|
|
|
util.WriteUint64(mod, pRowID, uint64(rowID))
|
|
|
|
}
|
|
|
|
|
|
|
|
return vtabError(ctx, mod, pCur, _CURSOR_ERROR, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
const (
|
|
|
|
_PTR_ERROR = iota
|
|
|
|
_VTAB_ERROR
|
|
|
|
_CURSOR_ERROR
|
|
|
|
)
|
|
|
|
|
|
|
|
func vtabError(ctx context.Context, mod api.Module, ptr, kind uint32, err error) uint32 {
|
|
|
|
const zErrMsgOffset = 8
|
|
|
|
msg, code := errorCode(err, ERROR)
|
|
|
|
if msg != "" && ptr != 0 {
|
|
|
|
switch kind {
|
|
|
|
case _VTAB_ERROR:
|
|
|
|
ptr = ptr + zErrMsgOffset // zErrMsg
|
|
|
|
case _CURSOR_ERROR:
|
|
|
|
ptr = util.ReadUint32(mod, ptr) + zErrMsgOffset // pVTab->zErrMsg
|
|
|
|
}
|
|
|
|
db := ctx.Value(connKey{}).(*Conn)
|
|
|
|
if ptr := util.ReadUint32(mod, ptr); ptr != 0 {
|
|
|
|
db.free(ptr)
|
|
|
|
}
|
|
|
|
util.WriteUint32(mod, ptr, db.newString(msg))
|
|
|
|
}
|
|
|
|
return code
|
|
|
|
}
|
|
|
|
|
|
|
|
func vtabGetHandle(ctx context.Context, mod api.Module, ptr uint32) any {
|
|
|
|
const handleOffset = 4
|
|
|
|
handle := util.ReadUint32(mod, ptr-handleOffset)
|
|
|
|
return util.GetHandle(ctx, handle)
|
|
|
|
}
|
|
|
|
|
|
|
|
func vtabDelHandle(ctx context.Context, mod api.Module, ptr uint32) error {
|
|
|
|
const handleOffset = 4
|
|
|
|
handle := util.ReadUint32(mod, ptr-handleOffset)
|
|
|
|
return util.DelHandle(ctx, handle)
|
|
|
|
}
|
|
|
|
|
|
|
|
func vtabPutHandle(ctx context.Context, mod api.Module, pptr uint32, val any) {
|
|
|
|
const handleOffset = 4
|
|
|
|
handle := util.AddHandle(ctx, val)
|
|
|
|
ptr := util.ReadUint32(mod, pptr)
|
|
|
|
util.WriteUint32(mod, ptr-handleOffset, handle)
|
|
|
|
}
|