873 lines
24 KiB
Go
873 lines
24 KiB
Go
// Copyright 2025 The Sqlite Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package sqlite // import "modernc.org/sqlite"
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
"sync"
|
|
"unsafe"
|
|
|
|
"modernc.org/libc"
|
|
sqlite3 "modernc.org/sqlite/lib"
|
|
"modernc.org/sqlite/vtab"
|
|
)
|
|
|
|
func init() {
|
|
vtab.SetRegisterFunc(registerModule)
|
|
}
|
|
|
|
var (
|
|
// vtabModules tracks Go virtual table modules registered via the vtab
|
|
// package. Each module is identified by an integer ID used as pAux when
|
|
// calling sqlite3_create_module_v2, so that trampolines can recover the
|
|
// Go Module implementation.
|
|
vtabModules = struct {
|
|
mu sync.RWMutex
|
|
m map[uintptr]*goModule
|
|
ids idGen
|
|
// name2id keeps stable IDs per module name to avoid unbounded growth
|
|
// across connections.
|
|
name2id map[string]uintptr
|
|
}{
|
|
m: make(map[uintptr]*goModule),
|
|
name2id: make(map[string]uintptr),
|
|
}
|
|
|
|
// nativeModules holds sqlite3_module instances for registered modules. We
|
|
// keep them in Go memory so their addresses remain stable for the C layer.
|
|
nativeModules = struct {
|
|
mu sync.RWMutex
|
|
m map[string]*sqlite3.Sqlite3_module
|
|
}{
|
|
m: make(map[string]*sqlite3.Sqlite3_module),
|
|
}
|
|
|
|
// vtabTables maps sqlite3_vtab* (pVtab) to the corresponding Go Table.
|
|
vtabTables = struct {
|
|
mu sync.RWMutex
|
|
m map[uintptr]*goTable
|
|
}{
|
|
m: make(map[uintptr]*goTable),
|
|
}
|
|
|
|
// vtabCursors maps sqlite3_vtab_cursor* (pCursor) to the corresponding Go
|
|
// Cursor.
|
|
vtabCursors = struct {
|
|
mu sync.RWMutex
|
|
m map[uintptr]*goCursor
|
|
}{
|
|
m: make(map[uintptr]*goCursor),
|
|
}
|
|
)
|
|
|
|
// goModule wraps a vtab.Module implementation with its name.
|
|
type goModule struct {
|
|
name string
|
|
impl vtab.Module
|
|
}
|
|
|
|
// goTable wraps a vtab.Table implementation and remembers its module.
|
|
type goTable struct {
|
|
mod *goModule
|
|
impl vtab.Table
|
|
}
|
|
|
|
// goCursor wraps a vtab.Cursor implementation and remembers its table.
|
|
type goCursor struct {
|
|
table *goTable
|
|
impl vtab.Cursor
|
|
}
|
|
|
|
// Use aliases of the underlying lib types so field layouts remain correct.
|
|
type cIndexConstraint = sqlite3.Tsqlite3_index_constraint
|
|
type cIndexOrderBy = sqlite3.Tsqlite3_index_orderby
|
|
type cConstraintUsage = sqlite3.Tsqlite3_index_constraint_usage
|
|
|
|
// registerModule is installed as the hook for vtab.RegisterModule.
|
|
func registerModule(name string, m vtab.Module) error {
|
|
if _, exists := d.modules[name]; exists {
|
|
return fmt.Errorf("sqlite: module %q already registered", name)
|
|
}
|
|
d.modules[name] = m
|
|
return nil
|
|
}
|
|
|
|
// registerModules installs all globally registered vtab modules on this
|
|
// connection by calling sqlite3_create_module_v2 for each one.
|
|
func (c *conn) registerModules() error {
|
|
for name, mod := range d.modules {
|
|
if err := c.registerSingleModule(name, mod); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *conn) registerSingleModule(name string, m vtab.Module) error {
|
|
// Allocate or reuse a stable ID for this module name and remember the Go implementation.
|
|
vtabModules.mu.Lock()
|
|
modID, ok := vtabModules.name2id[name]
|
|
if !ok {
|
|
modID = vtabModules.ids.next()
|
|
vtabModules.name2id[name] = modID
|
|
}
|
|
vtabModules.m[modID] = &goModule{name: name, impl: m}
|
|
vtabModules.mu.Unlock()
|
|
|
|
nativeModules.mu.Lock()
|
|
defer nativeModules.mu.Unlock()
|
|
if _, exists := nativeModules.m[name]; exists {
|
|
// Module struct already created; nothing more to do for this connection.
|
|
return nil
|
|
}
|
|
|
|
// Build a sqlite3_module descriptor with trampolines.
|
|
mod := &sqlite3.Sqlite3_module{}
|
|
mod.FiVersion = 1
|
|
mod.FxCreate = cFuncPointer(vtabCreateTrampoline)
|
|
mod.FxConnect = cFuncPointer(vtabConnectTrampoline)
|
|
mod.FxBestIndex = cFuncPointer(vtabBestIndexTrampoline)
|
|
mod.FxDisconnect = cFuncPointer(vtabDisconnectTrampoline)
|
|
mod.FxDestroy = cFuncPointer(vtabDestroyTrampoline)
|
|
mod.FxOpen = cFuncPointer(vtabOpenTrampoline)
|
|
mod.FxClose = cFuncPointer(vtabCloseTrampoline)
|
|
mod.FxFilter = cFuncPointer(vtabFilterTrampoline)
|
|
mod.FxNext = cFuncPointer(vtabNextTrampoline)
|
|
mod.FxEof = cFuncPointer(vtabEofTrampoline)
|
|
mod.FxColumn = cFuncPointer(vtabColumnTrampoline)
|
|
mod.FxRowid = cFuncPointer(vtabRowidTrampoline)
|
|
mod.FxFindFunction = cFuncPointer(vtabFindFunctionTrampoline)
|
|
mod.FxRename = cFuncPointer(vtabRenameTrampoline)
|
|
mod.FxUpdate = cFuncPointer(vtabUpdateTrampoline)
|
|
mod.FxBegin = cFuncPointer(vtabBeginTrampoline)
|
|
mod.FxSync = cFuncPointer(vtabSyncTrampoline)
|
|
mod.FxCommit = cFuncPointer(vtabCommitTrampoline)
|
|
mod.FxRollback = cFuncPointer(vtabRollbackTrampoline)
|
|
mod.FxSavepoint = cFuncPointer(vtabSavepointTrampoline)
|
|
mod.FxRelease = cFuncPointer(vtabReleaseTrampoline)
|
|
mod.FxRollbackTo = cFuncPointer(vtabRollbackToTrampoline)
|
|
|
|
nativeModules.m[name] = mod
|
|
|
|
// Prepare C string for module name.
|
|
zName, err := libc.CString(name)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer libc.Xfree(c.tls, zName)
|
|
|
|
// Register the module with this connection.
|
|
if rc := sqlite3.Xsqlite3_create_module_v2(c.tls, c.db, zName, uintptr(unsafe.Pointer(mod)), modID, 0); rc != sqlite3.SQLITE_OK {
|
|
return fmt.Errorf("create_module %q: %w", name, c.errstr(rc))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// vtabCreateTrampoline is the xCreate callback. It invokes the corresponding
|
|
// Go vtab.Module.Create method, declares a default schema based on argv, and
|
|
// allocates a sqlite3_vtab.
|
|
func vtabCreateTrampoline(tls *libc.TLS, db uintptr, pAux uintptr, argc int32, argv uintptr, ppVtab uintptr, pzErr uintptr) int32 {
|
|
gm := lookupGoModule(pAux)
|
|
if gm == nil {
|
|
setVtabError(tls, pzErr, fmt.Sprintf("vtab: unknown module id %d", pAux))
|
|
return sqlite3.SQLITE_ERROR
|
|
}
|
|
args := extractVtabArgs(tls, argc, argv)
|
|
ctx := vtab.NewContext(func(schema string) error {
|
|
zSchema, err := libc.CString(schema)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer libc.Xfree(tls, zSchema)
|
|
if rc := sqlite3.Xsqlite3_declare_vtab(tls, db, zSchema); rc != sqlite3.SQLITE_OK {
|
|
return fmt.Errorf("declare_vtab failed: rc=%d", rc)
|
|
}
|
|
return nil
|
|
})
|
|
tbl, err := gm.impl.Create(ctx, args)
|
|
if err != nil {
|
|
setVtabError(tls, pzErr, err.Error())
|
|
return sqlite3.SQLITE_ERROR
|
|
}
|
|
sz := unsafe.Sizeof(sqlite3.Sqlite3_vtab{})
|
|
p := sqlite3.Xsqlite3_malloc(tls, int32(sz))
|
|
if p == 0 {
|
|
setVtabError(tls, pzErr, "vtab: out of memory")
|
|
return sqlite3.SQLITE_NOMEM
|
|
}
|
|
mem := (*libc.RawMem)(unsafe.Pointer(p))[:sz:sz]
|
|
for i := range mem {
|
|
mem[i] = 0
|
|
}
|
|
*(*uintptr)(unsafe.Pointer(ppVtab)) = p
|
|
|
|
gt := &goTable{mod: gm, impl: tbl}
|
|
vtabTables.mu.Lock()
|
|
vtabTables.m[p] = gt
|
|
vtabTables.mu.Unlock()
|
|
return sqlite3.SQLITE_OK
|
|
}
|
|
|
|
// vtabConnectTrampoline is the xConnect callback. It mirrors
|
|
// vtabCreateTrampoline but calls Module.Connect.
|
|
func vtabConnectTrampoline(tls *libc.TLS, db uintptr, pAux uintptr, argc int32, argv uintptr, ppVtab uintptr, pzErr uintptr) int32 {
|
|
gm := lookupGoModule(pAux)
|
|
if gm == nil {
|
|
setVtabError(tls, pzErr, fmt.Sprintf("vtab: unknown module id %d", pAux))
|
|
return sqlite3.SQLITE_ERROR
|
|
}
|
|
args := extractVtabArgs(tls, argc, argv)
|
|
ctx := vtab.NewContext(func(schema string) error {
|
|
zSchema, err := libc.CString(schema)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer libc.Xfree(tls, zSchema)
|
|
if rc := sqlite3.Xsqlite3_declare_vtab(tls, db, zSchema); rc != sqlite3.SQLITE_OK {
|
|
return fmt.Errorf("declare_vtab failed: rc=%d", rc)
|
|
}
|
|
return nil
|
|
})
|
|
tbl, err := gm.impl.Connect(ctx, args)
|
|
if err != nil {
|
|
setVtabError(tls, pzErr, err.Error())
|
|
return sqlite3.SQLITE_ERROR
|
|
}
|
|
sz := unsafe.Sizeof(sqlite3.Sqlite3_vtab{})
|
|
p := sqlite3.Xsqlite3_malloc(tls, int32(sz))
|
|
if p == 0 {
|
|
setVtabError(tls, pzErr, "vtab: out of memory")
|
|
return sqlite3.SQLITE_NOMEM
|
|
}
|
|
mem := (*libc.RawMem)(unsafe.Pointer(p))[:sz:sz]
|
|
for i := range mem {
|
|
mem[i] = 0
|
|
}
|
|
*(*uintptr)(unsafe.Pointer(ppVtab)) = p
|
|
|
|
gt := &goTable{mod: gm, impl: tbl}
|
|
vtabTables.mu.Lock()
|
|
vtabTables.m[p] = gt
|
|
vtabTables.mu.Unlock()
|
|
return sqlite3.SQLITE_OK
|
|
}
|
|
|
|
// vtabBestIndexTrampoline maps sqlite3_index_info to vtab.IndexInfo and
|
|
// delegates to Table.BestIndex. It also mirrors constraint and ORDER BY
|
|
// information into the Go structure.
|
|
func vtabBestIndexTrampoline(tls *libc.TLS, pVtab uintptr, pInfo uintptr) int32 {
|
|
vtabTables.mu.RLock()
|
|
gt := vtabTables.m[pVtab]
|
|
vtabTables.mu.RUnlock()
|
|
if gt == nil {
|
|
return sqlite3.SQLITE_ERROR
|
|
}
|
|
|
|
idx := (*sqlite3.Sqlite3_index_info)(unsafe.Pointer(pInfo))
|
|
info := &vtab.IndexInfo{}
|
|
|
|
// Populate Constraints from sqlite3_index_info.aConstraint.
|
|
if idx.FnConstraint > 0 && idx.FaConstraint != 0 {
|
|
n := int(idx.FnConstraint)
|
|
cs := make([]vtab.Constraint, 0, n)
|
|
base := idx.FaConstraint
|
|
sz := unsafe.Sizeof(cIndexConstraint{})
|
|
for i := 0; i < n; i++ {
|
|
c := (*cIndexConstraint)(unsafe.Pointer(base + uintptr(i)*sz))
|
|
op := vtab.OpUnknown
|
|
switch int32(c.Fop) {
|
|
case sqlite3.SQLITE_INDEX_CONSTRAINT_EQ:
|
|
op = vtab.OpEQ
|
|
case sqlite3.SQLITE_INDEX_CONSTRAINT_GT:
|
|
op = vtab.OpGT
|
|
case sqlite3.SQLITE_INDEX_CONSTRAINT_LE:
|
|
op = vtab.OpLE
|
|
case sqlite3.SQLITE_INDEX_CONSTRAINT_LT:
|
|
op = vtab.OpLT
|
|
case sqlite3.SQLITE_INDEX_CONSTRAINT_GE:
|
|
op = vtab.OpGE
|
|
case sqlite3.SQLITE_INDEX_CONSTRAINT_MATCH:
|
|
op = vtab.OpMATCH
|
|
case sqlite3.SQLITE_INDEX_CONSTRAINT_NE:
|
|
op = vtab.OpNE
|
|
case sqlite3.SQLITE_INDEX_CONSTRAINT_IS:
|
|
op = vtab.OpIS
|
|
case sqlite3.SQLITE_INDEX_CONSTRAINT_ISNOT:
|
|
op = vtab.OpISNOT
|
|
case sqlite3.SQLITE_INDEX_CONSTRAINT_ISNULL:
|
|
op = vtab.OpISNULL
|
|
case sqlite3.SQLITE_INDEX_CONSTRAINT_ISNOTNULL:
|
|
op = vtab.OpISNOTNULL
|
|
case sqlite3.SQLITE_INDEX_CONSTRAINT_LIKE:
|
|
op = vtab.OpLIKE
|
|
case sqlite3.SQLITE_INDEX_CONSTRAINT_GLOB:
|
|
op = vtab.OpGLOB
|
|
case sqlite3.SQLITE_INDEX_CONSTRAINT_REGEXP:
|
|
op = vtab.OpREGEXP
|
|
case sqlite3.SQLITE_INDEX_CONSTRAINT_FUNCTION:
|
|
op = vtab.OpFUNCTION
|
|
case sqlite3.SQLITE_INDEX_CONSTRAINT_LIMIT:
|
|
op = vtab.OpLIMIT
|
|
case sqlite3.SQLITE_INDEX_CONSTRAINT_OFFSET:
|
|
op = vtab.OpOFFSET
|
|
}
|
|
cs = append(cs, vtab.Constraint{
|
|
Column: int(c.FiColumn),
|
|
Op: op,
|
|
Usable: c.Fusable != 0,
|
|
ArgIndex: -1, // 0-based; -1 means ignore
|
|
Omit: false,
|
|
})
|
|
}
|
|
info.Constraints = cs
|
|
}
|
|
|
|
// Populate OrderBy from sqlite3_index_info.aOrderBy.
|
|
if idx.FnOrderBy > 0 && idx.FaOrderBy != 0 {
|
|
n := int(idx.FnOrderBy)
|
|
obs := make([]vtab.OrderBy, 0, n)
|
|
base := idx.FaOrderBy
|
|
sz := unsafe.Sizeof(cIndexOrderBy{})
|
|
for i := 0; i < n; i++ {
|
|
ob := (*cIndexOrderBy)(unsafe.Pointer(base + uintptr(i)*sz))
|
|
obs = append(obs, vtab.OrderBy{
|
|
Column: int(ob.FiColumn),
|
|
Desc: ob.Fdesc != 0,
|
|
})
|
|
}
|
|
info.OrderBy = obs
|
|
}
|
|
|
|
// Populate ColUsed and idxFlags for module visibility.
|
|
if idx.FcolUsed != 0 {
|
|
info.ColUsed = uint64(idx.FcolUsed)
|
|
}
|
|
if idx.FidxFlags != 0 {
|
|
info.IdxFlags = int(idx.FidxFlags)
|
|
}
|
|
|
|
if err := gt.impl.BestIndex(info); err != nil {
|
|
// Report error via zErrMsg on pVtab.
|
|
setVtabZErrMsg(tls, pVtab, err.Error())
|
|
return sqlite3.SQLITE_ERROR
|
|
}
|
|
|
|
// Propagate any ArgIndex assignments back into aConstraintUsage so that
|
|
// SQLite will populate xFilter's argv[] accordingly.
|
|
if idx.FnConstraint > 0 && idx.FaConstraintUsage != 0 && len(info.Constraints) > 0 {
|
|
n := int(idx.FnConstraint)
|
|
base := idx.FaConstraintUsage
|
|
sz := unsafe.Sizeof(cConstraintUsage{})
|
|
for i := 0; i < n && i < len(info.Constraints); i++ {
|
|
cu := (*cConstraintUsage)(unsafe.Pointer(base + uintptr(i)*sz))
|
|
argIndex := info.Constraints[i].ArgIndex
|
|
if argIndex >= 0 {
|
|
// Go ArgIndex is 0-based; SQLite wants 1-based.
|
|
cu.FargvIndex = int32(argIndex + 1)
|
|
}
|
|
if info.Constraints[i].Omit {
|
|
cu.Fomit = 1
|
|
}
|
|
}
|
|
}
|
|
// Guard against int32 overflow: SQLite expects idxNum as int32.
|
|
if info.IdxNum < math.MinInt32 || info.IdxNum > math.MaxInt32 {
|
|
setVtabZErrMsg(tls, pVtab, fmt.Sprintf("vtab: IdxNum %d out of int32 range", info.IdxNum))
|
|
return sqlite3.SQLITE_ERROR
|
|
}
|
|
idx.FidxNum = int32(info.IdxNum)
|
|
if info.IdxStr != "" {
|
|
// Allocate using SQLite allocator because needToFreeIdxStr=1 instructs
|
|
// SQLite to free the string with sqlite3_free.
|
|
z := sqlite3AllocCString(tls, info.IdxStr)
|
|
if z != 0 {
|
|
idx.FidxStr = z
|
|
idx.FneedToFreeIdxStr = 1
|
|
}
|
|
}
|
|
if info.OrderByConsumed {
|
|
idx.ForderByConsumed = 1
|
|
}
|
|
if info.IdxFlags != 0 {
|
|
idx.FidxFlags = int32(info.IdxFlags)
|
|
}
|
|
if info.EstimatedCost != 0 {
|
|
idx.FestimatedCost = info.EstimatedCost
|
|
}
|
|
if info.EstimatedRows != 0 {
|
|
idx.FestimatedRows = sqlite3.Sqlite3_int64(info.EstimatedRows)
|
|
}
|
|
return sqlite3.SQLITE_OK
|
|
}
|
|
|
|
// vtabDisconnectTrampoline is xDisconnect. It frees the sqlite3_vtab and
|
|
// calls Table.Disconnect.
|
|
func vtabDisconnectTrampoline(tls *libc.TLS, pVtab uintptr) int32 {
|
|
vtabTables.mu.RLock()
|
|
gt := vtabTables.m[pVtab]
|
|
vtabTables.mu.RUnlock()
|
|
if gt != nil {
|
|
_ = gt.impl.Disconnect()
|
|
vtabTables.mu.Lock()
|
|
delete(vtabTables.m, pVtab)
|
|
vtabTables.mu.Unlock()
|
|
}
|
|
sqlite3.Xsqlite3_free(tls, pVtab)
|
|
return sqlite3.SQLITE_OK
|
|
}
|
|
|
|
// vtabDestroyTrampoline is xDestroy. Currently identical to Disconnect.
|
|
func vtabDestroyTrampoline(tls *libc.TLS, pVtab uintptr) int32 {
|
|
vtabTables.mu.RLock()
|
|
gt := vtabTables.m[pVtab]
|
|
vtabTables.mu.RUnlock()
|
|
if gt != nil {
|
|
_ = gt.impl.Destroy()
|
|
vtabTables.mu.Lock()
|
|
delete(vtabTables.m, pVtab)
|
|
vtabTables.mu.Unlock()
|
|
}
|
|
sqlite3.Xsqlite3_free(tls, pVtab)
|
|
return sqlite3.SQLITE_OK
|
|
}
|
|
|
|
// vtabOpenTrampoline is xOpen. It allocates an empty sqlite3_vtab_cursor and
|
|
// creates a Go Cursor via Table.Open.
|
|
func vtabOpenTrampoline(tls *libc.TLS, pVtab uintptr, ppCursor uintptr) int32 {
|
|
vtabTables.mu.RLock()
|
|
gt := vtabTables.m[pVtab]
|
|
vtabTables.mu.RUnlock()
|
|
if gt == nil {
|
|
return sqlite3.SQLITE_ERROR
|
|
}
|
|
|
|
curImpl, err := gt.impl.Open()
|
|
if err != nil {
|
|
return sqlite3.SQLITE_ERROR
|
|
}
|
|
|
|
sz := unsafe.Sizeof(sqlite3.Sqlite3_vtab_cursor{})
|
|
p := sqlite3.Xsqlite3_malloc(tls, int32(sz))
|
|
if p == 0 {
|
|
return sqlite3.SQLITE_NOMEM
|
|
}
|
|
mem := (*libc.RawMem)(unsafe.Pointer(p))[:sz:sz]
|
|
for i := range mem {
|
|
mem[i] = 0
|
|
}
|
|
*(*uintptr)(unsafe.Pointer(ppCursor)) = p
|
|
|
|
// Link cursor back to its vtab so error reporting can set zErrMsg.
|
|
cur := (*sqlite3.Sqlite3_vtab_cursor)(unsafe.Pointer(p))
|
|
cur.FpVtab = pVtab
|
|
|
|
gc := &goCursor{table: gt, impl: curImpl}
|
|
vtabCursors.mu.Lock()
|
|
vtabCursors.m[p] = gc
|
|
vtabCursors.mu.Unlock()
|
|
return sqlite3.SQLITE_OK
|
|
}
|
|
|
|
// vtabCloseTrampoline is xClose. It frees the sqlite3_vtab_cursor and calls
|
|
// Cursor.Close.
|
|
func vtabCloseTrampoline(tls *libc.TLS, pCursor uintptr) int32 {
|
|
vtabCursors.mu.RLock()
|
|
gc := vtabCursors.m[pCursor]
|
|
vtabCursors.mu.RUnlock()
|
|
if gc != nil {
|
|
_ = gc.impl.Close()
|
|
vtabCursors.mu.Lock()
|
|
delete(vtabCursors.m, pCursor)
|
|
vtabCursors.mu.Unlock()
|
|
}
|
|
sqlite3.Xsqlite3_free(tls, pCursor)
|
|
return sqlite3.SQLITE_OK
|
|
}
|
|
|
|
// vtabFilterTrampoline is xFilter.
|
|
func vtabFilterTrampoline(tls *libc.TLS, pCursor uintptr, idxNum int32, idxStr uintptr, argc int32, argv uintptr) int32 {
|
|
vtabCursors.mu.RLock()
|
|
gc := vtabCursors.m[pCursor]
|
|
vtabCursors.mu.RUnlock()
|
|
if gc == nil {
|
|
return sqlite3.SQLITE_ERROR
|
|
}
|
|
|
|
var idxStrGo string
|
|
if idxStr != 0 {
|
|
idxStrGo = libc.GoString(idxStr)
|
|
}
|
|
vals := functionArgs(tls, argc, argv)
|
|
if err := gc.impl.Filter(int(idxNum), idxStrGo, vals); err != nil {
|
|
// Set zErrMsg on the associated vtab for better diagnostics.
|
|
if pCursor != 0 {
|
|
cur := (*sqlite3.Sqlite3_vtab_cursor)(unsafe.Pointer(pCursor))
|
|
if cur.FpVtab != 0 {
|
|
setVtabZErrMsg(tls, cur.FpVtab, err.Error())
|
|
}
|
|
}
|
|
return sqlite3.SQLITE_ERROR
|
|
}
|
|
return sqlite3.SQLITE_OK
|
|
}
|
|
|
|
// vtabNextTrampoline is xNext.
|
|
func vtabNextTrampoline(tls *libc.TLS, pCursor uintptr) int32 {
|
|
_ = tls
|
|
vtabCursors.mu.RLock()
|
|
gc := vtabCursors.m[pCursor]
|
|
vtabCursors.mu.RUnlock()
|
|
if gc == nil {
|
|
return sqlite3.SQLITE_ERROR
|
|
}
|
|
if err := gc.impl.Next(); err != nil {
|
|
return sqlite3.SQLITE_ERROR
|
|
}
|
|
return sqlite3.SQLITE_OK
|
|
}
|
|
|
|
// vtabEofTrampoline is xEof.
|
|
func vtabEofTrampoline(tls *libc.TLS, pCursor uintptr) int32 {
|
|
_ = tls
|
|
vtabCursors.mu.RLock()
|
|
gc := vtabCursors.m[pCursor]
|
|
vtabCursors.mu.RUnlock()
|
|
if gc == nil || gc.impl.Eof() {
|
|
return 1
|
|
}
|
|
return 0
|
|
}
|
|
|
|
// vtabColumnTrampoline is xColumn.
|
|
func vtabColumnTrampoline(tls *libc.TLS, pCursor uintptr, ctx uintptr, iCol int32) int32 {
|
|
vtabCursors.mu.RLock()
|
|
gc := vtabCursors.m[pCursor]
|
|
vtabCursors.mu.RUnlock()
|
|
if gc == nil {
|
|
sqlite3.Xsqlite3_result_null(tls, ctx)
|
|
return sqlite3.SQLITE_OK
|
|
}
|
|
val, err := gc.impl.Column(int(iCol))
|
|
if err != nil {
|
|
// Report via result error on the context.
|
|
z, cerr := libc.CString(err.Error())
|
|
if cerr == nil {
|
|
defer libc.Xfree(tls, z)
|
|
sqlite3.Xsqlite3_result_error(tls, ctx, z, -1)
|
|
sqlite3.Xsqlite3_result_error_code(tls, ctx, sqlite3.SQLITE_ERROR)
|
|
} else {
|
|
sqlite3.Xsqlite3_result_error_code(tls, ctx, sqlite3.SQLITE_ERROR)
|
|
}
|
|
return sqlite3.SQLITE_ERROR
|
|
}
|
|
if err := functionReturnValue(tls, ctx, val); err != nil {
|
|
// Include a descriptive error message for easier debugging
|
|
// (e.g., unsupported type conversions).
|
|
if err != nil {
|
|
z, cerr := libc.CString(err.Error())
|
|
if cerr == nil {
|
|
defer libc.Xfree(tls, z)
|
|
sqlite3.Xsqlite3_result_error(tls, ctx, z, -1)
|
|
}
|
|
}
|
|
sqlite3.Xsqlite3_result_error_code(tls, ctx, sqlite3.SQLITE_ERROR)
|
|
return sqlite3.SQLITE_ERROR
|
|
}
|
|
return sqlite3.SQLITE_OK
|
|
}
|
|
|
|
// vtabRowidTrampoline is xRowid.
|
|
func vtabRowidTrampoline(tls *libc.TLS, pCursor uintptr, pRowid uintptr) int32 {
|
|
_ = tls
|
|
vtabCursors.mu.RLock()
|
|
gc := vtabCursors.m[pCursor]
|
|
vtabCursors.mu.RUnlock()
|
|
if gc == nil {
|
|
*(*int64)(unsafe.Pointer(pRowid)) = 0
|
|
return sqlite3.SQLITE_OK
|
|
}
|
|
rowid, err := gc.impl.Rowid()
|
|
if err != nil {
|
|
*(*int64)(unsafe.Pointer(pRowid)) = 0
|
|
return sqlite3.SQLITE_ERROR
|
|
}
|
|
*(*int64)(unsafe.Pointer(pRowid)) = rowid
|
|
return sqlite3.SQLITE_OK
|
|
}
|
|
|
|
func lookupGoModule(id uintptr) *goModule {
|
|
vtabModules.mu.RLock()
|
|
defer vtabModules.mu.RUnlock()
|
|
return vtabModules.m[id]
|
|
}
|
|
|
|
func extractVtabArgs(tls *libc.TLS, argc int32, argv uintptr) []string {
|
|
args := make([]string, argc)
|
|
for i := int32(0); i < argc; i++ {
|
|
cstr := *(*uintptr)(unsafe.Pointer(argv + uintptr(i)*unsafe.Sizeof(uintptr(0))))
|
|
args[i] = libc.GoString(cstr)
|
|
}
|
|
return args
|
|
}
|
|
|
|
func setVtabError(tls *libc.TLS, pzErr uintptr, msg string) {
|
|
if pzErr == 0 {
|
|
return
|
|
}
|
|
z := sqlite3AllocCString(tls, msg)
|
|
if z == 0 {
|
|
return
|
|
}
|
|
*(*uintptr)(unsafe.Pointer(pzErr)) = z
|
|
}
|
|
|
|
// setVtabZErrMsg sets pVtab->zErrMsg to a newly allocated C string containing
|
|
// msg. SQLite will free this pointer.
|
|
func setVtabZErrMsg(tls *libc.TLS, pVtab uintptr, msg string) {
|
|
if pVtab == 0 {
|
|
return
|
|
}
|
|
vt := (*sqlite3.Sqlite3_vtab)(unsafe.Pointer(pVtab))
|
|
if vt.FzErrMsg != 0 {
|
|
sqlite3.Xsqlite3_free(tls, vt.FzErrMsg)
|
|
vt.FzErrMsg = 0
|
|
}
|
|
z := sqlite3AllocCString(tls, msg)
|
|
if z == 0 {
|
|
return
|
|
}
|
|
vt.FzErrMsg = z
|
|
}
|
|
|
|
// Optional vtab callbacks
|
|
|
|
// vtabFindFunctionTrampoline is xFindFunction. We currently do not expose a
|
|
// Go surface for per-table SQL functions; report not found (return 0).
|
|
func vtabFindFunctionTrampoline(tls *libc.TLS, pVtab uintptr, nArg int32, zName uintptr, pxFunc uintptr, ppArg uintptr) int32 {
|
|
_ = tls
|
|
_ = pVtab
|
|
_ = nArg
|
|
_ = zName
|
|
if pxFunc != 0 {
|
|
*(*uintptr)(unsafe.Pointer(pxFunc)) = 0
|
|
}
|
|
if ppArg != 0 {
|
|
*(*uintptr)(unsafe.Pointer(ppArg)) = 0
|
|
}
|
|
return 0 // not found
|
|
}
|
|
|
|
// vtabRenameTrampoline is xRename. Calls Table.Rename if implemented.
|
|
func vtabRenameTrampoline(tls *libc.TLS, pVtab uintptr, zNew uintptr) int32 {
|
|
vtabTables.mu.RLock()
|
|
gt := vtabTables.m[pVtab]
|
|
vtabTables.mu.RUnlock()
|
|
if gt == nil {
|
|
return sqlite3.SQLITE_ERROR
|
|
}
|
|
name := libc.GoString(zNew)
|
|
if r, ok := gt.impl.(interface{ Rename(string) error }); ok {
|
|
if err := r.Rename(name); err != nil {
|
|
setVtabZErrMsg(tls, pVtab, err.Error())
|
|
return sqlite3.SQLITE_ERROR
|
|
}
|
|
}
|
|
return sqlite3.SQLITE_OK
|
|
}
|
|
|
|
// vtabUpdateTrampoline is xUpdate. Not supported by default; report read-only.
|
|
func vtabUpdateTrampoline(tls *libc.TLS, pVtab uintptr, argc int32, argv uintptr, pRowid uintptr) int32 {
|
|
vtabTables.mu.RLock()
|
|
gt := vtabTables.m[pVtab]
|
|
vtabTables.mu.RUnlock()
|
|
if gt == nil {
|
|
return sqlite3.SQLITE_ERROR
|
|
}
|
|
upd, ok := gt.impl.(interface {
|
|
Insert(cols []vtab.Value, rowid *int64) error
|
|
Update(oldRowid int64, cols []vtab.Value, newRowid *int64) error
|
|
Delete(oldRowid int64) error
|
|
})
|
|
if !ok {
|
|
return sqlite3.SQLITE_READONLY
|
|
}
|
|
|
|
// DELETE: argc == 1; argv[0]=oldRowid
|
|
if argc == 1 {
|
|
valPtr := *(*uintptr)(unsafe.Pointer(argv))
|
|
oldRowid := int64(0)
|
|
if sqlite3.Xsqlite3_value_type(tls, valPtr) != sqlite3.SQLITE_NULL {
|
|
oldRowid = int64(sqlite3.Xsqlite3_value_int64(tls, valPtr))
|
|
}
|
|
if err := upd.Delete(oldRowid); err != nil {
|
|
setVtabZErrMsg(tls, pVtab, err.Error())
|
|
return sqlite3.SQLITE_ERROR
|
|
}
|
|
return sqlite3.SQLITE_OK
|
|
}
|
|
|
|
// INSERT or UPDATE: argc == N+2. argv[0]=oldRowid (NULL for insert),
|
|
// argv[1..N]=column values, argv[N+1]=newRowid (or desired rowid for insert, may be NULL).
|
|
if argc < 3 {
|
|
return sqlite3.SQLITE_MISUSE
|
|
}
|
|
nCols := argc - 2
|
|
// Extract column values
|
|
colsPtr := argv + uintptr(1)*sqliteValPtrSize
|
|
cols := functionArgs(tls, nCols, colsPtr)
|
|
|
|
// Determine old/new rowid
|
|
oldPtr := *(*uintptr)(unsafe.Pointer(argv + uintptr(0)*sqliteValPtrSize))
|
|
newPtr := *(*uintptr)(unsafe.Pointer(argv + uintptr(argc-1)*sqliteValPtrSize))
|
|
|
|
oldIsNull := sqlite3.Xsqlite3_value_type(tls, oldPtr) == sqlite3.SQLITE_NULL
|
|
newIsNull := sqlite3.Xsqlite3_value_type(tls, newPtr) == sqlite3.SQLITE_NULL
|
|
|
|
if oldIsNull {
|
|
// INSERT
|
|
var rid int64
|
|
if !newIsNull {
|
|
rid = int64(sqlite3.Xsqlite3_value_int64(tls, newPtr))
|
|
}
|
|
if err := upd.Insert(cols, &rid); err != nil {
|
|
setVtabZErrMsg(tls, pVtab, err.Error())
|
|
return sqlite3.SQLITE_ERROR
|
|
}
|
|
if pRowid != 0 {
|
|
*(*int64)(unsafe.Pointer(pRowid)) = rid
|
|
}
|
|
return sqlite3.SQLITE_OK
|
|
}
|
|
|
|
// UPDATE
|
|
oldRowid := int64(sqlite3.Xsqlite3_value_int64(tls, oldPtr))
|
|
var newRid int64
|
|
if !newIsNull {
|
|
newRid = int64(sqlite3.Xsqlite3_value_int64(tls, newPtr))
|
|
}
|
|
if err := upd.Update(oldRowid, cols, &newRid); err != nil {
|
|
setVtabZErrMsg(tls, pVtab, err.Error())
|
|
return sqlite3.SQLITE_ERROR
|
|
}
|
|
if pRowid != 0 && newRid != 0 {
|
|
*(*int64)(unsafe.Pointer(pRowid)) = newRid
|
|
}
|
|
return sqlite3.SQLITE_OK
|
|
}
|
|
|
|
// Transactional callbacks
|
|
func vtabBeginTrampoline(tls *libc.TLS, pVtab uintptr) int32 {
|
|
vtabTables.mu.RLock()
|
|
gt := vtabTables.m[pVtab]
|
|
vtabTables.mu.RUnlock()
|
|
if gt == nil {
|
|
return sqlite3.SQLITE_ERROR
|
|
}
|
|
if tr, ok := gt.impl.(interface{ Begin() error }); ok {
|
|
if err := tr.Begin(); err != nil {
|
|
setVtabZErrMsg(tls, pVtab, err.Error())
|
|
return sqlite3.SQLITE_ERROR
|
|
}
|
|
}
|
|
return sqlite3.SQLITE_OK
|
|
}
|
|
|
|
func vtabSyncTrampoline(tls *libc.TLS, pVtab uintptr) int32 {
|
|
vtabTables.mu.RLock()
|
|
gt := vtabTables.m[pVtab]
|
|
vtabTables.mu.RUnlock()
|
|
if gt == nil {
|
|
return sqlite3.SQLITE_ERROR
|
|
}
|
|
if tr, ok := gt.impl.(interface{ Sync() error }); ok {
|
|
if err := tr.Sync(); err != nil {
|
|
setVtabZErrMsg(tls, pVtab, err.Error())
|
|
return sqlite3.SQLITE_ERROR
|
|
}
|
|
}
|
|
return sqlite3.SQLITE_OK
|
|
}
|
|
|
|
func vtabCommitTrampoline(tls *libc.TLS, pVtab uintptr) int32 {
|
|
vtabTables.mu.RLock()
|
|
gt := vtabTables.m[pVtab]
|
|
vtabTables.mu.RUnlock()
|
|
if gt == nil {
|
|
return sqlite3.SQLITE_ERROR
|
|
}
|
|
if tr, ok := gt.impl.(interface{ Commit() error }); ok {
|
|
if err := tr.Commit(); err != nil {
|
|
setVtabZErrMsg(tls, pVtab, err.Error())
|
|
return sqlite3.SQLITE_ERROR
|
|
}
|
|
}
|
|
return sqlite3.SQLITE_OK
|
|
}
|
|
|
|
func vtabRollbackTrampoline(tls *libc.TLS, pVtab uintptr) int32 {
|
|
vtabTables.mu.RLock()
|
|
gt := vtabTables.m[pVtab]
|
|
vtabTables.mu.RUnlock()
|
|
if gt == nil {
|
|
return sqlite3.SQLITE_ERROR
|
|
}
|
|
if tr, ok := gt.impl.(interface{ Rollback() error }); ok {
|
|
if err := tr.Rollback(); err != nil {
|
|
setVtabZErrMsg(tls, pVtab, err.Error())
|
|
return sqlite3.SQLITE_ERROR
|
|
}
|
|
}
|
|
return sqlite3.SQLITE_OK
|
|
}
|
|
|
|
func vtabSavepointTrampoline(tls *libc.TLS, pVtab uintptr, i int32) int32 {
|
|
vtabTables.mu.RLock()
|
|
gt := vtabTables.m[pVtab]
|
|
vtabTables.mu.RUnlock()
|
|
if gt == nil {
|
|
return sqlite3.SQLITE_ERROR
|
|
}
|
|
if tr, ok := gt.impl.(interface{ Savepoint(int) error }); ok {
|
|
if err := tr.Savepoint(int(i)); err != nil {
|
|
setVtabZErrMsg(tls, pVtab, err.Error())
|
|
return sqlite3.SQLITE_ERROR
|
|
}
|
|
}
|
|
return sqlite3.SQLITE_OK
|
|
}
|
|
|
|
func vtabReleaseTrampoline(tls *libc.TLS, pVtab uintptr, i int32) int32 {
|
|
vtabTables.mu.RLock()
|
|
gt := vtabTables.m[pVtab]
|
|
vtabTables.mu.RUnlock()
|
|
if gt == nil {
|
|
return sqlite3.SQLITE_ERROR
|
|
}
|
|
if tr, ok := gt.impl.(interface{ Release(int) error }); ok {
|
|
if err := tr.Release(int(i)); err != nil {
|
|
setVtabZErrMsg(tls, pVtab, err.Error())
|
|
return sqlite3.SQLITE_ERROR
|
|
}
|
|
}
|
|
return sqlite3.SQLITE_OK
|
|
}
|
|
|
|
func vtabRollbackToTrampoline(tls *libc.TLS, pVtab uintptr, i int32) int32 {
|
|
vtabTables.mu.RLock()
|
|
gt := vtabTables.m[pVtab]
|
|
vtabTables.mu.RUnlock()
|
|
if gt == nil {
|
|
return sqlite3.SQLITE_ERROR
|
|
}
|
|
if tr, ok := gt.impl.(interface{ RollbackTo(int) error }); ok {
|
|
if err := tr.RollbackTo(int(i)); err != nil {
|
|
setVtabZErrMsg(tls, pVtab, err.Error())
|
|
return sqlite3.SQLITE_ERROR
|
|
}
|
|
}
|
|
return sqlite3.SQLITE_OK
|
|
}
|