More vtab API.

This commit is contained in:
Nuno Cruces
2024-01-08 19:23:32 +00:00
parent 2fb325b223
commit 69937fbee5
15 changed files with 176 additions and 55 deletions

View File

@@ -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 {

View File

@@ -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
}

View File

@@ -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
})
}

View File

@@ -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
}

View File

@@ -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
})
}

View File

@@ -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
})
}

View File

@@ -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")
}

View File

@@ -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

View File

@@ -1,4 +0,0 @@
zipfile
zipfile_cds
sqlar_compress
sqlar_uncompress

13
func.go
View File

@@ -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)),
}
}

View File

@@ -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),
}
}

View File

@@ -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()

View File

@@ -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
}

72
vtab.go
View File

@@ -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 {

View File

@@ -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 {