Files
sqlite3/ext/statement/stmt.go

217 lines
4.4 KiB
Go
Raw Normal View History

2023-12-14 20:36:07 +00:00
// Package statement defines table-valued functions using SQL.
//
// It can be used to create "parametrized views":
// pre-packaged queries that can be parametrized at query execution time.
2023-11-30 01:13:18 +00:00
//
// https://github.com/0x09/sqlite-statement-vtab
package statement
import (
"encoding/json"
2025-01-17 14:40:12 +00:00
"errors"
2023-11-30 01:13:18 +00:00
"strconv"
"strings"
"unsafe"
"github.com/ncruces/go-sqlite3"
2024-07-04 15:28:49 +01:00
"github.com/ncruces/go-sqlite3/internal/util"
2023-11-30 01:13:18 +00:00
)
// Register registers the statement virtual table.
2024-07-08 12:06:57 +01:00
func Register(db *sqlite3.Conn) error {
return sqlite3.CreateModule(db, "statement", declare, declare)
2023-12-06 15:39:26 +00:00
}
2023-12-05 16:11:56 +00:00
2023-12-06 15:39:26 +00:00
type table struct {
stmt *sqlite3.Stmt
sql string
inuse bool
}
2023-12-05 16:11:56 +00:00
2023-12-06 15:39:26 +00:00
func declare(db *sqlite3.Conn, _, _, _ string, arg ...string) (*table, error) {
if len(arg) != 1 {
2024-07-04 15:28:49 +01:00
return nil, util.ErrorString("statement: wrong number of arguments")
2023-12-06 15:39:26 +00:00
}
2023-11-30 01:13:18 +00:00
2023-12-06 15:39:26 +00:00
sql := "SELECT * FROM\n" + arg[0]
2023-12-05 16:11:56 +00:00
2024-10-04 16:39:34 +01:00
stmt, _, err := db.PrepareFlags(sql, sqlite3.PREPARE_PERSISTENT)
2023-12-06 15:39:26 +00:00
if err != nil {
return nil, err
}
2023-12-05 16:11:56 +00:00
2023-12-06 15:39:26 +00:00
var sep string
var str strings.Builder
str.WriteString("CREATE TABLE x(")
outputs := stmt.ColumnCount()
for i := 0; i < outputs; i++ {
name := sqlite3.QuoteIdentifier(stmt.ColumnName(i))
str.WriteString(sep)
str.WriteString(name)
str.WriteString(" ")
str.WriteString(stmt.ColumnDeclType(i))
sep = ","
2023-11-30 01:13:18 +00:00
}
2023-12-06 15:39:26 +00:00
inputs := stmt.BindCount()
for i := 1; i <= inputs; i++ {
str.WriteString(sep)
name := stmt.BindName(i)
if name == "" {
str.WriteString("[")
str.WriteString(strconv.Itoa(i))
str.WriteString("] HIDDEN")
} else {
str.WriteString(sqlite3.QuoteIdentifier(name[1:]))
str.WriteString(" HIDDEN")
}
sep = ","
}
str.WriteByte(')')
2023-11-30 01:13:18 +00:00
2024-01-08 19:23:32 +00:00
err = db.DeclareVTab(str.String())
2023-12-06 15:39:26 +00:00
if err != nil {
stmt.Close()
return nil, err
}
2023-11-30 01:13:18 +00:00
2023-12-06 15:39:26 +00:00
return &table{sql: sql, stmt: stmt}, nil
2023-11-30 01:13:18 +00:00
}
2023-12-04 12:37:53 +00:00
func (t *table) Close() error {
return t.stmt.Close()
2023-11-30 01:13:18 +00:00
}
func (t *table) BestIndex(idx *sqlite3.IndexInfo) error {
2023-12-04 12:37:53 +00:00
idx.EstimatedCost = 1000
2023-11-30 01:13:18 +00:00
var argvIndex = 1
var needIndex bool
var listIndex []int
2023-12-04 12:37:53 +00:00
outputs := t.stmt.ColumnCount()
2023-11-30 01:13:18 +00:00
for i, cst := range idx.Constraint {
// Skip if this is a constraint on one of our output columns.
2023-12-04 12:37:53 +00:00
if cst.Column < outputs {
2023-11-30 01:13:18 +00:00
continue
}
// A given query plan is only usable if all provided input columns
// are usable and have equal constraints only.
if !cst.Usable || cst.Op != sqlite3.INDEX_CONSTRAINT_EQ {
return sqlite3.CONSTRAINT
}
// The non-zero argvIdx values must be contiguous.
// If they're not, build a list and serialize it through IdxStr.
2023-12-04 12:37:53 +00:00
nextIndex := cst.Column - outputs + 1
2023-11-30 01:13:18 +00:00
idx.ConstraintUsage[i] = sqlite3.IndexConstraintUsage{
ArgvIndex: argvIndex,
Omit: true,
}
if nextIndex != argvIndex {
needIndex = true
}
listIndex = append(listIndex, nextIndex)
argvIndex++
}
if needIndex {
buf, err := json.Marshal(listIndex)
if err != nil {
return err
}
idx.IdxStr = unsafe.String(&buf[0], len(buf))
}
return nil
}
2024-07-23 13:28:09 +01:00
func (t *table) Open() (_ sqlite3.VTabCursor, err error) {
2023-12-04 12:37:53 +00:00
stmt := t.stmt
if !t.inuse {
t.inuse = true
} else {
stmt, _, err = t.stmt.Conn().Prepare(t.sql)
if err != nil {
return nil, err
}
2023-11-30 01:13:18 +00:00
}
return &cursor{table: t, stmt: stmt}, nil
}
func (t *table) Rename(new string) error {
return nil
}
type cursor struct {
table *table
stmt *sqlite3.Stmt
arg []sqlite3.Value
rowID int64
}
func (c *cursor) Close() error {
2023-12-04 12:37:53 +00:00
if c.stmt == c.table.stmt {
c.table.inuse = false
2025-01-17 14:40:12 +00:00
return errors.Join(
c.stmt.Reset(),
c.stmt.ClearBindings())
2023-12-04 12:37:53 +00:00
}
2023-11-30 01:13:18 +00:00
return c.stmt.Close()
}
func (c *cursor) Filter(idxNum int, idxStr string, arg ...sqlite3.Value) error {
c.arg = arg
c.rowID = 0
2025-01-17 14:40:12 +00:00
err := errors.Join(
c.stmt.Reset(),
c.stmt.ClearBindings())
if err != nil {
2023-11-30 01:13:18 +00:00
return err
}
var list []int
if idxStr != "" {
2023-12-04 12:37:53 +00:00
buf := unsafe.Slice(unsafe.StringData(idxStr), len(idxStr))
err := json.Unmarshal(buf, &list)
2023-11-30 01:13:18 +00:00
if err != nil {
return err
}
}
for i, arg := range arg {
param := i + 1
if list != nil {
param = list[i]
}
err := c.stmt.BindValue(param, arg)
if err != nil {
return err
}
}
return c.Next()
}
func (c *cursor) Next() error {
if c.stmt.Step() {
c.rowID++
}
return c.stmt.Err()
}
func (c *cursor) EOF() bool {
2023-12-04 12:37:53 +00:00
return !c.stmt.Busy()
2023-11-30 01:13:18 +00:00
}
func (c *cursor) RowID() (int64, error) {
return c.rowID, nil
}
2024-07-26 12:25:15 +01:00
func (c *cursor) Column(ctx sqlite3.Context, col int) error {
2023-12-04 12:37:53 +00:00
switch outputs := c.stmt.ColumnCount(); {
case col < outputs:
2023-11-30 01:13:18 +00:00
ctx.ResultValue(c.stmt.ColumnValue(col))
2023-12-04 12:37:53 +00:00
case col-outputs < len(c.arg):
ctx.ResultValue(c.arg[col-outputs])
2023-11-30 01:13:18 +00:00
}
return nil
}