vtab.go

  1package sqlite3
  2
  3import (
  4	"context"
  5	"errors"
  6	"reflect"
  7
  8	"github.com/tetratelabs/wazero/api"
  9
 10	"github.com/ncruces/go-sqlite3/internal/util"
 11)
 12
 13// CreateModule registers a new virtual table module name.
 14// If create is nil, the virtual table is eponymous.
 15//
 16// https://sqlite.org/c3ref/create_module.html
 17func CreateModule[T VTab](db *Conn, name string, create, connect VTabConstructor[T]) error {
 18	var flags int
 19
 20	const (
 21		VTAB_CREATOR     = 0x001
 22		VTAB_DESTROYER   = 0x002
 23		VTAB_UPDATER     = 0x004
 24		VTAB_RENAMER     = 0x008
 25		VTAB_OVERLOADER  = 0x010
 26		VTAB_CHECKER     = 0x020
 27		VTAB_TXN         = 0x040
 28		VTAB_SAVEPOINTER = 0x080
 29		VTAB_SHADOWTABS  = 0x100
 30	)
 31
 32	if create != nil {
 33		flags |= VTAB_CREATOR
 34	}
 35
 36	vtab := reflect.TypeOf(connect).Out(0)
 37	if implements[VTabDestroyer](vtab) {
 38		flags |= VTAB_DESTROYER
 39	}
 40	if implements[VTabUpdater](vtab) {
 41		flags |= VTAB_UPDATER
 42	}
 43	if implements[VTabRenamer](vtab) {
 44		flags |= VTAB_RENAMER
 45	}
 46	if implements[VTabOverloader](vtab) {
 47		flags |= VTAB_OVERLOADER
 48	}
 49	if implements[VTabChecker](vtab) {
 50		flags |= VTAB_CHECKER
 51	}
 52	if implements[VTabTxn](vtab) {
 53		flags |= VTAB_TXN
 54	}
 55	if implements[VTabSavepointer](vtab) {
 56		flags |= VTAB_SAVEPOINTER
 57	}
 58	if implements[VTabShadowTabler](vtab) {
 59		flags |= VTAB_SHADOWTABS
 60	}
 61
 62	var modulePtr ptr_t
 63	defer db.arena.mark()()
 64	namePtr := db.arena.string(name)
 65	if connect != nil {
 66		modulePtr = util.AddHandle(db.ctx, module[T]{create, connect})
 67	}
 68	rc := res_t(db.call("sqlite3_create_module_go", stk_t(db.handle),
 69		stk_t(namePtr), stk_t(flags), stk_t(modulePtr)))
 70	return db.error(rc)
 71}
 72
 73func implements[T any](typ reflect.Type) bool {
 74	var ptr *T
 75	return typ.Implements(reflect.TypeOf(ptr).Elem())
 76}
 77
 78// DeclareVTab declares the schema of a virtual table.
 79//
 80// https://sqlite.org/c3ref/declare_vtab.html
 81func (c *Conn) DeclareVTab(sql string) error {
 82	if c.interrupt.Err() != nil {
 83		return INTERRUPT
 84	}
 85	defer c.arena.mark()()
 86	textPtr := c.arena.string(sql)
 87	rc := res_t(c.call("sqlite3_declare_vtab", stk_t(c.handle), stk_t(textPtr)))
 88	return c.error(rc)
 89}
 90
 91// VTabConflictMode is a virtual table conflict resolution mode.
 92//
 93// https://sqlite.org/c3ref/c_fail.html
 94type VTabConflictMode uint8
 95
 96const (
 97	VTAB_ROLLBACK VTabConflictMode = 1
 98	VTAB_IGNORE   VTabConflictMode = 2
 99	VTAB_FAIL     VTabConflictMode = 3
100	VTAB_ABORT    VTabConflictMode = 4
101	VTAB_REPLACE  VTabConflictMode = 5
102)
103
104// VTabOnConflict determines the virtual table conflict policy.
105//
106// https://sqlite.org/c3ref/vtab_on_conflict.html
107func (c *Conn) VTabOnConflict() VTabConflictMode {
108	return VTabConflictMode(c.call("sqlite3_vtab_on_conflict", stk_t(c.handle)))
109}
110
111// VTabConfigOption is a virtual table configuration option.
112//
113// https://sqlite.org/c3ref/c_vtab_constraint_support.html
114type VTabConfigOption uint8
115
116const (
117	VTAB_CONSTRAINT_SUPPORT VTabConfigOption = 1
118	VTAB_INNOCUOUS          VTabConfigOption = 2
119	VTAB_DIRECTONLY         VTabConfigOption = 3
120	VTAB_USES_ALL_SCHEMAS   VTabConfigOption = 4
121)
122
123// VTabConfig configures various facets of the virtual table interface.
124//
125// https://sqlite.org/c3ref/vtab_config.html
126func (c *Conn) VTabConfig(op VTabConfigOption, args ...any) error {
127	var i int32
128	if op == VTAB_CONSTRAINT_SUPPORT && len(args) > 0 {
129		if b, ok := args[0].(bool); ok && b {
130			i = 1
131		}
132	}
133	rc := res_t(c.call("sqlite3_vtab_config_go", stk_t(c.handle), stk_t(op), stk_t(i)))
134	return c.error(rc)
135}
136
137// VTabConstructor is a virtual table constructor function.
138type VTabConstructor[T VTab] func(db *Conn, module, schema, table string, arg ...string) (T, error)
139
140type module[T VTab] [2]VTabConstructor[T]
141
142type vtabConstructor int
143
144const (
145	xCreate  vtabConstructor = 0
146	xConnect vtabConstructor = 1
147)
148
149// A VTab describes a particular instance of the virtual table.
150// A VTab may optionally implement [io.Closer] to free resources.
151//
152// https://sqlite.org/c3ref/vtab.html
153type VTab interface {
154	// https://sqlite.org/vtab.html#xbestindex
155	BestIndex(*IndexInfo) error
156	// https://sqlite.org/vtab.html#xopen
157	Open() (VTabCursor, error)
158}
159
160// A VTabDestroyer allows a virtual table to drop persistent state.
161type VTabDestroyer interface {
162	VTab
163	// https://sqlite.org/vtab.html#sqlite3_module.xDestroy
164	Destroy() error
165}
166
167// A VTabUpdater allows a virtual table to be updated.
168// Implementations must not retain arg.
169type VTabUpdater interface {
170	VTab
171	// https://sqlite.org/vtab.html#xupdate
172	Update(arg ...Value) (rowid int64, err error)
173}
174
175// A VTabRenamer allows a virtual table to be renamed.
176type VTabRenamer interface {
177	VTab
178	// https://sqlite.org/vtab.html#xrename
179	Rename(new string) error
180}
181
182// A VTabOverloader allows a virtual table to overload SQL functions.
183type VTabOverloader interface {
184	VTab
185	// https://sqlite.org/vtab.html#xfindfunction
186	FindFunction(arg int, name string) (ScalarFunction, IndexConstraintOp)
187}
188
189// A VTabShadowTabler allows a virtual table to protect the content
190// of shadow tables from being corrupted by hostile SQL.
191//
192// Implementing this interface signals that a virtual table named
193// "mumble" reserves all table names starting with "mumble_".
194type VTabShadowTabler interface {
195	VTab
196	// https://sqlite.org/vtab.html#the_xshadowname_method
197	ShadowTables()
198}
199
200// A VTabChecker allows a virtual table to report errors
201// to the PRAGMA integrity_check and PRAGMA quick_check commands.
202//
203// Integrity should return an error if it finds problems in the content of the virtual table,
204// but should avoid returning a (wrapped) [Error], [ErrorCode] or [ExtendedErrorCode],
205// as those indicate the Integrity method itself encountered problems
206// while trying to evaluate the virtual table content.
207type VTabChecker interface {
208	VTab
209	// https://sqlite.org/vtab.html#xintegrity
210	Integrity(schema, table string, flags int) error
211}
212
213// A VTabTxn allows a virtual table to implement
214// transactions with two-phase commit.
215//
216// Anything that is required as part of a commit that may fail
217// should be performed in the Sync() callback.
218// Current versions of SQLite ignore any errors
219// returned by Commit() and Rollback().
220type VTabTxn interface {
221	VTab
222	// https://sqlite.org/vtab.html#xBegin
223	Begin() error
224	// https://sqlite.org/vtab.html#xsync
225	Sync() error
226	// https://sqlite.org/vtab.html#xcommit
227	Commit() error
228	// https://sqlite.org/vtab.html#xrollback
229	Rollback() error
230}
231
232// A VTabSavepointer allows a virtual table to implement
233// nested transactions.
234//
235// https://sqlite.org/vtab.html#xsavepoint
236type VTabSavepointer interface {
237	VTabTxn
238	Savepoint(id int) error
239	Release(id int) error
240	RollbackTo(id int) error
241}
242
243// A VTabCursor describes cursors that point
244// into the virtual table and are used
245// to loop through the virtual table.
246// A VTabCursor may optionally implement
247// [io.Closer] to free resources.
248// Implementations of Filter must not retain arg.
249//
250// https://sqlite.org/c3ref/vtab_cursor.html
251type VTabCursor interface {
252	// https://sqlite.org/vtab.html#xfilter
253	Filter(idxNum int, idxStr string, arg ...Value) error
254	// https://sqlite.org/vtab.html#xnext
255	Next() error
256	// https://sqlite.org/vtab.html#xeof
257	EOF() bool
258	// https://sqlite.org/vtab.html#xcolumn
259	Column(ctx Context, n int) error
260	// https://sqlite.org/vtab.html#xrowid
261	RowID() (int64, error)
262}
263
264// An IndexInfo describes virtual table indexing information.
265//
266// https://sqlite.org/c3ref/index_info.html
267type IndexInfo struct {
268	// Inputs
269	Constraint  []IndexConstraint
270	OrderBy     []IndexOrderBy
271	ColumnsUsed uint64
272	// Outputs
273	ConstraintUsage []IndexConstraintUsage
274	IdxNum          int
275	IdxStr          string
276	IdxFlags        IndexScanFlag
277	OrderByConsumed bool
278	EstimatedCost   float64
279	EstimatedRows   int64
280	// Internal
281	c      *Conn
282	handle ptr_t
283}
284
285// An IndexConstraint describes virtual table indexing constraint information.
286//
287// https://sqlite.org/c3ref/index_info.html
288type IndexConstraint struct {
289	Column int
290	Op     IndexConstraintOp
291	Usable bool
292}
293
294// An IndexOrderBy describes virtual table indexing order by information.
295//
296// https://sqlite.org/c3ref/index_info.html
297type IndexOrderBy struct {
298	Column int
299	Desc   bool
300}
301
302// An IndexConstraintUsage describes how virtual table indexing constraints will be used.
303//
304// https://sqlite.org/c3ref/index_info.html
305type IndexConstraintUsage struct {
306	ArgvIndex int
307	Omit      bool
308}
309
310// RHSValue returns the value of the right-hand operand of a constraint
311// if the right-hand operand is known.
312//
313// https://sqlite.org/c3ref/vtab_rhs_value.html
314func (idx *IndexInfo) RHSValue(column int) (Value, error) {
315	defer idx.c.arena.mark()()
316	valPtr := idx.c.arena.new(ptrlen)
317	rc := res_t(idx.c.call("sqlite3_vtab_rhs_value", stk_t(idx.handle),
318		stk_t(column), stk_t(valPtr)))
319	if err := idx.c.error(rc); err != nil {
320		return Value{}, err
321	}
322	return Value{
323		c:      idx.c,
324		handle: util.Read32[ptr_t](idx.c.mod, valPtr),
325	}, nil
326}
327
328// Collation returns the name of the collation for a virtual table constraint.
329//
330// https://sqlite.org/c3ref/vtab_collation.html
331func (idx *IndexInfo) Collation(column int) string {
332	ptr := ptr_t(idx.c.call("sqlite3_vtab_collation", stk_t(idx.handle),
333		stk_t(column)))
334	return util.ReadString(idx.c.mod, ptr, _MAX_NAME)
335}
336
337// Distinct determines if a virtual table query is DISTINCT.
338//
339// https://sqlite.org/c3ref/vtab_distinct.html
340func (idx *IndexInfo) Distinct() int {
341	i := int32(idx.c.call("sqlite3_vtab_distinct", stk_t(idx.handle)))
342	return int(i)
343}
344
345// In identifies and handles IN constraints.
346//
347// https://sqlite.org/c3ref/vtab_in.html
348func (idx *IndexInfo) In(column, handle int) bool {
349	b := int32(idx.c.call("sqlite3_vtab_in", stk_t(idx.handle),
350		stk_t(column), stk_t(handle)))
351	return b != 0
352}
353
354func (idx *IndexInfo) load() {
355	// https://sqlite.org/c3ref/index_info.html
356	mod := idx.c.mod
357	ptr := idx.handle
358
359	nConstraint := util.Read32[int32](mod, ptr+0)
360	idx.Constraint = make([]IndexConstraint, nConstraint)
361	idx.ConstraintUsage = make([]IndexConstraintUsage, nConstraint)
362	idx.OrderBy = make([]IndexOrderBy, util.Read32[int32](mod, ptr+8))
363
364	constraintPtr := util.Read32[ptr_t](mod, ptr+4)
365	constraint := idx.Constraint
366	for i := range idx.Constraint {
367		constraint[i] = IndexConstraint{
368			Column: int(util.Read32[int32](mod, constraintPtr+0)),
369			Op:     util.Read[IndexConstraintOp](mod, constraintPtr+4),
370			Usable: util.Read[byte](mod, constraintPtr+5) != 0,
371		}
372		constraintPtr += 12
373	}
374
375	orderByPtr := util.Read32[ptr_t](mod, ptr+12)
376	orderBy := idx.OrderBy
377	for i := range orderBy {
378		orderBy[i] = IndexOrderBy{
379			Column: int(util.Read32[int32](mod, orderByPtr+0)),
380			Desc:   util.Read[byte](mod, orderByPtr+4) != 0,
381		}
382		orderByPtr += 8
383	}
384
385	idx.EstimatedCost = util.ReadFloat64(mod, ptr+40)
386	idx.EstimatedRows = util.Read64[int64](mod, ptr+48)
387	idx.ColumnsUsed = util.Read64[uint64](mod, ptr+64)
388}
389
390func (idx *IndexInfo) save() {
391	// https://sqlite.org/c3ref/index_info.html
392	mod := idx.c.mod
393	ptr := idx.handle
394
395	usagePtr := util.Read32[ptr_t](mod, ptr+16)
396	for _, usage := range idx.ConstraintUsage {
397		util.Write32(mod, usagePtr+0, int32(usage.ArgvIndex))
398		if usage.Omit {
399			util.Write(mod, usagePtr+4, int8(1))
400		}
401		usagePtr += 8
402	}
403
404	util.Write32(mod, ptr+20, int32(idx.IdxNum))
405	if idx.IdxStr != "" {
406		util.Write32(mod, ptr+24, idx.c.newString(idx.IdxStr))
407		util.WriteBool(mod, ptr+28, true) // needToFreeIdxStr
408	}
409	if idx.OrderByConsumed {
410		util.WriteBool(mod, ptr+32, true)
411	}
412	util.WriteFloat64(mod, ptr+40, idx.EstimatedCost)
413	util.Write64(mod, ptr+48, idx.EstimatedRows)
414	util.Write32(mod, ptr+56, idx.IdxFlags)
415}
416
417// IndexConstraintOp is a virtual table constraint operator code.
418//
419// https://sqlite.org/c3ref/c_index_constraint_eq.html
420type IndexConstraintOp uint8
421
422const (
423	INDEX_CONSTRAINT_EQ        IndexConstraintOp = 2
424	INDEX_CONSTRAINT_GT        IndexConstraintOp = 4
425	INDEX_CONSTRAINT_LE        IndexConstraintOp = 8
426	INDEX_CONSTRAINT_LT        IndexConstraintOp = 16
427	INDEX_CONSTRAINT_GE        IndexConstraintOp = 32
428	INDEX_CONSTRAINT_MATCH     IndexConstraintOp = 64
429	INDEX_CONSTRAINT_LIKE      IndexConstraintOp = 65
430	INDEX_CONSTRAINT_GLOB      IndexConstraintOp = 66
431	INDEX_CONSTRAINT_REGEXP    IndexConstraintOp = 67
432	INDEX_CONSTRAINT_NE        IndexConstraintOp = 68
433	INDEX_CONSTRAINT_ISNOT     IndexConstraintOp = 69
434	INDEX_CONSTRAINT_ISNOTNULL IndexConstraintOp = 70
435	INDEX_CONSTRAINT_ISNULL    IndexConstraintOp = 71
436	INDEX_CONSTRAINT_IS        IndexConstraintOp = 72
437	INDEX_CONSTRAINT_LIMIT     IndexConstraintOp = 73
438	INDEX_CONSTRAINT_OFFSET    IndexConstraintOp = 74
439	INDEX_CONSTRAINT_FUNCTION  IndexConstraintOp = 150
440)
441
442// IndexScanFlag is a virtual table scan flag.
443//
444// https://sqlite.org/c3ref/c_index_scan_unique.html
445type IndexScanFlag uint32
446
447const (
448	INDEX_SCAN_UNIQUE IndexScanFlag = 1
449)
450
451func vtabModuleCallback(i vtabConstructor) func(_ context.Context, _ api.Module, _ ptr_t, _ int32, _, _, _ ptr_t) res_t {
452	return func(ctx context.Context, mod api.Module, pMod ptr_t, nArg int32, pArg, ppVTab, pzErr ptr_t) res_t {
453		arg := make([]reflect.Value, 1+nArg)
454		arg[0] = reflect.ValueOf(ctx.Value(connKey{}))
455
456		for i := range nArg {
457			ptr := util.Read32[ptr_t](mod, pArg+ptr_t(i)*ptrlen)
458			arg[i+1] = reflect.ValueOf(util.ReadString(mod, ptr, _MAX_SQL_LENGTH))
459		}
460
461		module := vtabGetHandle(ctx, mod, pMod)
462		val := reflect.ValueOf(module).Index(int(i)).Call(arg)
463		err, _ := val[1].Interface().(error)
464		if err == nil {
465			vtabPutHandle(ctx, mod, ppVTab, val[0].Interface())
466		}
467
468		return vtabError(ctx, mod, pzErr, _PTR_ERROR, err)
469	}
470}
471
472func vtabDisconnectCallback(ctx context.Context, mod api.Module, pVTab ptr_t) res_t {
473	err := vtabDelHandle(ctx, mod, pVTab)
474	return vtabError(ctx, mod, 0, _PTR_ERROR, err)
475}
476
477func vtabDestroyCallback(ctx context.Context, mod api.Module, pVTab ptr_t) res_t {
478	vtab := vtabGetHandle(ctx, mod, pVTab).(VTabDestroyer)
479	err := errors.Join(vtab.Destroy(), vtabDelHandle(ctx, mod, pVTab))
480	return vtabError(ctx, mod, 0, _PTR_ERROR, err)
481}
482
483func vtabBestIndexCallback(ctx context.Context, mod api.Module, pVTab, pIdxInfo ptr_t) res_t {
484	var info IndexInfo
485	info.handle = pIdxInfo
486	info.c = ctx.Value(connKey{}).(*Conn)
487	info.load()
488
489	vtab := vtabGetHandle(ctx, mod, pVTab).(VTab)
490	err := vtab.BestIndex(&info)
491
492	info.save()
493	return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err)
494}
495
496func vtabUpdateCallback(ctx context.Context, mod api.Module, pVTab ptr_t, nArg int32, pArg, pRowID ptr_t) res_t {
497	db := ctx.Value(connKey{}).(*Conn)
498	args := callbackArgs(db, nArg, pArg)
499	defer returnArgs(args)
500
501	vtab := vtabGetHandle(ctx, mod, pVTab).(VTabUpdater)
502	rowID, err := vtab.Update(*args...)
503	if err == nil {
504		util.Write64(mod, pRowID, rowID)
505	}
506
507	return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err)
508}
509
510func vtabRenameCallback(ctx context.Context, mod api.Module, pVTab, zNew ptr_t) res_t {
511	vtab := vtabGetHandle(ctx, mod, pVTab).(VTabRenamer)
512	err := vtab.Rename(util.ReadString(mod, zNew, _MAX_NAME))
513	return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err)
514}
515
516func vtabFindFuncCallback(ctx context.Context, mod api.Module, pVTab ptr_t, nArg int32, zName, pxFunc ptr_t) int32 {
517	vtab := vtabGetHandle(ctx, mod, pVTab).(VTabOverloader)
518	f, op := vtab.FindFunction(int(nArg), util.ReadString(mod, zName, _MAX_NAME))
519	if op != 0 {
520		var wrapper ptr_t
521		wrapper = util.AddHandle(ctx, func(c Context, arg ...Value) {
522			defer util.DelHandle(ctx, wrapper)
523			f(c, arg...)
524		})
525		util.Write32(mod, pxFunc, wrapper)
526	}
527	return int32(op)
528}
529
530func vtabIntegrityCallback(ctx context.Context, mod api.Module, pVTab, zSchema, zTabName ptr_t, mFlags uint32, pzErr ptr_t) res_t {
531	vtab := vtabGetHandle(ctx, mod, pVTab).(VTabChecker)
532	schema := util.ReadString(mod, zSchema, _MAX_NAME)
533	table := util.ReadString(mod, zTabName, _MAX_NAME)
534	err := vtab.Integrity(schema, table, int(mFlags))
535	// xIntegrity should return OK - even if it finds problems in the content of the virtual table.
536	// https://sqlite.org/vtab.html#xintegrity
537	vtabError(ctx, mod, pzErr, _PTR_ERROR, err)
538	_, code := errorCode(err, _OK)
539	return code
540}
541
542func vtabBeginCallback(ctx context.Context, mod api.Module, pVTab ptr_t) res_t {
543	vtab := vtabGetHandle(ctx, mod, pVTab).(VTabTxn)
544	err := vtab.Begin()
545	return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err)
546}
547
548func vtabSyncCallback(ctx context.Context, mod api.Module, pVTab ptr_t) res_t {
549	vtab := vtabGetHandle(ctx, mod, pVTab).(VTabTxn)
550	err := vtab.Sync()
551	return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err)
552}
553
554func vtabCommitCallback(ctx context.Context, mod api.Module, pVTab ptr_t) res_t {
555	vtab := vtabGetHandle(ctx, mod, pVTab).(VTabTxn)
556	err := vtab.Commit()
557	return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err)
558}
559
560func vtabRollbackCallback(ctx context.Context, mod api.Module, pVTab ptr_t) res_t {
561	vtab := vtabGetHandle(ctx, mod, pVTab).(VTabTxn)
562	err := vtab.Rollback()
563	return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err)
564}
565
566func vtabSavepointCallback(ctx context.Context, mod api.Module, pVTab ptr_t, id int32) res_t {
567	vtab := vtabGetHandle(ctx, mod, pVTab).(VTabSavepointer)
568	err := vtab.Savepoint(int(id))
569	return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err)
570}
571
572func vtabReleaseCallback(ctx context.Context, mod api.Module, pVTab ptr_t, id int32) res_t {
573	vtab := vtabGetHandle(ctx, mod, pVTab).(VTabSavepointer)
574	err := vtab.Release(int(id))
575	return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err)
576}
577
578func vtabRollbackToCallback(ctx context.Context, mod api.Module, pVTab ptr_t, id int32) res_t {
579	vtab := vtabGetHandle(ctx, mod, pVTab).(VTabSavepointer)
580	err := vtab.RollbackTo(int(id))
581	return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err)
582}
583
584func cursorOpenCallback(ctx context.Context, mod api.Module, pVTab, ppCur ptr_t) res_t {
585	vtab := vtabGetHandle(ctx, mod, pVTab).(VTab)
586
587	cursor, err := vtab.Open()
588	if err == nil {
589		vtabPutHandle(ctx, mod, ppCur, cursor)
590	}
591
592	return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err)
593}
594
595func cursorCloseCallback(ctx context.Context, mod api.Module, pCur ptr_t) res_t {
596	err := vtabDelHandle(ctx, mod, pCur)
597	return vtabError(ctx, mod, 0, _VTAB_ERROR, err)
598}
599
600func cursorFilterCallback(ctx context.Context, mod api.Module, pCur ptr_t, idxNum int32, idxStr ptr_t, nArg int32, pArg ptr_t) res_t {
601	db := ctx.Value(connKey{}).(*Conn)
602	args := callbackArgs(db, nArg, pArg)
603	defer returnArgs(args)
604
605	var idxName string
606	if idxStr != 0 {
607		idxName = util.ReadString(mod, idxStr, _MAX_LENGTH)
608	}
609
610	cursor := vtabGetHandle(ctx, mod, pCur).(VTabCursor)
611	err := cursor.Filter(int(idxNum), idxName, *args...)
612	return vtabError(ctx, mod, pCur, _CURSOR_ERROR, err)
613}
614
615func cursorEOFCallback(ctx context.Context, mod api.Module, pCur ptr_t) int32 {
616	cursor := vtabGetHandle(ctx, mod, pCur).(VTabCursor)
617	if cursor.EOF() {
618		return 1
619	}
620	return 0
621}
622
623func cursorNextCallback(ctx context.Context, mod api.Module, pCur ptr_t) res_t {
624	cursor := vtabGetHandle(ctx, mod, pCur).(VTabCursor)
625	err := cursor.Next()
626	return vtabError(ctx, mod, pCur, _CURSOR_ERROR, err)
627}
628
629func cursorColumnCallback(ctx context.Context, mod api.Module, pCur, pCtx ptr_t, n int32) res_t {
630	cursor := vtabGetHandle(ctx, mod, pCur).(VTabCursor)
631	db := ctx.Value(connKey{}).(*Conn)
632	err := cursor.Column(Context{db, pCtx}, int(n))
633	return vtabError(ctx, mod, pCur, _CURSOR_ERROR, err)
634}
635
636func cursorRowIDCallback(ctx context.Context, mod api.Module, pCur, pRowID ptr_t) res_t {
637	cursor := vtabGetHandle(ctx, mod, pCur).(VTabCursor)
638
639	rowID, err := cursor.RowID()
640	if err == nil {
641		util.Write64(mod, pRowID, rowID)
642	}
643
644	return vtabError(ctx, mod, pCur, _CURSOR_ERROR, err)
645}
646
647const (
648	_PTR_ERROR = iota
649	_VTAB_ERROR
650	_CURSOR_ERROR
651)
652
653func vtabError(ctx context.Context, mod api.Module, ptr ptr_t, kind uint32, err error) res_t {
654	const zErrMsgOffset = 8
655	msg, code := errorCode(err, ERROR)
656	if msg != "" && ptr != 0 {
657		switch kind {
658		case _VTAB_ERROR:
659			ptr = ptr + zErrMsgOffset // zErrMsg
660		case _CURSOR_ERROR:
661			ptr = util.Read32[ptr_t](mod, ptr) + zErrMsgOffset // pVTab->zErrMsg
662		}
663		db := ctx.Value(connKey{}).(*Conn)
664		if ptr := util.Read32[ptr_t](mod, ptr); ptr != 0 {
665			db.free(ptr)
666		}
667		util.Write32(mod, ptr, db.newString(msg))
668	}
669	return code
670}
671
672func vtabGetHandle(ctx context.Context, mod api.Module, ptr ptr_t) any {
673	const handleOffset = 4
674	handle := util.Read32[ptr_t](mod, ptr-handleOffset)
675	return util.GetHandle(ctx, handle)
676}
677
678func vtabDelHandle(ctx context.Context, mod api.Module, ptr ptr_t) error {
679	const handleOffset = 4
680	handle := util.Read32[ptr_t](mod, ptr-handleOffset)
681	return util.DelHandle(ctx, handle)
682}
683
684func vtabPutHandle(ctx context.Context, mod api.Module, pptr ptr_t, val any) {
685	const handleOffset = 4
686	handle := util.AddHandle(ctx, val)
687	ptr := util.Read32[ptr_t](mod, pptr)
688	util.Write32(mod, ptr-handleOffset, handle)
689}