mirror of
https://github.com/ncruces/go-sqlite3.git
synced 2026-01-11 21:49:13 +00:00
Virtual tables.
This commit is contained in:
16
README.md
16
README.md
@@ -81,21 +81,19 @@ on Linux, macOS, Windows and FreeBSD.
|
||||
Performance is tested by running
|
||||
[speedtest1](https://github.com/sqlite/sqlite/blob/master/test/speedtest1.c).
|
||||
|
||||
### Roadmap
|
||||
### Features
|
||||
|
||||
- [ ] advanced SQLite features
|
||||
- [x] custom functions
|
||||
- [x] nested transactions
|
||||
- [x] advanced SQLite features
|
||||
- [x] incremental BLOB I/O
|
||||
- [x] nested transactions
|
||||
- [x] custom functions
|
||||
- [x] virtual tables
|
||||
- [x] online backup
|
||||
- [x] JSON support
|
||||
- [ ] virtual tables
|
||||
- [ ] session extension
|
||||
- [ ] custom VFSes
|
||||
- [x] custom VFSes
|
||||
- [x] custom VFS API
|
||||
- [x] in-memory VFS
|
||||
- [x] read-only VFS, wrapping an [`io.ReaderAt`](https://pkg.go.dev/io#ReaderAt)
|
||||
- [ ] cloud-based VFS, based on [Cloud Backed SQLite](https://sqlite.org/cloudsqlite/doc/trunk/www/index.wiki)
|
||||
- [x] read-only VFS
|
||||
|
||||
### Alternatives
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ func openBlob(ctx sqlite3.Context, arg ...sqlite3.Value) {
|
||||
if ok {
|
||||
err = blob.Reopen(row)
|
||||
if errors.Is(err, sqlite3.MISUSE) {
|
||||
// Blob was closed (db, table or column changed).
|
||||
// Blob was closed (db, table, column or write changed).
|
||||
ok = false
|
||||
}
|
||||
}
|
||||
@@ -58,10 +58,11 @@ func openBlob(ctx sqlite3.Context, arg ...sqlite3.Value) {
|
||||
return
|
||||
}
|
||||
|
||||
// This ensures the blob is closed if db, table or column change.
|
||||
// This ensures the blob is closed if db, table, column or write change.
|
||||
ctx.SetAuxData(0, blob)
|
||||
ctx.SetAuxData(1, blob)
|
||||
ctx.SetAuxData(2, blob)
|
||||
ctx.SetAuxData(4, blob)
|
||||
}
|
||||
|
||||
// OpenCallback is the type for the blob_open callback.
|
||||
|
||||
20
sqlite.go
20
sqlite.go
@@ -428,16 +428,16 @@ func exportCallbacks(env wazero.HostModuleBuilder) wazero.HostModuleBuilder {
|
||||
util.ExportFuncII(env, "go_vtab_disconnect", vtabDisconnectCallback)
|
||||
util.ExportFuncII(env, "go_vtab_destroy", vtabDestroyCallback)
|
||||
util.ExportFuncIII(env, "go_vtab_best_index", vtabBestIndexCallback)
|
||||
util.ExportFuncIIIII(env, "go_vtab_update", vtabCallbackIIII)
|
||||
util.ExportFuncIII(env, "go_vtab_rename", vtabCallbackII)
|
||||
util.ExportFuncIIIII(env, "go_vtab_find_function", vtabCallbackIIII)
|
||||
util.ExportFuncII(env, "go_vtab_begin", vtabCallbackI)
|
||||
util.ExportFuncII(env, "go_vtab_sync", vtabCallbackI)
|
||||
util.ExportFuncII(env, "go_vtab_commit", vtabCallbackI)
|
||||
util.ExportFuncII(env, "go_vtab_rollback", vtabCallbackI)
|
||||
util.ExportFuncIII(env, "go_vtab_savepoint", vtabCallbackII)
|
||||
util.ExportFuncIII(env, "go_vtab_release", vtabCallbackII)
|
||||
util.ExportFuncIII(env, "go_vtab_rollback_to", vtabCallbackII)
|
||||
util.ExportFuncIIIII(env, "go_vtab_update", vtabUpdateCallback)
|
||||
util.ExportFuncIII(env, "go_vtab_rename", vtabRenameCallback)
|
||||
util.ExportFuncIIIII(env, "go_vtab_find_function", vtabFindFuncCallback)
|
||||
util.ExportFuncII(env, "go_vtab_begin", vtabBeginCallback)
|
||||
util.ExportFuncII(env, "go_vtab_sync", vtabSyncCallback)
|
||||
util.ExportFuncII(env, "go_vtab_commit", vtabCommitCallback)
|
||||
util.ExportFuncII(env, "go_vtab_rollback", vtabRollbackCallback)
|
||||
util.ExportFuncIII(env, "go_vtab_savepoint", vtabSavepointCallback)
|
||||
util.ExportFuncIII(env, "go_vtab_release", vtabReleaseCallback)
|
||||
util.ExportFuncIII(env, "go_vtab_rollback_to", vtabRollbackToCallback)
|
||||
util.ExportFuncIIIIII(env, "go_vtab_integrity", vtabIntegrityCallback)
|
||||
util.ExportFuncIII(env, "go_cur_open", cursorOpenCallback)
|
||||
util.ExportFuncII(env, "go_cur_close", cursorCloseCallback)
|
||||
|
||||
@@ -102,7 +102,7 @@ func Test_config01(t *testing.T) {
|
||||
cfg := config(ctx).WithArgs("mptest", name, "config01.test")
|
||||
mod, err := rt.InstantiateModule(ctx, module, cfg)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
t.Fatal(err)
|
||||
}
|
||||
mod.Close(ctx)
|
||||
}
|
||||
@@ -120,7 +120,7 @@ func Test_config02(t *testing.T) {
|
||||
cfg := config(ctx).WithArgs("mptest", name, "config02.test")
|
||||
mod, err := rt.InstantiateModule(ctx, module, cfg)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
t.Fatal(err)
|
||||
}
|
||||
mod.Close(ctx)
|
||||
}
|
||||
@@ -135,7 +135,7 @@ func Test_crash01(t *testing.T) {
|
||||
cfg := config(ctx).WithArgs("mptest", name, "crash01.test")
|
||||
mod, err := rt.InstantiateModule(ctx, module, cfg)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
t.Fatal(err)
|
||||
}
|
||||
mod.Close(ctx)
|
||||
}
|
||||
@@ -150,7 +150,7 @@ func Test_multiwrite01(t *testing.T) {
|
||||
cfg := config(ctx).WithArgs("mptest", name, "multiwrite01.test")
|
||||
mod, err := rt.InstantiateModule(ctx, module, cfg)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
t.Fatal(err)
|
||||
}
|
||||
mod.Close(ctx)
|
||||
}
|
||||
@@ -163,7 +163,7 @@ func Test_config01_memory(t *testing.T) {
|
||||
"--timeout", "1000")
|
||||
mod, err := rt.InstantiateModule(ctx, module, cfg)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
t.Fatal(err)
|
||||
}
|
||||
mod.Close(ctx)
|
||||
}
|
||||
@@ -180,7 +180,7 @@ func Test_multiwrite01_memory(t *testing.T) {
|
||||
"--timeout", "1000")
|
||||
mod, err := rt.InstantiateModule(ctx, module, cfg)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
t.Fatal(err)
|
||||
}
|
||||
mod.Close(ctx)
|
||||
}
|
||||
|
||||
@@ -92,7 +92,7 @@ func Benchmark_speedtest1(b *testing.B) {
|
||||
WithRandSource(rand.Reader)
|
||||
mod, err := rt.InstantiateModule(ctx, module, cfg)
|
||||
if err != nil {
|
||||
b.Error(err)
|
||||
b.Fatal(err)
|
||||
}
|
||||
mod.Close(ctx)
|
||||
}
|
||||
|
||||
115
vtab.go
115
vtab.go
@@ -133,6 +133,11 @@ type VTabOverloader interface {
|
||||
|
||||
// A VTabChecker allows a virtual table to report errors
|
||||
// to the PRAGMA integrity_check PRAGMA quick_check commands.
|
||||
//
|
||||
// Integrity should return an error if it finds problems in the content of the virtual table,
|
||||
// but should avoid returning a (wrapped) [Error], [ErrorCode] or [ExtendedErrorCode],
|
||||
// as those indicate the Integrity method itself encountered problems
|
||||
// while trying to evaluate the virtual table content.
|
||||
type VTabChecker interface {
|
||||
VTab
|
||||
// https://sqlite.org/vtab.html#xintegrity
|
||||
@@ -167,11 +172,11 @@ type VTabSavepointer interface {
|
||||
// A VTabCursor describes cursors that point
|
||||
// into the virtual table and are used
|
||||
// to loop through the virtual table.
|
||||
// A VTabCursor may optionally implement
|
||||
// [io.Closer] to free resources.
|
||||
//
|
||||
// http://sqlite.org/c3ref/vtab_cursor.html
|
||||
type VTabCursor interface {
|
||||
// https://sqlite.org/vtab.html#xclose
|
||||
Close() error
|
||||
// https://sqlite.org/vtab.html#xfilter
|
||||
Filter(idxNum int, idxStr string, arg ...Value) error
|
||||
// https://sqlite.org/vtab.html#xnext
|
||||
@@ -308,7 +313,7 @@ const (
|
||||
|
||||
// IndexScanFlag is a virtual table scan flag.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/c_index_scan_unique.html
|
||||
// https://sqlite.org/c3ref/c_index_scan_unique.html
|
||||
type IndexScanFlag uint32
|
||||
|
||||
const (
|
||||
@@ -334,20 +339,22 @@ func vtabReflectCallback(name string) func(_ context.Context, _ api.Module, _, _
|
||||
vtabPutHandle(ctx, mod, ppVTab, res[0].Interface())
|
||||
}
|
||||
|
||||
return vtabError(ctx, mod, pzErr, _MODULE_ERROR, err)
|
||||
return vtabError(ctx, mod, pzErr, _PTR_ERROR, err)
|
||||
}
|
||||
}
|
||||
|
||||
func vtabDisconnectCallback(ctx context.Context, mod api.Module, pVTab uint32) uint32 {
|
||||
vtab := vtabGetHandle(ctx, mod, pVTab).(VTab)
|
||||
err := vtab.Disconnect()
|
||||
return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err)
|
||||
vtabDelHandle(ctx, mod, pVTab)
|
||||
return errorCode(err, ERROR)
|
||||
}
|
||||
|
||||
func vtabDestroyCallback(ctx context.Context, mod api.Module, pVTab uint32) uint32 {
|
||||
vtab := vtabGetHandle(ctx, mod, pVTab).(VTabDestroyer)
|
||||
err := vtab.Destroy()
|
||||
return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err)
|
||||
vtabDelHandle(ctx, mod, pVTab)
|
||||
return errorCode(err, ERROR)
|
||||
}
|
||||
|
||||
func vtabBestIndexCallback(ctx context.Context, mod api.Module, pVTab, pIdxInfo uint32) uint32 {
|
||||
@@ -361,20 +368,89 @@ func vtabBestIndexCallback(ctx context.Context, mod api.Module, pVTab, pIdxInfo
|
||||
return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err)
|
||||
}
|
||||
|
||||
func vtabCallbackI(ctx context.Context, mod api.Module, pVTab uint32) uint32 {
|
||||
return uint32(INTERNAL)
|
||||
func vtabUpdateCallback(ctx context.Context, mod api.Module, pVTab, argc, argv, pRowID uint32) uint32 {
|
||||
vtab := vtabGetHandle(ctx, mod, pVTab).(VTabUpdater)
|
||||
|
||||
db := ctx.Value(connKey{}).(*Conn)
|
||||
args := callbackArgs(db, argc, argv)
|
||||
rowID, err := vtab.Update(args...)
|
||||
if err == nil {
|
||||
util.WriteUint64(mod, pRowID, uint64(rowID))
|
||||
}
|
||||
|
||||
return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err)
|
||||
}
|
||||
|
||||
func vtabCallbackII(ctx context.Context, mod api.Module, pVTab, _ uint32) uint32 {
|
||||
return uint32(INTERNAL)
|
||||
func vtabRenameCallback(ctx context.Context, mod api.Module, pVTab, zNew uint32) uint32 {
|
||||
vtab := vtabGetHandle(ctx, mod, pVTab).(VTabRenamer)
|
||||
err := vtab.Rename(util.ReadString(mod, zNew, _MAX_STRING))
|
||||
return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err)
|
||||
}
|
||||
|
||||
func vtabCallbackIIII(ctx context.Context, mod api.Module, pVTab, _, _, _ uint32) uint32 {
|
||||
return uint32(INTERNAL)
|
||||
func vtabFindFuncCallback(ctx context.Context, mod api.Module, pVTab, nArg, zName, pxFunc uint32) uint32 {
|
||||
vtab := vtabGetHandle(ctx, mod, pVTab).(VTabOverloader)
|
||||
fn, op := vtab.FindFunction(int(nArg), util.ReadString(mod, zName, _MAX_STRING))
|
||||
if fn != nil {
|
||||
handle := util.AddHandle(ctx, fn)
|
||||
util.WriteUint32(mod, pxFunc, handle)
|
||||
if op == 0 {
|
||||
op = 1
|
||||
}
|
||||
}
|
||||
return uint32(op)
|
||||
}
|
||||
|
||||
func vtabIntegrityCallback(ctx context.Context, mod api.Module, pVTab, zSchema, zTabName, mFlags, pzErr uint32) uint32 {
|
||||
return uint32(INTERNAL)
|
||||
vtab := vtabGetHandle(ctx, mod, pVTab).(VTabChecker)
|
||||
schema := util.ReadString(mod, zSchema, _MAX_STRING)
|
||||
table := util.ReadString(mod, zTabName, _MAX_STRING)
|
||||
err := vtab.Integrity(schema, table, int(mFlags))
|
||||
// xIntegrity should return OK - even if it finds problems in the content of the virtual table.
|
||||
// https://sqlite.org/vtab.html#xintegrity
|
||||
vtabError(ctx, mod, pzErr, _PTR_ERROR, err)
|
||||
return errorCode(err, _OK)
|
||||
}
|
||||
|
||||
func vtabBeginCallback(ctx context.Context, mod api.Module, pVTab uint32) uint32 {
|
||||
vtab := vtabGetHandle(ctx, mod, pVTab).(VTabTx)
|
||||
err := vtab.Begin()
|
||||
return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err)
|
||||
}
|
||||
|
||||
func vtabSyncCallback(ctx context.Context, mod api.Module, pVTab uint32) uint32 {
|
||||
vtab := vtabGetHandle(ctx, mod, pVTab).(VTabTx)
|
||||
err := vtab.Sync()
|
||||
return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err)
|
||||
}
|
||||
|
||||
func vtabCommitCallback(ctx context.Context, mod api.Module, pVTab uint32) uint32 {
|
||||
vtab := vtabGetHandle(ctx, mod, pVTab).(VTabTx)
|
||||
err := vtab.Commit()
|
||||
return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err)
|
||||
}
|
||||
|
||||
func vtabRollbackCallback(ctx context.Context, mod api.Module, pVTab uint32) uint32 {
|
||||
vtab := vtabGetHandle(ctx, mod, pVTab).(VTabTx)
|
||||
err := vtab.Rollback()
|
||||
return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err)
|
||||
}
|
||||
|
||||
func vtabSavepointCallback(ctx context.Context, mod api.Module, pVTab, id uint32) uint32 {
|
||||
vtab := vtabGetHandle(ctx, mod, pVTab).(VTabSavepointer)
|
||||
err := vtab.Savepoint(int(id))
|
||||
return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err)
|
||||
}
|
||||
|
||||
func vtabReleaseCallback(ctx context.Context, mod api.Module, pVTab, id uint32) uint32 {
|
||||
vtab := vtabGetHandle(ctx, mod, pVTab).(VTabSavepointer)
|
||||
err := vtab.Release(int(id))
|
||||
return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err)
|
||||
}
|
||||
|
||||
func vtabRollbackToCallback(ctx context.Context, mod api.Module, pVTab, id uint32) uint32 {
|
||||
vtab := vtabGetHandle(ctx, mod, pVTab).(VTabSavepointer)
|
||||
err := vtab.RollbackTo(int(id))
|
||||
return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err)
|
||||
}
|
||||
|
||||
func cursorOpenCallback(ctx context.Context, mod api.Module, pVTab, ppCur uint32) uint32 {
|
||||
@@ -389,9 +465,8 @@ func cursorOpenCallback(ctx context.Context, mod api.Module, pVTab, ppCur uint32
|
||||
}
|
||||
|
||||
func cursorCloseCallback(ctx context.Context, mod api.Module, pCur uint32) uint32 {
|
||||
cursor := vtabGetHandle(ctx, mod, pCur).(VTabCursor)
|
||||
err := cursor.Close()
|
||||
return vtabError(ctx, mod, pCur, _CURSOR_ERROR, err)
|
||||
err := vtabDelHandle(ctx, mod, pCur)
|
||||
return errorCode(err, ERROR)
|
||||
}
|
||||
|
||||
func cursorFilterCallback(ctx context.Context, mod api.Module, pCur, idxNum, idxStr, argc, argv uint32) uint32 {
|
||||
@@ -435,7 +510,7 @@ func cursorRowIDCallback(ctx context.Context, mod api.Module, pCur, pRowID uint3
|
||||
}
|
||||
|
||||
const (
|
||||
_MODULE_ERROR = iota
|
||||
_PTR_ERROR = iota
|
||||
_VTAB_ERROR
|
||||
_CURSOR_ERROR
|
||||
)
|
||||
@@ -461,6 +536,12 @@ func vtabGetHandle(ctx context.Context, mod api.Module, ptr uint32) any {
|
||||
return util.GetHandle(ctx, handle)
|
||||
}
|
||||
|
||||
func vtabDelHandle(ctx context.Context, mod api.Module, ptr uint32) error {
|
||||
const handleOffset = 4
|
||||
handle := util.ReadUint32(mod, ptr-handleOffset)
|
||||
return util.DelHandle(ctx, handle)
|
||||
}
|
||||
|
||||
func vtabPutHandle(ctx context.Context, mod api.Module, pptr uint32, val any) {
|
||||
const handleOffset = 4
|
||||
handle := util.AddHandle(ctx, val)
|
||||
|
||||
@@ -83,10 +83,6 @@ type seriesCursor struct {
|
||||
value int64
|
||||
}
|
||||
|
||||
func (*seriesCursor) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cur *seriesCursor) Filter(idxNum int, idxStr string, arg ...sqlite3.Value) error {
|
||||
switch len(arg) {
|
||||
case 0:
|
||||
|
||||
Reference in New Issue
Block a user