WAL and vacuum hooks.

This commit is contained in:
Nuno Cruces
2024-04-12 14:57:13 +01:00
parent 7260962aba
commit 1c3ad12434
17 changed files with 199 additions and 37 deletions

View File

@@ -82,7 +82,7 @@ func (c *Conn) SetAuthorizer(cb func(action AuthorizerActionCode, name3rd, name4
} }
func authorizerCallback(ctx context.Context, mod api.Module, pDB uint32, action AuthorizerActionCode, zName3rd, zName4th, zSchema, zNameInner uint32) AuthorizerReturnCode { func authorizerCallback(ctx context.Context, mod api.Module, pDB uint32, action AuthorizerActionCode, zName3rd, zName4th, zSchema, zNameInner uint32) (rc AuthorizerReturnCode) {
if c, ok := ctx.Value(connKey{}).(*Conn); ok && c.handle == pDB && c.authorizer != nil { if c, ok := ctx.Value(connKey{}).(*Conn); ok && c.handle == pDB && c.authorizer != nil {
var name3rd, name4th, schema, nameInner string var name3rd, name4th, schema, nameInner string
if zName3rd != 0 { if zName3rd != 0 {
@@ -97,7 +97,68 @@ func authorizerCallback(ctx context.Context, mod api.Module, pDB uint32, action
if zNameInner != 0 { if zNameInner != 0 {
nameInner = util.ReadString(mod, zNameInner, _MAX_NAME) nameInner = util.ReadString(mod, zNameInner, _MAX_NAME)
} }
return c.authorizer(action, name3rd, name4th, schema, nameInner) rc = c.authorizer(action, name3rd, name4th, schema, nameInner)
} }
return AUTH_OK return rc
}
// WalCheckpoint checkpoints a WAL database.
//
// https://sqlite.org/c3ref/wal_checkpoint_v2.html
func (c *Conn) WalCheckpoint(schema string, mode CheckpointMode) (nLog, nCkpt int, err error) {
defer c.arena.mark()()
nLogPtr := c.arena.new(ptrlen)
nCkptPtr := c.arena.new(ptrlen)
schemaPtr := c.arena.string(schema)
r := c.call("sqlite3_wal_checkpoint_v2",
uint64(c.handle), uint64(schemaPtr), uint64(mode),
uint64(nLogPtr), uint64(nCkptPtr))
nLog = int(int32(util.ReadUint32(c.mod, nLogPtr)))
nCkpt = int(int32(util.ReadUint32(c.mod, nCkptPtr)))
return nLog, nCkpt, c.error(r)
}
// WalAutoCheckpoint configures WAL auto-checkpoints.
//
// https://sqlite.org/c3ref/wal_autocheckpoint.html
func (c *Conn) WalAutoCheckpoint(pages int) error {
r := c.call("sqlite3_wal_autocheckpoint", uint64(c.handle), uint64(pages))
return c.error(r)
}
// WalHook registers a callback function to be invoked
// each time data is committed to a database in WAL mode.
//
// https://sqlite.org/c3ref/wal_hook.html
func (c *Conn) WalHook(cb func(db *Conn, schema string, pages int) error) {
var enable uint64
if cb != nil {
enable = 1
}
c.call("sqlite3_wal_hook_go", uint64(c.handle), enable)
c.wal = cb
}
func walCallback(ctx context.Context, mod api.Module, _, pDB, zSchema uint32, pages int32) (rc uint32) {
if c, ok := ctx.Value(connKey{}).(*Conn); ok && c.handle == pDB && c.wal != nil {
schema := util.ReadString(mod, zSchema, _MAX_NAME)
err := c.wal(c, schema, int(pages))
_, rc = errorCode(err, ERROR)
}
return rc
}
// AutoVacuumPages registers a autovacuum compaction amount callback.
//
// https://sqlite.org/c3ref/autovacuum_pages.html
func (c *Conn) AutoVacuumPages(cb func(schema string, dbPages, freePages, bytesPerPage uint) uint) error {
funcPtr := util.AddHandle(c.ctx, cb)
r := c.call("sqlite3_autovacuum_pages_go", uint64(c.handle), uint64(funcPtr))
return c.error(r)
}
func autoVacuumCallback(ctx context.Context, mod api.Module, pApp, zSchema, nDbPage, nFreePage, nBytePerPage uint32) uint32 {
fn := util.GetHandle(ctx, pApp).(func(schema string, dbPages, freePages, bytesPerPage uint) uint)
schema := util.ReadString(mod, zSchema, _MAX_NAME)
return uint32(fn(schema, uint(nDbPage), uint(nFreePage), uint(nBytePerPage)))
} }

21
conn.go
View File

@@ -29,6 +29,7 @@ type Conn struct {
update func(AuthorizerActionCode, string, string, int64) update func(AuthorizerActionCode, string, string, int64)
commit func() bool commit func() bool
rollback func() rollback func()
wal func(*Conn, string, int) error
arena arena arena arena
handle uint32 handle uint32
@@ -281,6 +282,12 @@ func (c *Conn) ReleaseMemory() error {
return c.error(r) return c.error(r)
} }
// GetInterrupt gets the context set with [Conn.SetInterrupt],
// or nil if none was set.
func (c *Conn) GetInterrupt() context.Context {
return c.interrupt
}
// SetInterrupt interrupts a long-running query when a context is done. // SetInterrupt interrupts a long-running query when a context is done.
// //
// Subsequent uses of the connection will return [INTERRUPT] // Subsequent uses of the connection will return [INTERRUPT]
@@ -325,13 +332,13 @@ func (c *Conn) checkInterrupt() {
} }
} }
func progressCallback(ctx context.Context, mod api.Module, pDB uint32) uint32 { func progressCallback(ctx context.Context, mod api.Module, pDB uint32) (interrupt uint32) {
if c, ok := ctx.Value(connKey{}).(*Conn); ok && c.handle == pDB && c.commit != nil { if c, ok := ctx.Value(connKey{}).(*Conn); ok && c.handle == pDB && c.commit != nil {
if c.interrupt != nil && c.interrupt.Err() != nil { if c.interrupt != nil && c.interrupt.Err() != nil {
return 1 interrupt = 1
} }
} }
return 0 return interrupt
} }
// BusyTimeout sets a busy timeout. // BusyTimeout sets a busy timeout.
@@ -359,13 +366,13 @@ func (c *Conn) BusyHandler(cb func(count int) (retry bool)) error {
return nil return nil
} }
func busyCallback(ctx context.Context, mod api.Module, pDB, count uint32) uint32 { func busyCallback(ctx context.Context, mod api.Module, pDB uint32, count int32) (retry uint32) {
if c, ok := ctx.Value(connKey{}).(*Conn); ok && c.handle == pDB && c.busy != nil { if c, ok := ctx.Value(connKey{}).(*Conn); ok && c.handle == pDB && c.busy != nil {
if retry := c.busy(int(count)); retry { if c.busy(int(count)) {
return 1 retry = 1
} }
} }
return 0 return retry
} }
func (c *Conn) error(rc uint64, sql ...string) error { func (c *Conn) error(rc uint64, sql ...string) error {

View File

@@ -305,6 +305,18 @@ const (
AUTH_IGNORE AuthorizerReturnCode = 2 /* Don't allow access, but don't generate an error */ AUTH_IGNORE AuthorizerReturnCode = 2 /* Don't allow access, but don't generate an error */
) )
// CheckpointMode are all the checkpoint mode values.
//
// https://sqlite.org/c3ref/c_checkpoint_full.html
type CheckpointMode uint32
const (
CHECKPOINT_PASSIVE CheckpointMode = 0 /* Do as much as possible w/o blocking */
CHECKPOINT_FULL CheckpointMode = 1 /* Wait for writers, then checkpoint */
CHECKPOINT_RESTART CheckpointMode = 2 /* Like FULL but wait for readers */
CHECKPOINT_TRUNCATE CheckpointMode = 3 /* Like RESTART but also truncate WAL */
)
// TxnState are the allowed return values from [Conn.TxnState]. // TxnState are the allowed return values from [Conn.TxnState].
// //
// https://sqlite.org/c3ref/c_txn_none.html // https://sqlite.org/c3ref/c_txn_none.html

View File

@@ -1,6 +1,6 @@
# Embeddable Wasm build of SQLite # Embeddable Wasm build of SQLite
This folder includes an embeddable Wasm build of SQLite 3.45.1 for use with This folder includes an embeddable Wasm build of SQLite 3.45.2 for use with
[`github.com/ncruces/go-sqlite3`](https://pkg.go.dev/github.com/ncruces/go-sqlite3). [`github.com/ncruces/go-sqlite3`](https://pkg.go.dev/github.com/ncruces/go-sqlite3).
The following optional features are compiled in: The following optional features are compiled in:

View File

@@ -1,8 +1,9 @@
aligned_alloc
free free
malloc malloc
malloc_destructor malloc_destructor
aligned_alloc
sqlite3_anycollseq_init sqlite3_anycollseq_init
sqlite3_autovacuum_pages_go
sqlite3_backup_finish sqlite3_backup_finish
sqlite3_backup_init sqlite3_backup_init
sqlite3_backup_pagecount sqlite3_backup_pagecount
@@ -115,4 +116,7 @@ sqlite3_vtab_in_first
sqlite3_vtab_in_next sqlite3_vtab_in_next
sqlite3_vtab_nochange sqlite3_vtab_nochange
sqlite3_vtab_on_conflict sqlite3_vtab_on_conflict
sqlite3_vtab_rhs_value sqlite3_vtab_rhs_value
sqlite3_wal_autocheckpoint
sqlite3_wal_checkpoint_v2
sqlite3_wal_hook_go

Binary file not shown.

View File

@@ -334,7 +334,7 @@ type _Index struct {
// GetIndexes return Indexes []gorm.Index and execErr error, // GetIndexes return Indexes []gorm.Index and execErr error,
// See the [doc] // See the [doc]
// //
// [doc]: https://www.sqlite.org/pragma.html#pragma_index_list // [doc]: https://sqlite.org/pragma.html#pragma_index_list
func (m _Migrator) GetIndexes(value interface{}) ([]gorm.Index, error) { func (m _Migrator) GetIndexes(value interface{}) ([]gorm.Index, error) {
indexes := make([]gorm.Index, 0) indexes := make([]gorm.Index, 0)
err := m.RunWithValue(value, func(stmt *gorm.Statement) error { err := m.RunWithValue(value, func(stmt *gorm.Statement) error {

View File

@@ -294,6 +294,8 @@ func exportCallbacks(env wazero.HostModuleBuilder) wazero.HostModuleBuilder {
util.ExportFuncII(env, "go_commit_hook", commitCallback) util.ExportFuncII(env, "go_commit_hook", commitCallback)
util.ExportFuncVI(env, "go_rollback_hook", rollbackCallback) util.ExportFuncVI(env, "go_rollback_hook", rollbackCallback)
util.ExportFuncVIIIIJ(env, "go_update_hook", updateCallback) util.ExportFuncVIIIIJ(env, "go_update_hook", updateCallback)
util.ExportFuncIIIII(env, "go_wal_hook", walCallback)
util.ExportFuncIIIIII(env, "go_autovacuum_pages", autoVacuumCallback)
util.ExportFuncIIIIIII(env, "go_authorizer", authorizerCallback) util.ExportFuncIIIIIII(env, "go_authorizer", authorizerCallback)
util.ExportFuncVIII(env, "go_log", logCallback) util.ExportFuncVIII(env, "go_log", logCallback)
util.ExportFuncVI(env, "go_destroy", destroyCallback) util.ExportFuncVI(env, "go_destroy", destroyCallback)

View File

@@ -8,12 +8,16 @@ int go_busy_handler(void *, int);
int go_commit_hook(void *); int go_commit_hook(void *);
void go_rollback_hook(void *); void go_rollback_hook(void *);
void go_update_hook(void *, int, char const *, char const *, sqlite3_int64); void go_update_hook(void *, int, char const *, char const *, sqlite3_int64);
int go_wal_hook(void *, sqlite3 *, const char *, int);
int go_authorizer(void *, int, const char *, const char *, const char *, int go_authorizer(void *, int, const char *, const char *, const char *,
const char *); const char *);
void go_log(void *, int, const char *); void go_log(void *, int, const char *);
unsigned int go_autovacuum_pages(void *, const char *, unsigned int,
unsigned int, unsigned int);
void sqlite3_progress_handler_go(sqlite3 *db, int n) { void sqlite3_progress_handler_go(sqlite3 *db, int n) {
sqlite3_progress_handler(db, n, go_progress_handler, /*arg=*/db); sqlite3_progress_handler(db, n, go_progress_handler, /*arg=*/db);
} }
@@ -34,6 +38,10 @@ void sqlite3_update_hook_go(sqlite3 *db, bool enable) {
sqlite3_update_hook(db, enable ? go_update_hook : NULL, /*arg=*/db); sqlite3_update_hook(db, enable ? go_update_hook : NULL, /*arg=*/db);
} }
void sqlite3_wal_hook_go(sqlite3 *db, bool enable) {
sqlite3_wal_hook(db, enable ? go_wal_hook : NULL, /*arg=*/NULL);
}
int sqlite3_set_authorizer_go(sqlite3 *db, bool enable) { int sqlite3_set_authorizer_go(sqlite3 *db, bool enable) {
return sqlite3_set_authorizer(db, enable ? go_authorizer : NULL, /*arg=*/db); return sqlite3_set_authorizer(db, enable ? go_authorizer : NULL, /*arg=*/db);
} }
@@ -41,4 +49,10 @@ int sqlite3_set_authorizer_go(sqlite3 *db, bool enable) {
int sqlite3_config_log_go(bool enable) { int sqlite3_config_log_go(bool enable) {
return sqlite3_config(SQLITE_CONFIG_LOG, enable ? go_log : NULL, return sqlite3_config(SQLITE_CONFIG_LOG, enable ? go_log : NULL,
/*arg=*/NULL); /*arg=*/NULL);
}
int sqlite3_autovacuum_pages_go(sqlite3 *db, go_handle app) {
int rc = sqlite3_autovacuum_pages(db, go_autovacuum_pages, app, go_destroy);
if (rc) go_destroy(app);
return rc;
} }

View File

@@ -120,7 +120,7 @@ func (s *Stmt) Status(op StmtStatus, reset bool) int {
} }
r := s.c.call("sqlite3_stmt_status", uint64(s.handle), r := s.c.call("sqlite3_stmt_status", uint64(s.handle),
uint64(op), i) uint64(op), i)
return int(r) return int(int32(r))
} }
// ClearBindings resets all bindings on the prepared statement. // ClearBindings resets all bindings on the prepared statement.
@@ -137,7 +137,7 @@ func (s *Stmt) ClearBindings() error {
func (s *Stmt) BindCount() int { func (s *Stmt) BindCount() int {
r := s.c.call("sqlite3_bind_parameter_count", r := s.c.call("sqlite3_bind_parameter_count",
uint64(s.handle)) uint64(s.handle))
return int(r) return int(int32(r))
} }
// BindIndex returns the index of a parameter in the prepared statement // BindIndex returns the index of a parameter in the prepared statement
@@ -149,7 +149,7 @@ func (s *Stmt) BindIndex(name string) int {
namePtr := s.c.arena.string(name) namePtr := s.c.arena.string(name)
r := s.c.call("sqlite3_bind_parameter_index", r := s.c.call("sqlite3_bind_parameter_index",
uint64(s.handle), uint64(namePtr)) uint64(s.handle), uint64(namePtr))
return int(r) return int(int32(r))
} }
// BindName returns the name of a parameter in the prepared statement. // BindName returns the name of a parameter in the prepared statement.
@@ -357,7 +357,7 @@ func (s *Stmt) BindValue(param int, value Value) error {
func (s *Stmt) ColumnCount() int { func (s *Stmt) ColumnCount() int {
r := s.c.call("sqlite3_column_count", r := s.c.call("sqlite3_column_count",
uint64(s.handle)) uint64(s.handle))
return int(r) return int(int32(r))
} }
// ColumnName returns the name of the result column. // ColumnName returns the name of the result column.

View File

@@ -11,6 +11,7 @@ import (
"github.com/ncruces/go-sqlite3" "github.com/ncruces/go-sqlite3"
_ "github.com/ncruces/go-sqlite3/embed" _ "github.com/ncruces/go-sqlite3/embed"
_ "github.com/ncruces/go-sqlite3/vfs/memdb"
) )
func TestConn_Open_dir(t *testing.T) { func TestConn_Open_dir(t *testing.T) {
@@ -449,3 +450,35 @@ func TestConn_DBName(t *testing.T) {
t.Errorf("got %s", name) t.Errorf("got %s", name)
} }
} }
func TestConn_AutoVacuumPages(t *testing.T) {
t.Parallel()
db, err := sqlite3.Open("file:test.db?vfs=memdb&_pragma=auto_vacuum(FULL)")
if err != nil {
t.Fatal(err)
}
defer db.Close()
err = db.AutoVacuumPages(func(schema string, dbPages, freePages, bytesPerPage uint) uint {
return freePages
})
if err != nil {
t.Fatal(err)
}
err = db.Exec(`CREATE TABLE test (col)`)
if err != nil {
t.Fatal(err)
}
err = db.Exec(`INSERT INTO test VALUES (zeroblob(1024*1024))`)
if err != nil {
t.Fatal(err)
}
err = db.Exec(`DROP TABLE test`)
if err != nil {
t.Fatal(err)
}
}

View File

@@ -31,3 +31,34 @@ func TestWAL_enter_exit(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
} }
func TestConn_WalCheckpoint(t *testing.T) {
t.Parallel()
file := filepath.Join(t.TempDir(), "test.db")
db, err := sqlite3.Open(file)
if err != nil {
t.Fatal(err)
}
defer db.Close()
err = db.WalAutoCheckpoint(1000)
if err != nil {
t.Fatal(err)
}
db.WalHook(func(db *sqlite3.Conn, schema string, pages int) error {
log, ckpt, err := db.WalCheckpoint(schema, sqlite3.CHECKPOINT_FULL)
t.Log(log, ckpt, err)
return err
})
err = db.Exec(`
PRAGMA journal_mode=WAL;
CREATE TABLE test (col);
`)
if err != nil {
t.Fatal(err)
}
}

10
txn.go
View File

@@ -257,7 +257,7 @@ func (c *Conn) RollbackHook(cb func()) {
c.rollback = cb c.rollback = cb
} }
// RollbackHook registers a callback function to be invoked // UpdateHook registers a callback function to be invoked
// whenever a row is updated, inserted or deleted in a rowid table. // whenever a row is updated, inserted or deleted in a rowid table.
// //
// https://sqlite.org/c3ref/update_hook.html // https://sqlite.org/c3ref/update_hook.html
@@ -270,13 +270,13 @@ func (c *Conn) UpdateHook(cb func(action AuthorizerActionCode, schema, table str
c.update = cb c.update = cb
} }
func commitCallback(ctx context.Context, mod api.Module, pDB uint32) uint32 { func commitCallback(ctx context.Context, mod api.Module, pDB uint32) (rollback uint32) {
if c, ok := ctx.Value(connKey{}).(*Conn); ok && c.handle == pDB && c.commit != nil { if c, ok := ctx.Value(connKey{}).(*Conn); ok && c.handle == pDB && c.commit != nil {
if ok := c.commit(); !ok { if !c.commit() {
return 1 rollback = 1
} }
} }
return 0 return rollback
} }
func rollbackCallback(ctx context.Context, mod api.Module, pDB uint32) { func rollbackCallback(ctx context.Context, mod api.Module, pDB uint32) {

View File

@@ -117,11 +117,9 @@ func (f *vfsFile) Unlock(lock LockLevel) error {
switch lock { switch lock {
case LOCK_SHARED: case LOCK_SHARED:
if rc := osDowngradeLock(f.File, f.lock); rc != _OK { rc := osDowngradeLock(f.File, f.lock)
return rc
}
f.lock = LOCK_SHARED f.lock = LOCK_SHARED
return nil return rc
case LOCK_NONE: case LOCK_NONE:
rc := osReleaseLock(f.File, f.lock) rc := osReleaseLock(f.File, f.lock)

View File

@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:523b3640c6de9accf3f6b01e7da2098faba12eec0168b67a78c56fef0716c7ae oid sha256:40b56dfd62584e32da7191c664ecfdd28dca419de05b9e46f4b36f7919a36d3e
size 469261 size 468861

View File

@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:855720cce2881c98d09c15eddf9cab0d5974a2a82f7f67987a28b97414629345 oid sha256:6be5a0accea1a62ecdaf2055388f02ab52ea13323b90c36dd5833d8a4717c7c1
size 483463 size 483476

12
vtab.go
View File

@@ -489,7 +489,7 @@ func vtabRenameCallback(ctx context.Context, mod api.Module, pVTab, zNew uint32)
return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err) return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err)
} }
func vtabFindFuncCallback(ctx context.Context, mod api.Module, pVTab, nArg, zName, pxFunc uint32) uint32 { func vtabFindFuncCallback(ctx context.Context, mod api.Module, pVTab uint32, nArg int32, zName, pxFunc uint32) uint32 {
vtab := vtabGetHandle(ctx, mod, pVTab).(VTabOverloader) vtab := vtabGetHandle(ctx, mod, pVTab).(VTabOverloader)
f, op := vtab.FindFunction(int(nArg), util.ReadString(mod, zName, _MAX_NAME)) f, op := vtab.FindFunction(int(nArg), util.ReadString(mod, zName, _MAX_NAME))
if op != 0 { if op != 0 {
@@ -539,19 +539,19 @@ func vtabRollbackCallback(ctx context.Context, mod api.Module, pVTab uint32) uin
return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err) return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err)
} }
func vtabSavepointCallback(ctx context.Context, mod api.Module, pVTab, id uint32) uint32 { func vtabSavepointCallback(ctx context.Context, mod api.Module, pVTab uint32, id int32) uint32 {
vtab := vtabGetHandle(ctx, mod, pVTab).(VTabSavepointer) vtab := vtabGetHandle(ctx, mod, pVTab).(VTabSavepointer)
err := vtab.Savepoint(int(id)) err := vtab.Savepoint(int(id))
return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err) return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err)
} }
func vtabReleaseCallback(ctx context.Context, mod api.Module, pVTab, id uint32) uint32 { func vtabReleaseCallback(ctx context.Context, mod api.Module, pVTab uint32, id int32) uint32 {
vtab := vtabGetHandle(ctx, mod, pVTab).(VTabSavepointer) vtab := vtabGetHandle(ctx, mod, pVTab).(VTabSavepointer)
err := vtab.Release(int(id)) err := vtab.Release(int(id))
return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err) return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err)
} }
func vtabRollbackToCallback(ctx context.Context, mod api.Module, pVTab, id uint32) uint32 { func vtabRollbackToCallback(ctx context.Context, mod api.Module, pVTab uint32, id int32) uint32 {
vtab := vtabGetHandle(ctx, mod, pVTab).(VTabSavepointer) vtab := vtabGetHandle(ctx, mod, pVTab).(VTabSavepointer)
err := vtab.RollbackTo(int(id)) err := vtab.RollbackTo(int(id))
return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err) return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err)
@@ -573,7 +573,7 @@ func cursorCloseCallback(ctx context.Context, mod api.Module, pCur uint32) uint3
return vtabError(ctx, mod, 0, _VTAB_ERROR, err) return vtabError(ctx, mod, 0, _VTAB_ERROR, err)
} }
func cursorFilterCallback(ctx context.Context, mod api.Module, pCur, idxNum, idxStr, nArg, pArg uint32) uint32 { func cursorFilterCallback(ctx context.Context, mod api.Module, pCur uint32, idxNum int32, idxStr, nArg, pArg uint32) uint32 {
cursor := vtabGetHandle(ctx, mod, pCur).(VTabCursor) cursor := vtabGetHandle(ctx, mod, pCur).(VTabCursor)
db := ctx.Value(connKey{}).(*Conn) db := ctx.Value(connKey{}).(*Conn)
args := make([]Value, nArg) args := make([]Value, nArg)
@@ -600,7 +600,7 @@ func cursorNextCallback(ctx context.Context, mod api.Module, pCur uint32) uint32
return vtabError(ctx, mod, pCur, _CURSOR_ERROR, err) return vtabError(ctx, mod, pCur, _CURSOR_ERROR, err)
} }
func cursorColumnCallback(ctx context.Context, mod api.Module, pCur, pCtx, n uint32) uint32 { func cursorColumnCallback(ctx context.Context, mod api.Module, pCur, pCtx uint32, n int32) uint32 {
cursor := vtabGetHandle(ctx, mod, pCur).(VTabCursor) cursor := vtabGetHandle(ctx, mod, pCur).(VTabCursor)
db := ctx.Value(connKey{}).(*Conn) db := ctx.Value(connKey{}).(*Conn)
err := cursor.Column(&Context{db, pCtx}, int(n)) err := cursor.Column(&Context{db, pCtx}, int(n))