2023-11-21 13:40:55 +00:00
|
|
|
// Package array provides the array table-valued SQL function.
|
2023-12-12 01:00:13 +00:00
|
|
|
//
|
|
|
|
|
// https://sqlite.org/carray.html
|
2023-11-21 13:40:55 +00:00
|
|
|
package array
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"fmt"
|
|
|
|
|
"reflect"
|
|
|
|
|
|
|
|
|
|
"github.com/ncruces/go-sqlite3"
|
2023-12-20 16:10:32 +00:00
|
|
|
"github.com/ncruces/go-sqlite3/internal/util"
|
2023-11-21 13:40:55 +00:00
|
|
|
)
|
|
|
|
|
|
2023-11-22 13:11:23 +00:00
|
|
|
// Register registers the array single-argument, table-valued SQL function.
|
2023-12-22 02:45:26 +00:00
|
|
|
// The argument must be bound to a Go slice or array of
|
|
|
|
|
// ints, floats, bools, strings or byte slices,
|
|
|
|
|
// using [sqlite3.BindPointer] or [sqlite3.Pointer].
|
2024-07-08 12:06:57 +01:00
|
|
|
func Register(db *sqlite3.Conn) error {
|
|
|
|
|
return sqlite3.CreateModule(db, "array", nil,
|
2023-11-29 00:46:27 +00:00
|
|
|
func(db *sqlite3.Conn, _, _, _ string, _ ...string) (array, error) {
|
2024-01-08 19:23:32 +00:00
|
|
|
err := db.DeclareVTab(`CREATE TABLE x(value, array HIDDEN)`)
|
2023-11-22 13:11:23 +00:00
|
|
|
return array{}, err
|
|
|
|
|
})
|
2023-11-21 13:40:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type array struct{}
|
|
|
|
|
|
|
|
|
|
func (array) BestIndex(idx *sqlite3.IndexInfo) error {
|
|
|
|
|
for i, cst := range idx.Constraint {
|
|
|
|
|
if cst.Column == 1 && cst.Op == sqlite3.INDEX_CONSTRAINT_EQ && cst.Usable {
|
|
|
|
|
idx.ConstraintUsage[i] = sqlite3.IndexConstraintUsage{
|
|
|
|
|
Omit: true,
|
|
|
|
|
ArgvIndex: 1,
|
|
|
|
|
}
|
|
|
|
|
idx.EstimatedCost = 1
|
|
|
|
|
idx.EstimatedRows = 100
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return sqlite3.CONSTRAINT
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (array) Open() (sqlite3.VTabCursor, error) {
|
|
|
|
|
return &cursor{}, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type cursor struct {
|
|
|
|
|
array reflect.Value
|
|
|
|
|
rowID int
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *cursor) EOF() bool {
|
|
|
|
|
return c.rowID >= c.array.Len()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *cursor) Next() error {
|
|
|
|
|
c.rowID++
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *cursor) RowID() (int64, error) {
|
2025-10-15 16:25:35 +01:00
|
|
|
// One-based RowID for consistency with carray and other tables.
|
|
|
|
|
return int64(c.rowID) + 1, nil
|
2023-11-21 13:40:55 +00:00
|
|
|
}
|
|
|
|
|
|
2024-07-26 12:25:15 +01:00
|
|
|
func (c *cursor) Column(ctx sqlite3.Context, n int) error {
|
2023-11-21 13:40:55 +00:00
|
|
|
if n != 0 {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
v := c.array.Index(c.rowID)
|
|
|
|
|
k := v.Kind()
|
|
|
|
|
|
|
|
|
|
if k == reflect.Interface {
|
|
|
|
|
if v.IsNil() {
|
|
|
|
|
ctx.ResultNull()
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
v = v.Elem()
|
|
|
|
|
k = v.Kind()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch {
|
|
|
|
|
case v.CanInt():
|
|
|
|
|
ctx.ResultInt64(v.Int())
|
|
|
|
|
|
|
|
|
|
case v.CanUint():
|
|
|
|
|
i64 := int64(v.Uint())
|
|
|
|
|
if i64 < 0 {
|
|
|
|
|
return fmt.Errorf("array: integer element overflow:%.0w %d", sqlite3.MISMATCH, v.Uint())
|
|
|
|
|
}
|
|
|
|
|
ctx.ResultInt64(i64)
|
|
|
|
|
|
|
|
|
|
case v.CanFloat():
|
|
|
|
|
ctx.ResultFloat(v.Float())
|
|
|
|
|
|
|
|
|
|
case k == reflect.Bool:
|
|
|
|
|
ctx.ResultBool(v.Bool())
|
|
|
|
|
|
|
|
|
|
case k == reflect.String:
|
|
|
|
|
ctx.ResultText(v.String())
|
|
|
|
|
|
2023-11-23 09:54:18 +00:00
|
|
|
case (k == reflect.Slice || k == reflect.Array && v.CanAddr()) &&
|
2023-11-21 13:40:55 +00:00
|
|
|
v.Type().Elem().Kind() == reflect.Uint8:
|
|
|
|
|
ctx.ResultBlob(v.Bytes())
|
|
|
|
|
|
|
|
|
|
default:
|
2023-12-20 16:10:32 +00:00
|
|
|
return fmt.Errorf("array: unsupported element:%.0w %v", sqlite3.MISMATCH, util.ReflectType(v))
|
2023-11-21 13:40:55 +00:00
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *cursor) Filter(idxNum int, idxStr string, arg ...sqlite3.Value) error {
|
|
|
|
|
array := reflect.ValueOf(arg[0].Pointer())
|
2023-11-23 03:28:56 +00:00
|
|
|
array, err := indexable(array)
|
2023-11-21 13:40:55 +00:00
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
c.array = array
|
|
|
|
|
c.rowID = 0
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2023-12-06 15:39:26 +00:00
|
|
|
func indexable(v reflect.Value) (reflect.Value, error) {
|
2023-12-20 16:10:32 +00:00
|
|
|
switch v.Kind() {
|
|
|
|
|
case reflect.Slice:
|
2023-11-21 13:40:55 +00:00
|
|
|
return v, nil
|
2023-12-20 16:10:32 +00:00
|
|
|
case reflect.Array:
|
2023-11-21 13:40:55 +00:00
|
|
|
return v, nil
|
2023-12-20 16:10:32 +00:00
|
|
|
case reflect.Pointer:
|
2023-11-21 13:40:55 +00:00
|
|
|
if v := v.Elem(); v.Kind() == reflect.Array {
|
|
|
|
|
return v, nil
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-12-20 16:10:32 +00:00
|
|
|
return v, fmt.Errorf("array: unsupported argument:%.0w %v", sqlite3.MISMATCH, util.ReflectType(v))
|
2023-11-21 13:40:55 +00:00
|
|
|
}
|