mirror of
https://github.com/ncruces/go-sqlite3.git
synced 2026-01-11 21:49:13 +00:00
More vtab API.
This commit is contained in:
@@ -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 {
|
||||
|
||||
11
context.go
11
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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
zipfile
|
||||
zipfile_cds
|
||||
sqlar_compress
|
||||
sqlar_uncompress
|
||||
13
func.go
13
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)),
|
||||
}
|
||||
}
|
||||
|
||||
9
stmt.go
9
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),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
74
value.go
74
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
|
||||
}
|
||||
|
||||
72
vtab.go
72
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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user