| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965 |
- // 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
- }
|