package sqlite3
import (
"context"
"errors"
"reflect"
"github.com/tetratelabs/wazero/api"
"github.com/ncruces/go-sqlite3/internal/util"
)
func CreateModule [T VTab ](db *Conn , name string , create , connect VTabConstructor [T ]) error {
var flags int
const (
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
)
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
}
if implements [VTabShadowTabler ](vtab ) {
flags |= VTAB_SHADOWTABS
}
var modulePtr ptr_t
defer db .arena .mark ()()
namePtr := db .arena .string (name )
if connect != nil {
modulePtr = util .AddHandle (db .ctx , module [T ]{create , connect })
}
rc := res_t (db .call ("sqlite3_create_module_go" , stk_t (db .handle ),
stk_t (namePtr ), stk_t (flags ), stk_t (modulePtr )))
return db .error (rc )
}
func implements[T any ](typ reflect .Type ) bool {
var ptr *T
return typ .Implements (reflect .TypeOf (ptr ).Elem ())
}
func (c *Conn ) DeclareVTab (sql string ) error {
if c .interrupt .Err () != nil {
return INTERRUPT
}
defer c .arena .mark ()()
textPtr := c .arena .string (sql )
rc := res_t (c .call ("sqlite3_declare_vtab" , stk_t (c .handle ), stk_t (textPtr )))
return c .error (rc )
}
type VTabConflictMode uint8
const (
VTAB_ROLLBACK VTabConflictMode = 1
VTAB_IGNORE VTabConflictMode = 2
VTAB_FAIL VTabConflictMode = 3
VTAB_ABORT VTabConflictMode = 4
VTAB_REPLACE VTabConflictMode = 5
)
func (c *Conn ) VTabOnConflict () VTabConflictMode {
return VTabConflictMode (c .call ("sqlite3_vtab_on_conflict" , stk_t (c .handle )))
}
type VTabConfigOption uint8
const (
VTAB_CONSTRAINT_SUPPORT VTabConfigOption = 1
VTAB_INNOCUOUS VTabConfigOption = 2
VTAB_DIRECTONLY VTabConfigOption = 3
VTAB_USES_ALL_SCHEMAS VTabConfigOption = 4
)
func (c *Conn ) VTabConfig (op VTabConfigOption , args ...any ) error {
var i int32
if op == VTAB_CONSTRAINT_SUPPORT && len (args ) > 0 {
if b , ok := args [0 ].(bool ); ok && b {
i = 1
}
}
rc := res_t (c .call ("sqlite3_vtab_config_go" , stk_t (c .handle ), stk_t (op ), stk_t (i )))
return c .error (rc )
}
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
)
type VTab interface {
BestIndex (*IndexInfo ) error
Open () (VTabCursor , error )
}
type VTabDestroyer interface {
VTab
Destroy () error
}
type VTabUpdater interface {
VTab
Update (arg ...Value ) (rowid int64 , err error )
}
type VTabRenamer interface {
VTab
Rename (new string ) error
}
type VTabOverloader interface {
VTab
FindFunction (arg int , name string ) (ScalarFunction , IndexConstraintOp )
}
type VTabShadowTabler interface {
VTab
ShadowTables ()
}
type VTabChecker interface {
VTab
Integrity (schema, table string , flags int ) error
}
type VTabTxn interface {
VTab
Begin () error
Sync () error
Commit () error
Rollback () error
}
type VTabSavepointer interface {
VTabTxn
Savepoint (id int ) error
Release (id int ) error
RollbackTo (id int ) error
}
type VTabCursor interface {
Filter (idxNum int , idxStr string , arg ...Value ) error
Next () error
EOF () bool
Column (ctx Context , n int ) error
RowID () (int64 , error )
}
type IndexInfo struct {
Constraint []IndexConstraint
OrderBy []IndexOrderBy
ColumnsUsed uint64
ConstraintUsage []IndexConstraintUsage
IdxNum int
IdxStr string
IdxFlags IndexScanFlag
OrderByConsumed bool
EstimatedCost float64
EstimatedRows int64
c *Conn
handle ptr_t
}
type IndexConstraint struct {
Column int
Op IndexConstraintOp
Usable bool
}
type IndexOrderBy struct {
Column int
Desc bool
}
type IndexConstraintUsage struct {
ArgvIndex int
Omit bool
}
func (idx *IndexInfo ) RHSValue (column int ) (Value , error ) {
defer idx .c .arena .mark ()()
valPtr := idx .c .arena .new (ptrlen )
rc := res_t (idx .c .call ("sqlite3_vtab_rhs_value" , stk_t (idx .handle ),
stk_t (column ), stk_t (valPtr )))
if err := idx .c .error (rc ); err != nil {
return Value {}, err
}
return Value {
c : idx .c ,
handle : util .Read32 [ptr_t ](idx .c .mod , valPtr ),
}, nil
}
func (idx *IndexInfo ) Collation (column int ) string {
ptr := ptr_t (idx .c .call ("sqlite3_vtab_collation" , stk_t (idx .handle ),
stk_t (column )))
return util .ReadString (idx .c .mod , ptr , _MAX_NAME )
}
func (idx *IndexInfo ) Distinct () int {
i := int32 (idx .c .call ("sqlite3_vtab_distinct" , stk_t (idx .handle )))
return int (i )
}
func (idx *IndexInfo ) In (column , handle int ) bool {
b := int32 (idx .c .call ("sqlite3_vtab_in" , stk_t (idx .handle ),
stk_t (column ), stk_t (handle )))
return b != 0
}
func (idx *IndexInfo ) load () {
mod := idx .c .mod
ptr := idx .handle
nConstraint := util .Read32 [int32 ](mod , ptr +0 )
idx .Constraint = make ([]IndexConstraint , nConstraint )
idx .ConstraintUsage = make ([]IndexConstraintUsage , nConstraint )
idx .OrderBy = make ([]IndexOrderBy , util .Read32 [int32 ](mod , ptr +8 ))
constraintPtr := util .Read32 [ptr_t ](mod , ptr +4 )
constraint := idx .Constraint
for i := range idx .Constraint {
constraint [i ] = IndexConstraint {
Column : int (util .Read32 [int32 ](mod , constraintPtr +0 )),
Op : util .Read [IndexConstraintOp ](mod , constraintPtr +4 ),
Usable : util .Read [byte ](mod , constraintPtr +5 ) != 0 ,
}
constraintPtr += 12
}
orderByPtr := util .Read32 [ptr_t ](mod , ptr +12 )
orderBy := idx .OrderBy
for i := range orderBy {
orderBy [i ] = IndexOrderBy {
Column : int (util .Read32 [int32 ](mod , orderByPtr +0 )),
Desc : util .Read [byte ](mod , orderByPtr +4 ) != 0 ,
}
orderByPtr += 8
}
idx .EstimatedCost = util .ReadFloat64 (mod , ptr +40 )
idx .EstimatedRows = util .Read64 [int64 ](mod , ptr +48 )
idx .ColumnsUsed = util .Read64 [uint64 ](mod , ptr +64 )
}
func (idx *IndexInfo ) save () {
mod := idx .c .mod
ptr := idx .handle
usagePtr := util .Read32 [ptr_t ](mod , ptr +16 )
for _ , usage := range idx .ConstraintUsage {
util .Write32 (mod , usagePtr +0 , int32 (usage .ArgvIndex ))
if usage .Omit {
util .Write (mod , usagePtr +4 , int8 (1 ))
}
usagePtr += 8
}
util .Write32 (mod , ptr +20 , int32 (idx .IdxNum ))
if idx .IdxStr != "" {
util .Write32 (mod , ptr +24 , idx .c .newString (idx .IdxStr ))
util .WriteBool (mod , ptr +28 , true )
}
if idx .OrderByConsumed {
util .WriteBool (mod , ptr +32 , true )
}
util .WriteFloat64 (mod , ptr +40 , idx .EstimatedCost )
util .Write64 (mod , ptr +48 , idx .EstimatedRows )
util .Write32 (mod , ptr +56 , idx .IdxFlags )
}
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
)
type IndexScanFlag uint32
const (
INDEX_SCAN_UNIQUE IndexScanFlag = 0x00000001
INDEX_SCAN_HEX IndexScanFlag = 0x00000002
)
func vtabModuleCallback(i vtabConstructor ) func (_ context .Context , _ api .Module , _ ptr_t , _ int32 , _ , _ , _ ptr_t ) res_t {
return func (ctx context .Context , mod api .Module , pMod ptr_t , nArg int32 , pArg , ppVTab , pzErr ptr_t ) res_t {
arg := make ([]reflect .Value , 1 +nArg )
arg [0 ] = reflect .ValueOf (ctx .Value (connKey {}))
for i := range nArg {
ptr := util .Read32 [ptr_t ](mod , pArg +ptr_t (i )*ptrlen )
arg [i +1 ] = reflect .ValueOf (util .ReadString (mod , ptr , _MAX_SQL_LENGTH ))
}
module := vtabGetHandle (ctx , mod , pMod )
val := reflect .ValueOf (module ).Index (int (i )).Call (arg )
err , _ := val [1 ].Interface ().(error )
if err == nil {
vtabPutHandle (ctx , mod , ppVTab , val [0 ].Interface ())
}
return vtabError (ctx , mod , pzErr , _PTR_ERROR , err , ERROR )
}
}
func vtabDisconnectCallback(ctx context .Context , mod api .Module , pVTab ptr_t ) res_t {
err := vtabDelHandle (ctx , mod , pVTab )
return vtabError (ctx , mod , 0 , _PTR_ERROR , err , ERROR )
}
func vtabDestroyCallback(ctx context .Context , mod api .Module , pVTab ptr_t ) res_t {
vtab := vtabGetHandle (ctx , mod , pVTab ).(VTabDestroyer )
err := errors .Join (vtab .Destroy (), vtabDelHandle (ctx , mod , pVTab ))
return vtabError (ctx , mod , 0 , _PTR_ERROR , err , ERROR )
}
func vtabBestIndexCallback(ctx context .Context , mod api .Module , pVTab , pIdxInfo ptr_t ) res_t {
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 , ERROR )
}
func vtabUpdateCallback(ctx context .Context , mod api .Module , pVTab ptr_t , nArg int32 , pArg , pRowID ptr_t ) res_t {
db := ctx .Value (connKey {}).(*Conn )
args := callbackArgs (db , nArg , pArg )
defer returnArgs (args )
vtab := vtabGetHandle (ctx , mod , pVTab ).(VTabUpdater )
rowID , err := vtab .Update (*args ...)
if err == nil {
util .Write64 (mod , pRowID , rowID )
}
return vtabError (ctx , mod , pVTab , _VTAB_ERROR , err , ERROR )
}
func vtabRenameCallback(ctx context .Context , mod api .Module , pVTab , zNew ptr_t ) res_t {
vtab := vtabGetHandle (ctx , mod , pVTab ).(VTabRenamer )
err := vtab .Rename (util .ReadString (mod , zNew , _MAX_NAME ))
return vtabError (ctx , mod , pVTab , _VTAB_ERROR , err , ERROR )
}
func vtabFindFuncCallback(ctx context .Context , mod api .Module , pVTab ptr_t , nArg int32 , zName , pxFunc ptr_t ) int32 {
vtab := vtabGetHandle (ctx , mod , pVTab ).(VTabOverloader )
f , op := vtab .FindFunction (int (nArg ), util .ReadString (mod , zName , _MAX_NAME ))
if op != 0 {
var wrapper ptr_t
wrapper = util .AddHandle (ctx , func (c Context , arg ...Value ) {
defer util .DelHandle (ctx , wrapper )
f (c , arg ...)
})
util .Write32 (mod , pxFunc , wrapper )
}
return int32 (op )
}
func vtabIntegrityCallback(ctx context .Context , mod api .Module , pVTab , zSchema , zTabName ptr_t , mFlags uint32 , pzErr ptr_t ) res_t {
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 ))
return vtabError (ctx , mod , pzErr , _PTR_ERROR , err , _OK )
}
func vtabBeginCallback(ctx context .Context , mod api .Module , pVTab ptr_t ) res_t {
vtab := vtabGetHandle (ctx , mod , pVTab ).(VTabTxn )
err := vtab .Begin ()
return vtabError (ctx , mod , pVTab , _VTAB_ERROR , err , ERROR )
}
func vtabSyncCallback(ctx context .Context , mod api .Module , pVTab ptr_t ) res_t {
vtab := vtabGetHandle (ctx , mod , pVTab ).(VTabTxn )
err := vtab .Sync ()
return vtabError (ctx , mod , pVTab , _VTAB_ERROR , err , ERROR )
}
func vtabCommitCallback(ctx context .Context , mod api .Module , pVTab ptr_t ) res_t {
vtab := vtabGetHandle (ctx , mod , pVTab ).(VTabTxn )
err := vtab .Commit ()
return vtabError (ctx , mod , pVTab , _VTAB_ERROR , err , ERROR )
}
func vtabRollbackCallback(ctx context .Context , mod api .Module , pVTab ptr_t ) res_t {
vtab := vtabGetHandle (ctx , mod , pVTab ).(VTabTxn )
err := vtab .Rollback ()
return vtabError (ctx , mod , pVTab , _VTAB_ERROR , err , ERROR )
}
func vtabSavepointCallback(ctx context .Context , mod api .Module , pVTab ptr_t , id int32 ) res_t {
vtab := vtabGetHandle (ctx , mod , pVTab ).(VTabSavepointer )
err := vtab .Savepoint (int (id ))
return vtabError (ctx , mod , pVTab , _VTAB_ERROR , err , ERROR )
}
func vtabReleaseCallback(ctx context .Context , mod api .Module , pVTab ptr_t , id int32 ) res_t {
vtab := vtabGetHandle (ctx , mod , pVTab ).(VTabSavepointer )
err := vtab .Release (int (id ))
return vtabError (ctx , mod , pVTab , _VTAB_ERROR , err , ERROR )
}
func vtabRollbackToCallback(ctx context .Context , mod api .Module , pVTab ptr_t , id int32 ) res_t {
vtab := vtabGetHandle (ctx , mod , pVTab ).(VTabSavepointer )
err := vtab .RollbackTo (int (id ))
return vtabError (ctx , mod , pVTab , _VTAB_ERROR , err , ERROR )
}
func cursorOpenCallback(ctx context .Context , mod api .Module , pVTab , ppCur ptr_t ) res_t {
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 , ERROR )
}
func cursorCloseCallback(ctx context .Context , mod api .Module , pCur ptr_t ) res_t {
err := vtabDelHandle (ctx , mod , pCur )
return vtabError (ctx , mod , 0 , _PTR_ERROR , err , ERROR )
}
func cursorFilterCallback(ctx context .Context , mod api .Module , pCur ptr_t , idxNum int32 , idxStr ptr_t , nArg int32 , pArg ptr_t ) res_t {
db := ctx .Value (connKey {}).(*Conn )
args := callbackArgs (db , nArg , pArg )
defer returnArgs (args )
var idxName string
if idxStr != 0 {
idxName = util .ReadString (mod , idxStr , _MAX_LENGTH )
}
cursor := vtabGetHandle (ctx , mod , pCur ).(VTabCursor )
err := cursor .Filter (int (idxNum ), idxName , *args ...)
return vtabError (ctx , mod , pCur , _CURSOR_ERROR , err , ERROR )
}
func cursorEOFCallback(ctx context .Context , mod api .Module , pCur ptr_t ) int32 {
cursor := vtabGetHandle (ctx , mod , pCur ).(VTabCursor )
if cursor .EOF () {
return 1
}
return 0
}
func cursorNextCallback(ctx context .Context , mod api .Module , pCur ptr_t ) res_t {
cursor := vtabGetHandle (ctx , mod , pCur ).(VTabCursor )
err := cursor .Next ()
return vtabError (ctx , mod , pCur , _CURSOR_ERROR , err , ERROR )
}
func cursorColumnCallback(ctx context .Context , mod api .Module , pCur , pCtx ptr_t , n int32 ) res_t {
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 , ERROR )
}
func cursorRowIDCallback(ctx context .Context , mod api .Module , pCur , pRowID ptr_t ) res_t {
cursor := vtabGetHandle (ctx , mod , pCur ).(VTabCursor )
rowID , err := cursor .RowID ()
if err == nil {
util .Write64 (mod , pRowID , rowID )
}
return vtabError (ctx , mod , pCur , _CURSOR_ERROR , err , ERROR )
}
const (
_PTR_ERROR = iota
_VTAB_ERROR
_CURSOR_ERROR
)
func vtabError(ctx context .Context , mod api .Module , ptr ptr_t , kind uint32 , err error , def ErrorCode ) res_t {
const zErrMsgOffset = 8
msg , code := errorCode (err , def )
if ptr != 0 && msg != "" {
switch kind {
case _VTAB_ERROR :
ptr = ptr + zErrMsgOffset
case _CURSOR_ERROR :
ptr = util .Read32 [ptr_t ](mod , ptr ) + zErrMsgOffset
}
db := ctx .Value (connKey {}).(*Conn )
if ptr := util .Read32 [ptr_t ](mod , ptr ); ptr != 0 {
db .free (ptr )
}
util .Write32 (mod , ptr , db .newString (msg ))
}
return code
}
func vtabGetHandle(ctx context .Context , mod api .Module , ptr ptr_t ) any {
const handleOffset = 4
handle := util .Read32 [ptr_t ](mod , ptr -handleOffset )
return util .GetHandle (ctx , handle )
}
func vtabDelHandle(ctx context .Context , mod api .Module , ptr ptr_t ) error {
const handleOffset = 4
handle := util .Read32 [ptr_t ](mod , ptr -handleOffset )
return util .DelHandle (ctx , handle )
}
func vtabPutHandle(ctx context .Context , mod api .Module , pptr ptr_t , val any ) {
const handleOffset = 4
handle := util .AddHandle (ctx , val )
ptr := util .Read32 [ptr_t ](mod , pptr )
util .Write32 (mod , ptr -handleOffset , handle )
}
The pages are generated with Golds v0.8.2 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .