diff --git a/README.md b/README.md index f8ba77d..25b2ccb 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ Go, wazero and [`x/sys`](https://pkg.go.dev/golang.org/x/sys) are the _only_ run - [`github.com/ncruces/go-sqlite3/ext/blobio`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/blobio) simplifies [incremental BLOB I/O](https://sqlite.org/c3ref/blob_open.html). - [`github.com/ncruces/go-sqlite3/ext/bloom`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/bloom) - provides the [`bloom_filter`](https://github.com/nalgeon/sqlean/issues/27#issuecomment-1002267134) virtual table. + provides a [Bloom filter](https://github.com/nalgeon/sqlean/issues/27#issuecomment-1002267134) virtual table. - [`github.com/ncruces/go-sqlite3/ext/csv`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/csv) reads [comma-separated values](https://sqlite.org/csv.html). - [`github.com/ncruces/go-sqlite3/ext/fileio`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/fileio) diff --git a/driver/driver.go b/driver/driver.go index 912b740..e7863b1 100644 --- a/driver/driver.go +++ b/driver/driver.go @@ -229,6 +229,7 @@ func (c *conn) Raw() *sqlite3.Conn { return c.Conn } +// Deprecated: use BeginTx instead. func (c *conn) Begin() (driver.Tx, error) { return c.BeginTx(context.Background(), driver.TxOptions{}) } @@ -559,19 +560,20 @@ func (r *rows) Next(dest []driver.Value) error { return err } -func (r *rows) decodeTime(i int, v any) (_ time.Time, _ bool) { +func (r *rows) decodeTime(i int, v any) (_ time.Time, ok bool) { if r.tmRead == sqlite3.TimeFormatDefault { - return - } - switch r.declType(i) { - case "DATE", "TIME", "DATETIME", "TIMESTAMP": - // maybe - default: + // handled by maybeTime return } switch v.(type) { case int64, float64, string: - // maybe + // could be a time value + default: + return + } + switch r.declType(i) { + case "DATE", "TIME", "DATETIME", "TIMESTAMP": + // could be a time value default: return } diff --git a/embed/sqlite3.wasm b/embed/sqlite3.wasm index 2689f77..8dfc7da 100755 Binary files a/embed/sqlite3.wasm and b/embed/sqlite3.wasm differ diff --git a/ext/bloom/bloom.go b/ext/bloom/bloom.go index 71d4bc7..621dd19 100644 --- a/ext/bloom/bloom.go +++ b/ext/bloom/bloom.go @@ -15,7 +15,6 @@ import ( "github.com/dchest/siphash" "github.com/ncruces/go-sqlite3" - "github.com/ncruces/go-sqlite3/internal/util" ) // Register registers the bloom_filter virtual table: @@ -161,6 +160,41 @@ func (b *bloom) Rename(new string) error { return err } +func (t *bloom) ShadowTables() {} + +func (t *bloom) Integrity(schema, table string, flags int) error { + load, _, err := t.db.Prepare(fmt.Sprintf( + `SELECT typeof(data), length(data), p, n, m, k FROM %s.%s WHERE rowid = 1`, + sqlite3.QuoteIdentifier(t.schema), sqlite3.QuoteIdentifier(t.storage))) + if err != nil { + return fmt.Errorf("bloom: %v", err) // can't wrap! + } + defer load.Close() + + err = errors.New("bloom: invalid parameters") + if !load.Step() { + return err + } + if t := load.ColumnText(0); t != "blob" { + return err + } + if m := load.ColumnInt64(4); m <= 0 || m%8 != 0 { + return err + } else if load.ColumnInt64(1) != m/8 { + return err + } + if p := load.ColumnFloat(2); p <= 0 || p >= 1 { + return err + } + if n := load.ColumnInt64(3); n <= 0 { + return err + } + if k := load.ColumnInt(5); k <= 0 { + return err + } + return nil +} + func (b *bloom) BestIndex(idx *sqlite3.IndexInfo) error { for n, cst := range idx.Constraint { if cst.Usable && cst.Column == 1 && @@ -274,8 +308,6 @@ func (c *cursor) Column(ctx *sqlite3.Context, n int) error { ctx.ResultBool(true) case 1: ctx.ResultValue(*c.arg) - default: - panic(util.AssertErr()) } return nil } diff --git a/ext/bloom/bloom_test.go b/ext/bloom/bloom_test.go index d07e99b..d9df376 100644 --- a/ext/bloom/bloom_test.go +++ b/ext/bloom/bloom_test.go @@ -127,4 +127,14 @@ func Test_compatible(t *testing.T) { if err != nil { t.Fatal(err) } + + err = db.Exec(`PRAGMA integrity_check`) + if err != nil { + t.Error(err) + } + + err = db.Exec(`PRAGMA quick_check`) + if err != nil { + t.Error(err) + } } diff --git a/sqlite3/vtab.c b/sqlite3/vtab.c index a9af043..4c85cf9 100644 --- a/sqlite3/vtab.c +++ b/sqlite3/vtab.c @@ -1,16 +1,18 @@ +#include #include #include "include.h" #include "sqlite3.h" -#define SQLITE_VTAB_CREATOR_GO /******/ 0x01 -#define SQLITE_VTAB_DESTROYER_GO /****/ 0x02 -#define SQLITE_VTAB_UPDATER_GO /******/ 0x04 -#define SQLITE_VTAB_RENAMER_GO /******/ 0x08 -#define SQLITE_VTAB_OVERLOADER_GO /***/ 0x10 -#define SQLITE_VTAB_CHECKER_GO /******/ 0x20 -#define SQLITE_VTAB_TXN_GO /**********/ 0x40 -#define SQLITE_VTAB_SAVEPOINTER_GO /**/ 0x80 +#define SQLITE_VTAB_CREATOR_GO /******/ 0x001 +#define SQLITE_VTAB_DESTROYER_GO /****/ 0x002 +#define SQLITE_VTAB_UPDATER_GO /******/ 0x004 +#define SQLITE_VTAB_RENAMER_GO /******/ 0x008 +#define SQLITE_VTAB_OVERLOADER_GO /***/ 0x010 +#define SQLITE_VTAB_CHECKER_GO /******/ 0x020 +#define SQLITE_VTAB_TXN_GO /**********/ 0x040 +#define SQLITE_VTAB_SAVEPOINTER_GO /**/ 0x080 +#define SQLITE_VTAB_SHADOWTABS_GO /***/ 0x100 int go_vtab_create(sqlite3_module *, int argc, const char *const *argv, sqlite3_vtab **, char **pzErr); @@ -157,6 +159,8 @@ static int go_vtab_integrity_wrapper(sqlite3_vtab *pVTab, const char *zSchema, return rc; } +static int go_vtab_shadown_name_wrapper(const char *zName) { return 1; } + int sqlite3_create_module_go(sqlite3 *db, const char *zName, int flags, go_handle handle) { struct go_module *mod = malloc(sizeof(struct go_module)); @@ -208,6 +212,9 @@ int sqlite3_create_module_go(sqlite3 *db, const char *zName, int flags, mod->base.xRelease = go_vtab_release; mod->base.xRollbackTo = go_vtab_rollback_to; } + if (flags & SQLITE_VTAB_SHADOWTABS_GO) { + mod->base.xShadowName = go_vtab_shadown_name_wrapper; + } if (mod->base.xCreate && !mod->base.xDestroy) { mod->base.xDestroy = mod->base.xDisconnect; } diff --git a/vfs/adiantum/README.md b/vfs/adiantum/README.md index b993484..508c685 100644 --- a/vfs/adiantum/README.md +++ b/vfs/adiantum/README.md @@ -1,13 +1,7 @@ -# Go `"adiantum"` SQLite VFS +# Go `adiantum` SQLite VFS This package wraps an SQLite VFS to offer encryption at rest. -> [!WARNING] -> This work was not certified by a cryptographer. -> If you need vetted encryption, you should purchase the -> [SQLite Encryption Extension](https://sqlite.org/see), -> and either wrap it, or seek assistance wrapping it. - The `"adiantum"` VFS wraps the default SQLite VFS using the [Adiantum](https://github.com/lukechampine/adiantum) tweakable and length-preserving encryption.\ diff --git a/vfs/memdb/README.md b/vfs/memdb/README.md index 193e29d..2e2611b 100644 --- a/vfs/memdb/README.md +++ b/vfs/memdb/README.md @@ -1,4 +1,4 @@ -# Go `"memdb"` SQLite VFS +# Go `memdb` SQLite VFS This package implements the [`"memdb"`](https://sqlite.org/src/doc/tip/src/memdb.c) SQLite VFS in pure Go. diff --git a/vfs/readervfs/README.md b/vfs/readervfs/README.md index 47a5f33..5281b88 100644 --- a/vfs/readervfs/README.md +++ b/vfs/readervfs/README.md @@ -1,4 +1,4 @@ -# Go `"reader"` SQLite VFS +# Go `reader` SQLite VFS This package implements a `"reader"` SQLite VFS that allows accessing any [`io.ReaderAt`](https://pkg.go.dev/io#ReaderAt) diff --git a/vtab.go b/vtab.go index a330c98..7c19330 100644 --- a/vtab.go +++ b/vtab.go @@ -16,14 +16,15 @@ func CreateModule[T VTab](db *Conn, name string, create, connect VTabConstructor var flags int const ( - VTAB_CREATOR = 0x01 - VTAB_DESTROYER = 0x02 - VTAB_UPDATER = 0x04 - VTAB_RENAMER = 0x08 - VTAB_OVERLOADER = 0x10 - VTAB_CHECKER = 0x20 - VTAB_TXN = 0x40 - VTAB_SAVEPOINTER = 0x80 + 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 { @@ -52,6 +53,9 @@ func CreateModule[T VTab](db *Conn, name string, create, connect VTabConstructor if implements[VTabSavepointer](vtab) { flags |= VTAB_SAVEPOINTER } + if implements[VTabShadowTabler](vtab) { + flags |= VTAB_SHADOWTABS + } defer db.arena.mark()() namePtr := db.arena.string(name) @@ -174,6 +178,17 @@ type VTabOverloader interface { FindFunction(arg int, name string) (ScalarFunction, IndexConstraintOp) } +// A VTabShadowTabler allows a virtual table to protect the content +// of shadow tables from being corrupted by hostile SQL. +// +// Implementing this interface signals that a virtual table named +// "mumble" reserves all table names starting with "mumble_". +type VTabShadowTabler interface { + VTab + // https://sqlite.org/vtab.html#the_xshadowname_method + ShadowTables() +} + // A VTabChecker allows a virtual table to report errors // to the PRAGMA integrity_check and PRAGMA quick_check commands. //