feat: 新增 SQLite3 虚拟表框架及方言扩展
- 新增 dialect/sqlite3/vtab 包,提供干净的虚拟表接口(Module/Table/Cursor) - 适配层自动将 BestIndex 约束与 Filter 值绑定(ConstraintInfo.Value),无需手动编解码 IdxStr - 支持 OpLIMIT/OpOFFSET 约束下推 - 新增 SupportsDistinct 方言选项,控制 SELECT 级和表达式级 DISTINCT 生成 - sqlite3 方言注册 IF() 函数支持
This commit is contained in:
@@ -0,0 +1,204 @@
|
||||
//go:build sqlite_vtable || vtable
|
||||
|
||||
package vtab
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
gosqlite3 "github.com/mattn/go-sqlite3"
|
||||
)
|
||||
|
||||
// ─── 桥接层:Module → gosqlite3.Module ──────────────────────
|
||||
|
||||
type moduleAdapter struct {
|
||||
mod Module
|
||||
}
|
||||
|
||||
func (a *moduleAdapter) Create(c *gosqlite3.SQLiteConn, args []string) (gosqlite3.VTab, error) {
|
||||
return a.build(c, args, true)
|
||||
}
|
||||
|
||||
func (a *moduleAdapter) Connect(c *gosqlite3.SQLiteConn, args []string) (gosqlite3.VTab, error) {
|
||||
return a.build(c, args, false)
|
||||
}
|
||||
|
||||
func (a *moduleAdapter) DestroyModule() {}
|
||||
|
||||
func (a *moduleAdapter) build(c *gosqlite3.SQLiteConn, args []string, isCreate bool) (gosqlite3.VTab, error) {
|
||||
declare := func(schema string) error { return c.DeclareVTab(schema) }
|
||||
|
||||
var (
|
||||
table Table
|
||||
err error
|
||||
)
|
||||
if isCreate {
|
||||
table, err = a.mod.Create(args, declare)
|
||||
} else {
|
||||
table, err = a.mod.Connect(args, declare)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
base := &baseVtabAdapter{table: table}
|
||||
// if wt, ok := table.(WritableTable); ok {
|
||||
// return &writableVtabAdapter{baseVtabAdapter: base, wt: wt}, nil
|
||||
// }
|
||||
return base, nil
|
||||
}
|
||||
|
||||
func NewModuleAdapter(mod Module) gosqlite3.Module {
|
||||
return &moduleAdapter{mod}
|
||||
}
|
||||
|
||||
// ─── 桥接层:Table(只读)────────────────────────────────────
|
||||
|
||||
type planKey struct {
|
||||
idxNum int
|
||||
idxStr string
|
||||
}
|
||||
|
||||
type baseVtabAdapter struct {
|
||||
table Table
|
||||
// plans 保存每个查询计划中 Used=true 的约束(按值传入顺序),
|
||||
// BestIndex 写入,Filter 通过 (idxNum, idxStr) 查询后绑定值。
|
||||
plans map[planKey][]ConstraintInfo
|
||||
}
|
||||
|
||||
func (v *baseVtabAdapter) BestIndex(csts []gosqlite3.InfoConstraint, obs []gosqlite3.InfoOrderBy) (*gosqlite3.IndexResult, error) {
|
||||
ci := make([]ConstraintInfo, len(csts))
|
||||
for i, c := range csts {
|
||||
ci[i] = ConstraintInfo{Column: c.Column, Op: c.Op, Usable: c.Usable}
|
||||
}
|
||||
ob := make([]OrderByInfo, len(obs))
|
||||
for i, o := range obs {
|
||||
ob[i] = OrderByInfo{Column: o.Column, Desc: o.Desc}
|
||||
}
|
||||
|
||||
out, err := v.table.BestIndex(ci, ob)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 按值传入顺序收集 Used=true 的约束,供 Filter 阶段绑定值。
|
||||
var usedCi []ConstraintInfo
|
||||
for i, used := range out.Used {
|
||||
if used {
|
||||
usedCi = append(usedCi, ci[i])
|
||||
}
|
||||
}
|
||||
if v.plans == nil {
|
||||
v.plans = make(map[planKey][]ConstraintInfo)
|
||||
}
|
||||
v.plans[planKey{out.IdxNum, out.IdxStr}] = usedCi
|
||||
|
||||
return &gosqlite3.IndexResult{
|
||||
Used: out.Used,
|
||||
IdxNum: out.IdxNum,
|
||||
IdxStr: out.IdxStr,
|
||||
AlreadyOrdered: out.AlreadyOrdered,
|
||||
EstimatedCost: out.EstimatedCost,
|
||||
EstimatedRows: out.EstimatedRows,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (v *baseVtabAdapter) Disconnect() error { return v.table.Disconnect() }
|
||||
func (v *baseVtabAdapter) Destroy() error { return v.table.Destroy() }
|
||||
|
||||
func (v *baseVtabAdapter) Open() (gosqlite3.VTabCursor, error) {
|
||||
c, err := v.table.Open()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &cursorAdapter{cursor: c, adapter: v}, nil
|
||||
}
|
||||
|
||||
// ─── 桥接层:WritableTable ────────────────────────────────────
|
||||
|
||||
// writableVtabAdapter 内嵌只读适配器并实现 gosqlite3.VTabUpdater,
|
||||
// go-sqlite3 通过类型断言检测到该接口后会启用 INSERT/UPDATE/DELETE。
|
||||
type writableVtabAdapter struct {
|
||||
*baseVtabAdapter
|
||||
wt WritableTable
|
||||
}
|
||||
|
||||
func (v *writableVtabAdapter) Delete(rowid any) error {
|
||||
return v.wt.Delete(rowid)
|
||||
}
|
||||
|
||||
func (v *writableVtabAdapter) Insert(rowid any, values []any) (int64, error) {
|
||||
// rowid 是 SQLite 建议的值(通常为 nil,由表自行决定)
|
||||
return v.wt.Insert(values)
|
||||
}
|
||||
|
||||
func (v *writableVtabAdapter) Update(rowid any, values []any) error {
|
||||
return v.wt.Update(rowid, values)
|
||||
}
|
||||
|
||||
// ─── 桥接层:Cursor ───────────────────────────────────────────
|
||||
|
||||
type cursorAdapter struct {
|
||||
cursor Cursor
|
||||
adapter *baseVtabAdapter
|
||||
}
|
||||
|
||||
func (c *cursorAdapter) Close() error { return c.cursor.Close() }
|
||||
func (c *cursorAdapter) Next() error { return c.cursor.Next() }
|
||||
func (c *cursorAdapter) EOF() bool { return c.cursor.EOF() }
|
||||
|
||||
func (c *cursorAdapter) Filter(idxNum int, idxStr string, vals []any) error {
|
||||
ci := c.adapter.plans[planKey{idxNum, idxStr}]
|
||||
constraints := make([]ConstraintInfo, len(vals))
|
||||
for i, val := range vals {
|
||||
constraints[i].Value = val
|
||||
if i < len(ci) {
|
||||
constraints[i].Column = ci[i].Column
|
||||
constraints[i].Op = ci[i].Op
|
||||
constraints[i].Usable = true
|
||||
}
|
||||
}
|
||||
return c.cursor.Filter(idxNum, constraints)
|
||||
}
|
||||
|
||||
func (c *cursorAdapter) Rowid() (int64, error) { return c.cursor.Rowid() }
|
||||
|
||||
func (c *cursorAdapter) Column(ctx *gosqlite3.SQLiteContext, col int) error {
|
||||
val, err := c.cursor.Column(col)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resultValue(ctx, val)
|
||||
return nil
|
||||
}
|
||||
|
||||
// resultValue 将 Go 值写入 SQLite 上下文。
|
||||
func resultValue(ctx *gosqlite3.SQLiteContext, val any) {
|
||||
if val == nil {
|
||||
ctx.ResultNull()
|
||||
return
|
||||
}
|
||||
switch v := val.(type) {
|
||||
case int:
|
||||
ctx.ResultInt(v)
|
||||
case int32:
|
||||
ctx.ResultInt(int(v))
|
||||
case int64:
|
||||
ctx.ResultInt64(v)
|
||||
case float32:
|
||||
ctx.ResultDouble(float64(v))
|
||||
case float64:
|
||||
ctx.ResultDouble(v)
|
||||
case bool:
|
||||
if v {
|
||||
ctx.ResultInt(1)
|
||||
} else {
|
||||
ctx.ResultInt(0)
|
||||
}
|
||||
case string:
|
||||
ctx.ResultText(v)
|
||||
case []byte:
|
||||
ctx.ResultBlob(v)
|
||||
default:
|
||||
ctx.ResultText(fmt.Sprint(v))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user