diff --git a/config.go b/config.go index 79f8169..e68935e 100644 --- a/config.go +++ b/config.go @@ -33,7 +33,7 @@ func (c *Conn) Config(op DBConfig, arg ...bool) (bool, error) { return util.ReadUint32(c.mod, argsPtr) != 0, c.error(r) } -// ConfigLog sets up the error logging callback for the connection. +// ConfigLog sets up the error logging callback for the connection. // // https://www.sqlite.org/errlog.html func (c *Conn) ConfigLog(cb func(code ExtendedErrorCode, msg string)) error { diff --git a/context.go b/context.go index 6ea97ad..a3c82e5 100644 --- a/context.go +++ b/context.go @@ -184,7 +184,7 @@ func (ctx Context) ResultJSON(value any) { // // https://sqlite.org/c3ref/result_blob.html func (ctx Context) ResultValue(value Value) { - if value.sqlite != ctx.c.sqlite { + if value.c != ctx.c { ctx.ResultError(MISUSE) return } @@ -218,3 +218,12 @@ func (ctx Context) ResultError(err error) { uint64(ctx.handle), uint64(code)) } } + +// VTabNoChange may return true if a column is being fetched as part +// of an update during which the column value will not change. +// +// https://www.sqlite.org/c3ref/vtab_nochange.html +func (ctx Context) VTabNoChange() bool { + r := ctx.c.call("sqlite3_vtab_nochange", uint64(ctx.handle)) + return r != 0 +} diff --git a/ext/array/array.go b/ext/array/array.go index 9e23ba6..301736c 100644 --- a/ext/array/array.go +++ b/ext/array/array.go @@ -18,7 +18,7 @@ import ( func Register(db *sqlite3.Conn) { sqlite3.CreateModule[array](db, "array", nil, func(db *sqlite3.Conn, _, _, _ string, _ ...string) (array, error) { - err := db.DeclareVtab(`CREATE TABLE x(value, array HIDDEN)`) + err := db.DeclareVTab(`CREATE TABLE x(value, array HIDDEN)`) return array{}, err }) } diff --git a/ext/csv/csv.go b/ext/csv/csv.go index efe8ca0..eb931c1 100644 --- a/ext/csv/csv.go +++ b/ext/csv/csv.go @@ -95,11 +95,11 @@ func RegisterFS(db *sqlite3.Conn, fsys fs.FS) { schema = getSchema(header, columns, row) } - err = db.DeclareVtab(schema) + err = db.DeclareVTab(schema) if err != nil { return nil, err } - err = db.VtabConfig(sqlite3.VTAB_DIRECTONLY) + err = db.VTabConfig(sqlite3.VTAB_DIRECTONLY) if err != nil { return nil, err } diff --git a/ext/fileio/fileio.go b/ext/fileio/fileio.go index 6774506..90c9727 100644 --- a/ext/fileio/fileio.go +++ b/ext/fileio/fileio.go @@ -28,8 +28,8 @@ func RegisterFS(db *sqlite3.Conn, fsys fs.FS) { db.CreateFunction("writefile", -1, sqlite3.DIRECTONLY, writefile) } sqlite3.CreateModule(db, "fsdir", nil, func(db *sqlite3.Conn, _, _, _ string, _ ...string) (fsdir, error) { - err := db.DeclareVtab(`CREATE TABLE x(name,mode,mtime TIMESTAMP,data,path HIDDEN,dir HIDDEN)`) - db.VtabConfig(sqlite3.VTAB_DIRECTONLY) + err := db.DeclareVTab(`CREATE TABLE x(name,mode,mtime TIMESTAMP,data,path HIDDEN,dir HIDDEN)`) + db.VTabConfig(sqlite3.VTAB_DIRECTONLY) return fsdir{fsys}, err }) } diff --git a/ext/lines/lines.go b/ext/lines/lines.go index 73bf075..f5df4d6 100644 --- a/ext/lines/lines.go +++ b/ext/lines/lines.go @@ -36,14 +36,14 @@ func Register(db *sqlite3.Conn) { func RegisterFS(db *sqlite3.Conn, fsys fs.FS) { sqlite3.CreateModule[lines](db, "lines", nil, func(db *sqlite3.Conn, _, _, _ string, _ ...string) (lines, error) { - err := db.DeclareVtab(`CREATE TABLE x(line TEXT, data HIDDEN)`) - db.VtabConfig(sqlite3.VTAB_INNOCUOUS) + err := db.DeclareVTab(`CREATE TABLE x(line TEXT, data HIDDEN)`) + db.VTabConfig(sqlite3.VTAB_INNOCUOUS) return lines{}, err }) sqlite3.CreateModule[lines](db, "lines_read", nil, func(db *sqlite3.Conn, _, _, _ string, _ ...string) (lines, error) { - err := db.DeclareVtab(`CREATE TABLE x(line TEXT, data HIDDEN)`) - db.VtabConfig(sqlite3.VTAB_DIRECTONLY) + err := db.DeclareVTab(`CREATE TABLE x(line TEXT, data HIDDEN)`) + db.VTabConfig(sqlite3.VTAB_DIRECTONLY) return lines{fsys}, err }) } diff --git a/ext/pivot/pivot.go b/ext/pivot/pivot.go index 523d9f0..7b8fa6a 100644 --- a/ext/pivot/pivot.go +++ b/ext/pivot/pivot.go @@ -90,7 +90,7 @@ func declare(db *sqlite3.Conn, _, _, _ string, arg ...string) (_ *table, err err } create.WriteByte(')') - err = db.DeclareVtab(create.String()) + err = db.DeclareVTab(create.String()) if err != nil { return nil, err } @@ -142,6 +142,8 @@ func (t *table) BestIndex(idx *sqlite3.IndexInfo) error { } idxStr.WriteString(sep) idxStr.WriteString(t.keys[ord.Column]) + idxStr.WriteString(" COLLATE ") + idxStr.WriteString(idx.Collation(ord.Column)) if ord.Desc { idxStr.WriteString(" DESC") } diff --git a/ext/statement/stmt.go b/ext/statement/stmt.go index 7049043..99b5b4f 100644 --- a/ext/statement/stmt.go +++ b/ext/statement/stmt.go @@ -67,7 +67,7 @@ func declare(db *sqlite3.Conn, _, _, _ string, arg ...string) (*table, error) { } str.WriteByte(')') - err = db.DeclareVtab(str.String()) + err = db.DeclareVTab(str.String()) if err != nil { stmt.Close() return nil, err diff --git a/ext/todo.txt b/ext/todo.txt deleted file mode 100644 index 8b951bc..0000000 --- a/ext/todo.txt +++ /dev/null @@ -1,4 +0,0 @@ -zipfile -zipfile_cds -sqlar_compress -sqlar_uncompress \ No newline at end of file diff --git a/func.go b/func.go index fbf0812..d16e443 100644 --- a/func.go +++ b/func.go @@ -87,6 +87,17 @@ type WindowFunction interface { Inverse(ctx Context, arg ...Value) } +// OverloadFunction overloads a function for a virtual table. +// +// https://sqlite.org/c3ref/overload_function.html +func (c *Conn) OverloadFunction(name string, nArg int) error { + defer c.arena.mark()() + namePtr := c.arena.string(name) + r := c.call("sqlite3_overload_function", + uint64(c.handle), uint64(namePtr), uint64(nArg)) + return c.error(r) +} + func destroyCallback(ctx context.Context, mod api.Module, pApp uint32) { util.DelHandle(ctx, pApp) } @@ -167,7 +178,7 @@ func callbackArgs(db *Conn, nArg, pArg uint32) []Value { args := make([]Value, nArg) for i := range args { args[i] = Value{ - sqlite: db.sqlite, + c: db, handle: util.ReadUint32(db.mod, pArg+ptrlen*uint32(i)), } } diff --git a/stmt.go b/stmt.go index e9b80ae..48c2ad5 100644 --- a/stmt.go +++ b/stmt.go @@ -340,7 +340,7 @@ func (s *Stmt) BindJSON(param int, value any) error { // // https://sqlite.org/c3ref/bind_blob.html func (s *Stmt) BindValue(param int, value Value) error { - if value.sqlite != s.c.sqlite { + if value.c != s.c { return MISUSE } r := s.c.call("sqlite3_bind_value", @@ -403,10 +403,7 @@ func (s *Stmt) ColumnDeclType(col int) string { // // https://sqlite.org/c3ref/column_blob.html func (s *Stmt) ColumnBool(col int) bool { - if i := s.ColumnInt64(col); i != 0 { - return true - } - return false + return s.ColumnInt64(col) != 0 } // ColumnInt returns the value of the result column as an int. @@ -547,8 +544,8 @@ func (s *Stmt) ColumnValue(col int) Value { r := s.c.call("sqlite3_column_value", uint64(s.handle), uint64(col)) return Value{ + c: s.c, unprot: true, - sqlite: s.c.sqlite, handle: uint32(r), } } diff --git a/tests/func_test.go b/tests/func_test.go index efeb129..0d86b68 100644 --- a/tests/func_test.go +++ b/tests/func_test.go @@ -167,6 +167,26 @@ func TestCreateFunction(t *testing.T) { } } +func TestOverloadFunction(t *testing.T) { + t.Parallel() + + db, err := sqlite3.Open(":memory:") + if err != nil { + t.Fatal(err) + } + defer db.Close() + + err = db.OverloadFunction("test", 0) + if err != nil { + t.Fatal(err) + } + + err = db.Exec(`SELECT test()`) + if err == nil { + t.Fatal("want error") + } +} + func TestAnyCollationNeeded(t *testing.T) { t.Parallel() diff --git a/value.go b/value.go index e180eb4..fda7f4a 100644 --- a/value.go +++ b/value.go @@ -13,7 +13,7 @@ import ( // // https://sqlite.org/c3ref/value.html type Value struct { - *sqlite + c *Conn handle uint32 unprot bool copied bool @@ -30,10 +30,10 @@ func (v Value) protected() uint64 { // // https://sqlite.org/c3ref/value_dup.html func (v Value) Dup() *Value { - r := v.call("sqlite3_value_dup", uint64(v.handle)) + r := v.c.call("sqlite3_value_dup", uint64(v.handle)) return &Value{ + c: v.c, copied: true, - sqlite: v.sqlite, handle: uint32(r), } } @@ -45,7 +45,7 @@ func (dup *Value) Close() error { if !dup.copied { panic(util.ValueErr) } - dup.call("sqlite3_value_free", uint64(dup.handle)) + dup.c.call("sqlite3_value_free", uint64(dup.handle)) dup.handle = 0 return nil } @@ -54,7 +54,7 @@ func (dup *Value) Close() error { // // https://sqlite.org/c3ref/value_blob.html func (v Value) Type() Datatype { - r := v.call("sqlite3_value_type", v.protected()) + r := v.c.call("sqlite3_value_type", v.protected()) return Datatype(r) } @@ -65,10 +65,7 @@ func (v Value) Type() Datatype { // // https://sqlite.org/c3ref/value_blob.html func (v Value) Bool() bool { - if i := v.Int64(); i != 0 { - return true - } - return false + return v.Int64() != 0 } // Int returns the value as an int. @@ -82,7 +79,7 @@ func (v Value) Int() int { // // https://sqlite.org/c3ref/value_blob.html func (v Value) Int64() int64 { - r := v.call("sqlite3_value_int64", v.protected()) + r := v.c.call("sqlite3_value_int64", v.protected()) return int64(r) } @@ -90,7 +87,7 @@ func (v Value) Int64() int64 { // // https://sqlite.org/c3ref/value_blob.html func (v Value) Float() float64 { - r := v.call("sqlite3_value_double", v.protected()) + r := v.c.call("sqlite3_value_double", v.protected()) return math.Float64frombits(r) } @@ -136,7 +133,7 @@ func (v Value) Blob(buf []byte) []byte { // // https://sqlite.org/c3ref/value_blob.html func (v Value) RawText() []byte { - r := v.call("sqlite3_value_text", v.protected()) + r := v.c.call("sqlite3_value_text", v.protected()) return v.rawBytes(uint32(r)) } @@ -146,7 +143,7 @@ func (v Value) RawText() []byte { // // https://sqlite.org/c3ref/value_blob.html func (v Value) RawBlob() []byte { - r := v.call("sqlite3_value_blob", v.protected()) + r := v.c.call("sqlite3_value_blob", v.protected()) return v.rawBytes(uint32(r)) } @@ -155,15 +152,15 @@ func (v Value) rawBytes(ptr uint32) []byte { return nil } - r := v.call("sqlite3_value_bytes", v.protected()) - return util.View(v.mod, ptr, r) + r := v.c.call("sqlite3_value_bytes", v.protected()) + return util.View(v.c.mod, ptr, r) } // Pointer gets the pointer associated with this value, // or nil if it has no associated pointer. func (v Value) Pointer() any { - r := v.call("sqlite3_value_pointer_go", v.protected()) - return util.GetHandle(v.ctx, uint32(r)) + r := v.c.call("sqlite3_value_pointer_go", v.protected()) + return util.GetHandle(v.c.ctx, uint32(r)) } // JSON parses a JSON-encoded value @@ -186,3 +183,46 @@ func (v Value) JSON(ptr any) error { } return json.Unmarshal(data, ptr) } + +// NoChange returns true if and only if the value is unchanged +// in a virtual table update operatiom. +// +// https://sqlite.org/c3ref/value_blob.html +func (v Value) NoChange() bool { + r := v.c.call("sqlite3_value_nochange", v.protected()) + return r != 0 +} + +// InFirst returns the first element +// on the right-hand side of an IN constraint. +// +// https://www.sqlite.org/c3ref/vtab_in_first.html +func (v Value) InFirst() (Value, error) { + defer v.c.arena.mark()() + valPtr := v.c.arena.new(ptrlen) + r := v.c.call("sqlite3_vtab_in_first", uint64(v.handle), uint64(valPtr)) + if err := v.c.error(r); err != nil { + return Value{}, err + } + return Value{ + c: v.c, + handle: util.ReadUint32(v.c.mod, valPtr), + }, nil +} + +// InNext returns the next element +// on the right-hand side of an IN constraint. +// +// https://www.sqlite.org/c3ref/vtab_in_first.html +func (v Value) InNext() (Value, error) { + defer v.c.arena.mark()() + valPtr := v.c.arena.new(ptrlen) + r := v.c.call("sqlite3_vtab_in_next", uint64(v.handle), uint64(valPtr)) + if err := v.c.error(r); err != nil { + return Value{}, err + } + return Value{ + c: v.c, + handle: util.ReadUint32(v.c.mod, valPtr), + }, nil +} diff --git a/vtab.go b/vtab.go index f53610a..d4dc011 100644 --- a/vtab.go +++ b/vtab.go @@ -66,32 +66,53 @@ func implements[T any](typ reflect.Type) bool { return typ.Implements(reflect.TypeOf(ptr).Elem()) } -// DeclareVtab declares the schema of a virtual table. +// DeclareVTab declares the schema of a virtual table. // // https://sqlite.org/c3ref/declare_vtab.html -func (c *Conn) DeclareVtab(sql string) error { +func (c *Conn) DeclareVTab(sql string) error { defer c.arena.mark()() sqlPtr := c.arena.string(sql) r := c.call("sqlite3_declare_vtab", uint64(c.handle), uint64(sqlPtr)) return c.error(r) } -// IndexConstraintOp is a virtual table constraint operator code. +// VTabConflictMode is a virtual table conflict resolution mode. // -// https://sqlite.org/c3ref/c_vtab_constraint_support.html -type VtabConfigOption uint8 +// https://www.sqlite.org/c3ref/c_fail.html +type VTabConflictMode uint8 const ( - VTAB_CONSTRAINT_SUPPORT VtabConfigOption = 1 - VTAB_INNOCUOUS VtabConfigOption = 2 - VTAB_DIRECTONLY VtabConfigOption = 3 - VTAB_USES_ALL_SCHEMAS VtabConfigOption = 4 + VTAB_ROLLBACK VTabConflictMode = 1 + VTAB_IGNORE VTabConflictMode = 2 + VTAB_FAIL VTabConflictMode = 3 + VTAB_ABORT VTabConflictMode = 4 + VTAB_REPLACE VTabConflictMode = 5 ) -// VtabConfig configures various facets of the virtual table interface. +// VTabOnConflict determines the virtual table conflict policy. +// +// https://sqlite.org/c3ref/vtab_on_conflict.html +func (c *Conn) VTabOnConflict() VTabConflictMode { + r := c.call("sqlite3_vtab_on_conflict", uint64(c.handle)) + return VTabConflictMode(r) +} + +// VTabConfigOption is a virtual table configuration option. +// +// https://sqlite.org/c3ref/c_vtab_constraint_support.html +type VTabConfigOption uint8 + +const ( + VTAB_CONSTRAINT_SUPPORT VTabConfigOption = 1 + VTAB_INNOCUOUS VTabConfigOption = 2 + VTAB_DIRECTONLY VTabConfigOption = 3 + VTAB_USES_ALL_SCHEMAS VTabConfigOption = 4 +) + +// VTabConfig configures various facets of the virtual table interface. // // https://sqlite.org/c3ref/vtab_config.html -func (c *Conn) VtabConfig(op VtabConfigOption, args ...any) error { +func (c *Conn) VTabConfig(op VTabConfigOption, args ...any) error { var i uint64 if op == VTAB_CONSTRAINT_SUPPORT && len(args) > 0 { if b, ok := args[0].(bool); ok && b { @@ -268,11 +289,36 @@ func (idx *IndexInfo) RHSValue(column int) (Value, error) { return Value{}, err } return Value{ - sqlite: idx.c.sqlite, + c: idx.c, handle: util.ReadUint32(idx.c.mod, valPtr), }, nil } +// Collation returns the name of the collation for a virtual table constraint. +// +// https://sqlite.org/c3ref/vtab_collation.html +func (idx *IndexInfo) Collation(column int) string { + r := idx.c.call("sqlite3_vtab_collation", + uint64(idx.handle), uint64(column)) + return util.ReadString(idx.c.mod, uint32(r), _MAX_NAME) +} + +// Distinct determines if a virtual table query is DISTINCT. +// +// https://sqlite.org/c3ref/vtab_distinct.html +func (idx *IndexInfo) Distinct() int { + r := idx.c.call("sqlite3_vtab_distinct", uint64(idx.handle)) + return int(r) +} + +// In identifies and handles IN constraints. +// +// https://sqlite.org/c3ref/vtab_in.html +func (idx *IndexInfo) In(column, handle int) bool { + r := idx.c.call("sqlite3_vtab_in", uint64(idx.handle), uint64(column), uint64(handle)) + return r != 0 +} + func (idx *IndexInfo) load() { // https://sqlite.org/c3ref/index_info.html mod := idx.c.mod @@ -574,7 +620,7 @@ func vtabError(ctx context.Context, mod api.Module, ptr, kind uint32, err error) case _VTAB_ERROR: ptr = ptr + 8 // zErrMsg case _CURSOR_ERROR: - ptr = util.ReadUint32(mod, ptr) + 8 // pVtab->zErrMsg + ptr = util.ReadUint32(mod, ptr) + 8 // pVTab->zErrMsg } db := ctx.Value(connKey{}).(*Conn) if ptr := util.ReadUint32(mod, ptr); ptr != 0 { diff --git a/vtab_test.go b/vtab_test.go index cf4f5c9..69c4a4b 100644 --- a/vtab_test.go +++ b/vtab_test.go @@ -17,7 +17,7 @@ func ExampleCreateModule() { err = sqlite3.CreateModule[seriesTable](db, "generate_series", nil, func(db *sqlite3.Conn, module, schema, table string, arg ...string) (seriesTable, error) { - err := db.DeclareVtab(`CREATE TABLE x(value, start HIDDEN, stop HIDDEN, step HIDDEN)`) + err := db.DeclareVTab(`CREATE TABLE x(value, start HIDDEN, stop HIDDEN, step HIDDEN)`) return seriesTable{}, err }) if err != nil {