// Copyright 2023 Ross Light // SPDX-License-Identifier: ISC package sqlite import ( "fmt" "strings" "sync" "unsafe" "modernc.org/libc" "modernc.org/libc/sys/types" lib "modernc.org/sqlite/lib" ) // A Module declares a [virtual table] that can be registered with a [Conn] // via [Conn.SetModule]. // // [virtual table]: https://sqlite.org/vtab.html type Module struct { // Connect establishes a connection to an existing virtual table. // This is the only required field. Connect VTableConnectFunc // Create is called to create a new instance of the virtual table // in response to a [CREATE VIRTUAL TABLE statement]. // If it is nil, then the virtual table is [eponymous]. // UseConnectAsCreate determines whether the virtual table is eponymous-only. // // [CREATE VIRTUAL TABLE statement]: https://sqlite.org/lang_createvtab.html // [eponymous]: https://sqlite.org/vtab.html#epovtab Create VTableConnectFunc // If UseConnectAsCreate is true and Create is nil, // then the virtual table is eponymous, but not eponymous-only. // This means that the virtual table can still be given a name // with CREATE VIRTUAL TABLE // and indicates that the virtual table has no persistent state // that needs to be created and destroyed. UseConnectAsCreate bool } // VTableConnectFunc is a [Module.Connect] or [Module.Create] callback. type VTableConnectFunc func(*Conn, *VTableConnectOptions) (VTable, *VTableConfig, error) // VTableConnectOptions is the set of arguments to a [VTableConnectFunc]. type VTableConnectOptions struct { // ModuleName is the name of the [Module] being invoked. ModuleName string // DatabaseName is the name of the database in which the new virtual table is being created. // The database name is "main" for the primary database, // or "temp" for TEMP database, // or the name given at the end of the ATTACH statement for attached databases. DatabaseName string // VTableName is the name of the name of the new virtual table. // For eponymous virtual tables, this will be the same as ModuleName. VTableName string // Arguments passed to the CREATE VIRTUAL TABLE statement. Args []string } // VTableConfig specifies the configuration of a [VTable] returned by [VTableConnectFunc]. // [VTableConfig.Declaration] is the only required field. type VTableConfig struct { // Declaration must be a [CREATE TABLE statement] // that defines the columns in the virtual table and their data type. // The name of the table in this CREATE TABLE statement is ignored, // as are all constraints. // // [CREATE TABLE statement]: https://sqlite.org/lang_createtable.html Declaration string // If ConstraintSupport is true, then the virtual table implementation // guarantees that if [WritableVTable.Update] or [WritableVTable.DeleteRow] // returns a [ResultConstraint] error, // it will do so before any modifications to internal or persistent data structures // have been made. ConstraintSupport bool // If AllowIndirect is false, then the virtual table may only be used from top-level SQL. // If AllowIndirect is true, then the virtual table can be used in VIEWs, TRIGGERs, // and schema structures (e.g. CHECK constraints and DEFAULT clauses). // // This is the inverse of SQLITE_DIRECTONLY. // See https://sqlite.org/c3ref/c_vtab_constraint_support.html // for more details. // This defaults to false for better security. AllowIndirect bool } // VTable represents a connected [virtual table]. // // [virtual table]: https://sqlite.org/vtab.html type VTable interface { // BestIndex informs SQLite the best way to access the virtual table. // While compiling a single SQL query, // the SQLite core might call BestIndex multiple times with different inputs. // The SQLite core will then select the combination // that appears to give the best performance. BestIndex(*IndexInputs) (*IndexOutputs, error) // Open creates a new cursor. Open() (VTableCursor, error) // Disconnect releases any resources associated with the virtual table. Disconnect() error // Destroy is called when the table is "DROP"ed // to tear down any persistent data structures // and release any resources associated with the virtual table. Destroy() error } // VTableUpdateParams is the set of parameters to the [WritableVTable.Update] method. type VTableUpdateParams struct { OldRowID Value NewRowID Value Columns []Value } // IsInsert reports whether the arguments represent an INSERT. // If not, then the arguments represent an UPDATE. func (p VTableUpdateParams) IsInsert() bool { return p.OldRowID.Type() == TypeNull } // A WritableVTable is a [VTable] that supports modifications. type WritableVTable interface { VTable Update(params VTableUpdateParams) (rowID int64, err error) DeleteRow(rowID Value) error } // A TransactionVTable is a [VTable] that supports transactions. type TransactionVTable interface { VTable // Begin begins a transaction on a virtual table. // Virtual table transactions do not nest, // so the Begin method will not be invoked more than once // on a single virtual table // without an intervening call to either Commit or Rollback. Begin() error // Sync signals the start of a two-phase commit on a virtual table. // This method is only invoked after a call to the Begin method // and prior to a Commit or Rollback. Sync() error // Commit causes a virtual table transaction to commit. Commit() error // Rollback causes a virtual table transaction to rollback. Rollback() error } // A SavepointVTable is a [VTable] that supports savepoints. type SavepointVTable interface { TransactionVTable // Savepoint signals that the virtual table // should save its current state as savepoint N. Savepoint(n int) error // Release invalidates all savepoints greater than or equal to n. Release(n int) error // RollbackTo signals that the state of the virtual table // should return to what it was when Savepoint(n) was last called. // This invalidates all savepoints greater than n. RollbackTo(n int) error } // A RenameVTable is a [VTable] that supports its non-eponymous form being renamed. type RenameVTable interface { VTable Rename(new string) error } // IndexInputs is the set of arguments that the SQLite core passes to [VTable.BestIndex]. type IndexInputs struct { // Constraints corresponds to the WHERE clause. Constraints []IndexConstraint // OrderBy corresponds to the ORDER BY clause. OrderBy []IndexOrderBy // ColumnsUsed is a bitmask of columns used by the statement. ColumnsUsed uint64 } func newIndexInputs(tls *libc.TLS, infoPtr uintptr) *IndexInputs { info := (*lib.Sqlite3_index_info)(unsafe.Pointer(infoPtr)) inputs := &IndexInputs{ Constraints: make([]IndexConstraint, info.FnConstraint), OrderBy: make([]IndexOrderBy, info.FnOrderBy), ColumnsUsed: info.FcolUsed, } ppVal := lib.Xsqlite3_malloc(tls, int32(unsafe.Sizeof(uintptr(0)))) if ppVal != 0 { defer lib.Xsqlite3_free(tls, ppVal) } for i := range inputs.Constraints { inputs.Constraints[i].copyFromC(tls, infoPtr, int32(i), ppVal) } aOrderBy := info.FaOrderBy for i := range inputs.OrderBy { o := (*lib.Sqlite3_index_orderby)(unsafe.Pointer(aOrderBy)) inputs.OrderBy[i] = IndexOrderBy{ Column: int(o.FiColumn), Desc: o.Fdesc != 0, } aOrderBy += unsafe.Sizeof(lib.Sqlite3_index_orderby{}) } return inputs } // IndexOrderBy is a term in the ORDER BY clause. type IndexOrderBy struct { // Column is column index. // Column indices start at 0. Column int // Desc is true if descending or false if ascending. Desc bool } // IndexOutputs is the information that [VTable.BestIndex] returns to the SQLite core. type IndexOutputs struct { // ConstraintUsage is a mapping from [IndexInputs.Constraints] // to [VTableCursor.Filter] arguments. // The mapping is in the same order as [IndexInputs.Constraints] // and must not contain more than len(IndexInputs.Constraints) elements. // If len(ConstraintUsage) < len(IndexInputs.Constraints), // then ConstraintUsage is treated as if the missing elements have the zero value. ConstraintUsage []IndexConstraintUsage // ID is used to identify the index in [VTableCursor.Filter]. ID IndexID // OrderByConsumed is true if the output is already ordered. OrderByConsumed bool // EstimatedCost is an estimate of the cost of a particular strategy. // A cost of N indicates that the cost of the strategy // is similar to a linear scan of an SQLite table with N rows. // A cost of log(N) indicates that the expense of the operation // is similar to that of a binary search on a unique indexed field // of an SQLite table with N rows. // A negative or zero cost uses a large default cost unless UseZeroEstimates is true. EstimatedCost float64 // EstimatedRows is an estimate of the number of rows // that will be returned by the strategy. // A negative or zero estimate uses 25 unless UseZeroEstimates is true. EstimatedRows int64 // If UseZeroEstimates is true and EstimatedCost or EstimatedRows is zero, // then the zeroes will be used instead of being interpreted as defaults. UseZeroEstimates bool // IndexFlags is a bitmask of other flags about the index. IndexFlags IndexFlags } func (outputs *IndexOutputs) copyToC(tls *libc.TLS, infoPtr uintptr) error { info := (*lib.Sqlite3_index_info)(unsafe.Pointer(infoPtr)) aConstraintUsage := info.FaConstraintUsage for _, u := range outputs.ConstraintUsage { ptr := (*lib.Sqlite3_index_constraint_usage)(unsafe.Pointer(aConstraintUsage)) ptr.FargvIndex = int32(u.ArgvIndex) if u.Omit { ptr.Fomit = 1 } else { ptr.Fomit = 0 } aConstraintUsage += unsafe.Sizeof(lib.Sqlite3_index_constraint_usage{}) } info.FidxNum = outputs.ID.Num if len(outputs.ID.String) == 0 { info.FidxStr = 0 info.FneedToFreeIdxStr = 0 } else { var err error info.FidxStr, err = sqliteCString(tls, outputs.ID.String) if err != nil { return err } info.FneedToFreeIdxStr = 1 } if outputs.OrderByConsumed { info.ForderByConsumed = 1 } else { info.ForderByConsumed = 0 } if outputs.EstimatedCost > 0 || outputs.UseZeroEstimates { info.FestimatedCost = outputs.EstimatedCost } if outputs.EstimatedRows > 0 || outputs.UseZeroEstimates { info.FestimatedRows = outputs.EstimatedRows } info.FidxFlags = int32(outputs.IndexFlags) return nil } // IndexConstraintUsage maps a single constraint from [IndexInputs.Constraints] // to a [VTableCursor.Filter] argument in the [IndexOutputs.ConstraintUsage] list. type IndexConstraintUsage struct { // ArgvIndex is the intended [VTableCursor.Filter] argument index plus one. // If ArgvIndex is zero or negative, // then the constraint is not passed to [VTableCursor.Filter]. // Within the [IndexOutputs.ConstraintUsage] list, // there must be exactly one entry with an ArgvIndex of 1, // another of 2, another of 3, and so forth // to as many or as few as the [VTable.BestIndex] method wants. ArgvIndex int // If Omit is true, then it is a hint to SQLite // that the virtual table will guarantee that the constraint will always be satisfied. // SQLite will always double-check that rows satisfy the constraint if Omit is false, // but may skip this check if Omit is true. Omit bool } // IndexID is a virtual table index identifier. // The meaning of its fields is defined by the virtual table implementation. // String cannot contain NUL bytes. type IndexID struct { Num int32 String string } // IndexFlags is a bitmap of options returned in [IndexOutputs.IndexFlags]. type IndexFlags uint32 const ( // IndexScanUnique indicates that the virtual table // will only return zero or one rows given the input constraints. IndexScanUnique IndexFlags = lib.SQLITE_INDEX_SCAN_UNIQUE ) // VTableCursor is a cursor over a [VTable] used to loop through the table. type VTableCursor interface { // Filter begins a search of a virtual table. // The ID is one that is returned by [VTable.BestIndex]. // The arguments will be populated as specified by [IndexOutputs.ConstraintUsage]. Filter(id IndexID, argv []Value) error // Next advances the cursor to the next row of a result set // initiated by [VTableCursor.Filter]. // If the cursor is already pointing at the last row when this routine is called, // then the cursor no longer points to valid data // and a subsequent call to the [VTableCursor.EOF] method must return true. Next() error // Column returns the value for the i-th column of the current row. // Column indices start at 0. // // If noChange is true, then the column access is part of an UPDATE operation // during which the column value will not change. // This can be used as a hint to return [Unchanged] instead of fetching the value: // [WritableVTable.Update] implementations can check [Value.NoChange] to test for this condition. Column(i int, noChange bool) (Value, error) // RowID returns the row ID of the row that the cursor is currently pointing at. RowID() (int64, error) // EOF reports if the cursor is not pointing to a valid row of data. EOF() bool // Close releases any resources associated with the cursor. Close() error } // SetModule registers or unregisters a virtual table module with the given name. func (c *Conn) SetModule(name string, module *Module) error { if c == nil { return fmt.Errorf("sqlite: set module %q: nil connection", name) } cname, err := libc.CString(name) if err != nil { return fmt.Errorf("sqlite: set module %q: %v", name, err) } defer libc.Xfree(c.tls, cname) if module == nil { res := ResultCode(lib.Xsqlite3_create_module_v2(c.tls, c.conn, cname, 0, 0, 0)) if err := res.ToError(); err != nil { return fmt.Errorf("sqlite: set module %q: %w", name, err) } return nil } if module.Connect == nil { return fmt.Errorf("sqlite: set module %q: connect not provided", name) } cmod := lib.Xsqlite3_malloc(c.tls, int32(unsafe.Sizeof(lib.Sqlite3_module{}))) if cmod == 0 { return fmt.Errorf("sqlite: set module %q: %w", name, ResultNoMem.ToError()) } libc.Xmemset(c.tls, cmod, 0, types.Size_t(unsafe.Sizeof(lib.Sqlite3_module{}))) cmodPtr := (*lib.Sqlite3_module)(unsafe.Pointer(cmod)) cmodPtr.FiVersion = 3 cmodPtr.FxConnect = cFuncPointer(vtabConnectTrampoline) if module.Create != nil { cmodPtr.FxCreate = cFuncPointer(vtabCreateTrampoline) } else if module.UseConnectAsCreate { cmodPtr.FxCreate = cmodPtr.FxConnect } cmodPtr.FxBestIndex = cFuncPointer(vtabBestIndexTrampoline) cmodPtr.FxDisconnect = cFuncPointer(vtabDisconnect) cmodPtr.FxDestroy = cFuncPointer(vtabDestroy) cmodPtr.FxOpen = cFuncPointer(vtabOpenTrampoline) cmodPtr.FxClose = cFuncPointer(vtabCloseTrampoline) cmodPtr.FxFilter = cFuncPointer(vtabFilterTrampoline) cmodPtr.FxNext = cFuncPointer(vtabNextTrampoline) cmodPtr.FxEof = cFuncPointer(vtabEOFTrampoline) cmodPtr.FxColumn = cFuncPointer(vtabColumnTrampoline) cmodPtr.FxRowid = cFuncPointer(vtabRowIDTrampoline) cmodPtr.FxUpdate = cFuncPointer(vtabUpdateTrampoline) cmodPtr.FxBegin = cFuncPointer(vtabBeginTrampoline) cmodPtr.FxSync = cFuncPointer(vtabSyncTrampoline) cmodPtr.FxCommit = cFuncPointer(vtabCommitTrampoline) cmodPtr.FxRollback = cFuncPointer(vtabRollbackTrampoline) cmodPtr.FxRename = cFuncPointer(vtabRenameTrampoline) cmodPtr.FxSavepoint = cFuncPointer(vtabSavepointTrampoline) cmodPtr.FxRelease = cFuncPointer(vtabReleaseTrampoline) cmodPtr.FxRollbackTo = cFuncPointer(vtabRollbackToTrampoline) xDestroy := cFuncPointer(destroyModule) xmodules.mu.Lock() defensiveCopy := new(Module) *defensiveCopy = *module // Module pointer address is unique for lifetime of module. xmodules.m[cmod] = defensiveCopy xmodules.mu.Unlock() res := ResultCode(lib.Xsqlite3_create_module_v2(c.tls, c.conn, cname, cmod, cmod, xDestroy)) if err := res.ToError(); err != nil { return fmt.Errorf("sqlite: set module %q: %w", name, err) } return nil } func vtabCreateTrampoline(tls *libc.TLS, db uintptr, pAux uintptr, argc int32, argv uintptr, ppVTab uintptr, pzErr uintptr) int32 { xmodules.mu.RLock() module := xmodules.m[pAux] xmodules.mu.RUnlock() return callConnectFunc(tls, module.Create, db, argc, argv, ppVTab, pzErr) } func vtabConnectTrampoline(tls *libc.TLS, db uintptr, pAux uintptr, argc int32, argv uintptr, ppVTab uintptr, pzErr uintptr) int32 { xmodules.mu.RLock() module := xmodules.m[pAux] xmodules.mu.RUnlock() return callConnectFunc(tls, module.Connect, db, argc, argv, ppVTab, pzErr) } func callConnectFunc(tls *libc.TLS, connect VTableConnectFunc, db uintptr, argc int32, argv uintptr, ppVTab uintptr, pzErr uintptr) (retcode int32) { allConns.mu.RLock() c := allConns.table[db] allConns.mu.RUnlock() options := new(VTableConnectOptions) if argc > 0 { options.ModuleName = libc.GoString(*(*uintptr)(unsafe.Pointer(argv))) argc-- argv += uintptr(ptrSize) } if argc > 0 { options.DatabaseName = libc.GoString(*(*uintptr)(unsafe.Pointer(argv))) argc-- argv += uintptr(ptrSize) } if argc > 0 { options.VTableName = libc.GoString(*(*uintptr)(unsafe.Pointer(argv))) argc-- argv += uintptr(ptrSize) } if argc > 0 { options.Args = make([]string, argc) for i := range options.Args { options.Args[i] = libc.GoString(*(*uintptr)(unsafe.Pointer(argv))) argv += uintptr(ptrSize) } } vtab, cfg, err := connect(c, options) if err != nil { zerr, _ := sqliteCString(tls, err.Error()) *(*uintptr)(unsafe.Pointer(pzErr)) = zerr return int32(ErrCode(err)) } defer func() { if retcode != lib.SQLITE_OK { vtab.Disconnect() } }() // Call vtab configuration functions based on result. cdecl, err := libc.CString(cfg.Declaration) if err != nil { return lib.SQLITE_NOMEM } defer libc.Xfree(tls, cdecl) if res := ResultCode(lib.Xsqlite3_declare_vtab(tls, db, cdecl)); !res.IsSuccess() { return int32(res) } if !cfg.AllowIndirect { lib.Xsqlite3_vtab_config(tls, db, lib.SQLITE_VTAB_DIRECTONLY, 0) } if cfg.ConstraintSupport { vargs := libc.NewVaList(int32(1)) lib.Xsqlite3_vtab_config(tls, db, lib.SQLITE_VTAB_DIRECTONLY, vargs) libc.Xfree(tls, vargs) } vtabWrapperSize := int32(unsafe.Sizeof(vtabWrapper{})) pvtab := lib.Xsqlite3_malloc(tls, vtabWrapperSize) *(*uintptr)(unsafe.Pointer(ppVTab)) = pvtab if pvtab == 0 { return lib.SQLITE_NOMEM } libc.Xmemset(tls, pvtab, 0, types.Size_t(vtabWrapperSize)) avt := assertVTable(vtab) xvtables.mu.Lock() id := xvtables.ids.next() xvtables.m[id] = avt xvtables.mu.Unlock() (*vtabWrapper)(unsafe.Pointer(pvtab)).id = id return lib.SQLITE_OK } func vtabDisconnect(tls *libc.TLS, pVTab uintptr) int32 { id := (*vtabWrapper)(unsafe.Pointer(pVTab)).id lib.Xsqlite3_free(tls, pVTab) xvtables.mu.Lock() xvtables.ids.reclaim(id) vtab := xvtables.m[id] delete(xvtables.m, id) xvtables.mu.Unlock() return int32(ErrCode(vtab.Disconnect())) } func vtabDestroy(tls *libc.TLS, pVTab uintptr) int32 { id := (*vtabWrapper)(unsafe.Pointer(pVTab)).id lib.Xsqlite3_free(tls, pVTab) xvtables.mu.Lock() xvtables.ids.reclaim(id) vtab := xvtables.m[id] delete(xvtables.m, id) xvtables.mu.Unlock() return int32(ErrCode(vtab.Destroy())) } func vtabBestIndexTrampoline(tls *libc.TLS, pVTab uintptr, infoPtr uintptr) int32 { vw := (*vtabWrapper)(unsafe.Pointer(pVTab)) info := (*lib.Sqlite3_index_info)(unsafe.Pointer(infoPtr)) xvtables.mu.RLock() vtab := xvtables.m[vw.id] xvtables.mu.RUnlock() outputs, err := vtab.BestIndex(newIndexInputs(tls, infoPtr)) if err != nil { vw.setErrorMessage(tls, err.Error()) return int32(ErrCode(err)) } if len(outputs.ConstraintUsage) > int(info.FnConstraint) { vw.setErrorMessage(tls, fmt.Sprintf("len(ConstraintUsage) = %d (> %d)", len(outputs.ConstraintUsage), info.FnConstraint)) return int32(ResultMisuse) } if err := outputs.copyToC(tls, infoPtr); err != nil { vw.setErrorMessage(tls, err.Error()) return int32(ErrCode(err)) } return lib.SQLITE_OK } func vtabOpenTrampoline(tls *libc.TLS, pVTab uintptr, ppCursor uintptr) int32 { vw := (*vtabWrapper)(unsafe.Pointer(pVTab)) vtabID := vw.id xvtables.mu.RLock() vtab := xvtables.m[vtabID] xvtables.mu.RUnlock() cursor, err := vtab.Open() if err != nil { vw.setErrorMessage(tls, err.Error()) return int32(ErrCode(err)) } cursorWrapperSize := int32(unsafe.Sizeof(vtabWrapper{})) pcursor := lib.Xsqlite3_malloc(tls, cursorWrapperSize) *(*uintptr)(unsafe.Pointer(ppCursor)) = pcursor if pcursor == 0 { cursor.Close() vw.setErrorMessage(tls, "no memory for cursor wrapper") return lib.SQLITE_NOMEM } libc.Xmemset(tls, pcursor, 0, types.Size_t(cursorWrapperSize)) xcursors.mu.Lock() cursorID := xcursors.ids.next() xcursors.m[cursorID] = cursor xcursors.mu.Unlock() (*cursorWrapper)(unsafe.Pointer(pcursor)).id = cursorID return lib.SQLITE_OK } func vtabCloseTrampoline(tls *libc.TLS, pCursor uintptr) int32 { id := (*cursorWrapper)(unsafe.Pointer(pCursor)).id pVTab := (*cursorWrapper)(unsafe.Pointer(pCursor)).base.FpVtab xcursors.mu.Lock() cur := xcursors.m[id] delete(xcursors.m, id) xcursors.ids.reclaim(id) xcursors.mu.Unlock() lib.Xsqlite3_free(tls, pCursor) if err := cur.Close(); err != nil { (*vtabWrapper)(unsafe.Pointer(pVTab)).setErrorMessage(tls, err.Error()) return int32(ErrCode(err)) } return lib.SQLITE_OK } func vtabFilterTrampoline(tls *libc.TLS, pCursor uintptr, idxNum int32, idxStr uintptr, argc int32, argv uintptr) int32 { cw := (*cursorWrapper)(unsafe.Pointer(pCursor)) xcursors.mu.RLock() cur := xcursors.m[cw.id] xcursors.mu.RUnlock() idxID := IndexID{ Num: idxNum, String: libc.GoString(idxStr), } goArgv := make([]Value, 0, int(argc)) for ; len(goArgv) < cap(goArgv); argv += uintptr(ptrSize) { goArgv = append(goArgv, Value{ tls: tls, ptrOrType: *(*uintptr)(unsafe.Pointer(argv)), }) } if err := cur.Filter(idxID, goArgv); err != nil { cw.setErrorMessage(tls, err.Error()) return int32(ErrCode(err)) } return lib.SQLITE_OK } func vtabNextTrampoline(tls *libc.TLS, pCursor uintptr) int32 { cw := (*cursorWrapper)(unsafe.Pointer(pCursor)) xcursors.mu.RLock() cur := xcursors.m[cw.id] xcursors.mu.RUnlock() if err := cur.Next(); err != nil { cw.setErrorMessage(tls, err.Error()) return int32(ErrCode(err)) } return lib.SQLITE_OK } func vtabEOFTrampoline(tls *libc.TLS, pCursor uintptr) int32 { id := (*cursorWrapper)(unsafe.Pointer(pCursor)).id xcursors.mu.RLock() cur := xcursors.m[id] xcursors.mu.RUnlock() if cur.EOF() { return 1 } return 0 } func vtabColumnTrampoline(tls *libc.TLS, pCursor uintptr, ctx uintptr, n int32) int32 { id := (*cursorWrapper)(unsafe.Pointer(pCursor)).id xcursors.mu.RLock() cur := xcursors.m[id] xcursors.mu.RUnlock() goCtx := Context{tls: tls, ptr: ctx} noChange := lib.Xsqlite3_vtab_nochange(tls, ctx) != 0 v, err := cur.Column(int(n), noChange) if err != nil { goCtx.result(TextValue(err.Error()), nil) return int32(ErrCode(err)) } if noChange && v.tls == nil && v.NoChange() { // Skip calling a result function if the method returns Unchanged. return lib.SQLITE_OK } goCtx.result(v, nil) return lib.SQLITE_OK } func vtabRowIDTrampoline(tls *libc.TLS, pCursor uintptr, pRowid uintptr) int32 { cw := (*cursorWrapper)(unsafe.Pointer(pCursor)) xcursors.mu.RLock() cur := xcursors.m[cw.id] xcursors.mu.RUnlock() rowID, err := cur.RowID() if err != nil { cw.setErrorMessage(tls, err.Error()) return int32(ErrCode(err)) } *(*int64)(unsafe.Pointer(pRowid)) = rowID return lib.SQLITE_OK } func vtabUpdateTrampoline(tls *libc.TLS, pVTab uintptr, argc int32, argv uintptr, pRowid uintptr) int32 { vw := (*vtabWrapper)(unsafe.Pointer(pVTab)) xvtables.mu.RLock() vtab := xvtables.m[vw.id] xvtables.mu.RUnlock() if vtab.Write == nil { vw.setErrorMessage(tls, fmt.Sprintf("%T does not implement WritableVTable", vtab.VTable)) return lib.SQLITE_READONLY } if argc < 1 { panic("SQLite did not give enough arguments to xUpdate") } oldRowID := Value{ tls: tls, ptrOrType: *(*uintptr)(unsafe.Pointer(argv)), } if argc == 1 { if err := vtab.Write.DeleteRow(oldRowID); err != nil { vw.setErrorMessage(tls, err.Error()) return int32(ErrCode(err)) } return lib.SQLITE_OK } goArgs := VTableUpdateParams{ OldRowID: oldRowID, } argv += unsafe.Sizeof(uintptr(0)) goArgs.NewRowID = Value{ tls: tls, ptrOrType: *(*uintptr)(unsafe.Pointer(argv)), } if argc > 2 { goArgs.Columns = make([]Value, argc-2) argv += unsafe.Sizeof(uintptr(0)) for i := range goArgs.Columns { goArgs.Columns[i] = Value{ tls: tls, ptrOrType: *(*uintptr)(unsafe.Pointer(argv)), } argv += unsafe.Sizeof(uintptr(0)) } } insertRowID, err := vtab.Write.Update(goArgs) if err != nil { vw.setErrorMessage(tls, err.Error()) return int32(ErrCode(err)) } *(*int64)(unsafe.Pointer(pRowid)) = insertRowID return lib.SQLITE_OK } func vtabBeginTrampoline(tls *libc.TLS, pVTab uintptr) int32 { vw := (*vtabWrapper)(unsafe.Pointer(pVTab)) xvtables.mu.RLock() vtab := xvtables.m[vw.id] xvtables.mu.RUnlock() if vtab.Transaction != nil { if err := vtab.Transaction.Begin(); err != nil { vw.setErrorMessage(tls, err.Error()) return int32(ErrCode(err)) } } return lib.SQLITE_OK } func vtabSyncTrampoline(tls *libc.TLS, pVTab uintptr) int32 { vw := (*vtabWrapper)(unsafe.Pointer(pVTab)) xvtables.mu.RLock() vtab := xvtables.m[vw.id] xvtables.mu.RUnlock() if vtab.Transaction != nil { if err := vtab.Transaction.Sync(); err != nil { vw.setErrorMessage(tls, err.Error()) return int32(ErrCode(err)) } } return lib.SQLITE_OK } func vtabCommitTrampoline(tls *libc.TLS, pVTab uintptr) int32 { vw := (*vtabWrapper)(unsafe.Pointer(pVTab)) xvtables.mu.RLock() vtab := xvtables.m[vw.id] xvtables.mu.RUnlock() if vtab.Transaction != nil { if err := vtab.Transaction.Commit(); err != nil { vw.setErrorMessage(tls, err.Error()) return int32(ErrCode(err)) } } return lib.SQLITE_OK } func vtabRollbackTrampoline(tls *libc.TLS, pVTab uintptr) int32 { vw := (*vtabWrapper)(unsafe.Pointer(pVTab)) xvtables.mu.RLock() vtab := xvtables.m[vw.id] xvtables.mu.RUnlock() if vtab.Transaction != nil { if err := vtab.Transaction.Rollback(); err != nil { vw.setErrorMessage(tls, err.Error()) return int32(ErrCode(err)) } } return lib.SQLITE_OK } func vtabRenameTrampoline(tls *libc.TLS, pVTab uintptr, zNew uintptr) int32 { vw := (*vtabWrapper)(unsafe.Pointer(pVTab)) xvtables.mu.RLock() vtab := xvtables.m[vw.id] xvtables.mu.RUnlock() if vtab.Rename == nil { vw.setErrorMessage(tls, fmt.Sprintf("no Rename method for %T", vtab.VTable)) return lib.SQLITE_READONLY } if err := vtab.Rename.Rename(libc.GoString(zNew)); err != nil { vw.setErrorMessage(tls, err.Error()) return int32(ErrCode(err)) } return lib.SQLITE_OK } func vtabSavepointTrampoline(tls *libc.TLS, pVTab uintptr, n int32) int32 { vw := (*vtabWrapper)(unsafe.Pointer(pVTab)) xvtables.mu.RLock() vtab := xvtables.m[vw.id] xvtables.mu.RUnlock() if vtab.Savepoint != nil { if err := vtab.Savepoint.Savepoint(int(n)); err != nil { vw.setErrorMessage(tls, err.Error()) return int32(ErrCode(err)) } } return lib.SQLITE_OK } func vtabReleaseTrampoline(tls *libc.TLS, pVTab uintptr, n int32) int32 { vw := (*vtabWrapper)(unsafe.Pointer(pVTab)) xvtables.mu.RLock() vtab := xvtables.m[vw.id] xvtables.mu.RUnlock() if vtab.Savepoint != nil { if err := vtab.Savepoint.Release(int(n)); err != nil { vw.setErrorMessage(tls, err.Error()) return int32(ErrCode(err)) } } return lib.SQLITE_OK } func vtabRollbackToTrampoline(tls *libc.TLS, pVTab uintptr, n int32) int32 { vw := (*vtabWrapper)(unsafe.Pointer(pVTab)) xvtables.mu.RLock() vtab := xvtables.m[vw.id] xvtables.mu.RUnlock() if vtab.Savepoint != nil { if err := vtab.Savepoint.RollbackTo(int(n)); err != nil { vw.setErrorMessage(tls, err.Error()) return int32(ErrCode(err)) } } return lib.SQLITE_OK } func destroyModule(tls *libc.TLS, pAux uintptr) { xmodules.mu.Lock() delete(xmodules.m, pAux) xmodules.mu.Unlock() lib.Xsqlite3_free(tls, pAux) } type vtabWrapper struct { base lib.Sqlite3_vtab id uintptr } func (vw *vtabWrapper) setErrorMessage(tls *libc.TLS, s string) { if vw.base.FzErrMsg != 0 { lib.Xsqlite3_free(tls, vw.base.FzErrMsg) } vw.base.FzErrMsg, _ = sqliteCString(tls, s) } type cursorWrapper struct { base lib.Sqlite3_vtab_cursor id uintptr } func (cw *cursorWrapper) setErrorMessage(tls *libc.TLS, s string) { vw := (*vtabWrapper)(unsafe.Pointer(cw.base.FpVtab)) vw.setErrorMessage(tls, s) } type assertedVTable struct { VTable Write WritableVTable Transaction TransactionVTable Savepoint SavepointVTable Rename RenameVTable } func assertVTable(vtab VTable) assertedVTable { avt := assertedVTable{VTable: vtab} avt.Write, _ = vtab.(WritableVTable) avt.Transaction, _ = vtab.(TransactionVTable) avt.Savepoint, _ = vtab.(SavepointVTable) avt.Rename, _ = vtab.(RenameVTable) return avt } var ( xmodules = struct { mu sync.RWMutex m map[uintptr]*Module }{ m: make(map[uintptr]*Module), } xvtables = struct { mu sync.RWMutex m map[uintptr]assertedVTable ids idGen }{ m: make(map[uintptr]assertedVTable), } xcursors = struct { mu sync.RWMutex m map[uintptr]VTableCursor ids idGen }{ m: make(map[uintptr]VTableCursor), } ) // sqliteCString copies a Go string to SQLite-allocated memory. func sqliteCString(tls *libc.TLS, s string) (uintptr, error) { if strings.Contains(s, "\x00") { return 0, fmt.Errorf("%q contains NUL bytes", s) } csize := len(s) + 1 c := lib.Xsqlite3_malloc(tls, int32(csize)) if c == 0 { return 0, fmt.Errorf("%w: cannot allocate %d bytes", ResultNoMem.ToError(), len(s)) } cslice := unsafe.Slice((*byte)(unsafe.Pointer(c)), csize) copy(cslice, s) cslice[len(s)] = 0 return c, nil }