Files
db/dialect/sqlite3/vtab/vtab.go
T
what 21b80bdea4 feat: 完善扫描器、exec 及 schema 相关功能
- exec/scanner: 用 *interface{} 替换 **json.RawMessage 扫描目标,兼容 DuckDB 返回 map[string]interface{} 的场景;新增 toJSONRawMessage 转换函数
- exec/scanner: ScanVal 支持结构体指针,通过 JSON 中间层转换(DuckDB STRUCT 列)
- exec/scanner: 将 *sql.RawBytes 和 *[]byte 的处理从 ScanValContext 移入 scanner.ScanVal
- exec/query_executor: 简化 ScanValContext,移除私有 scan 方法
- exec: 补充 scanner 级别 ScanVal 测试用例
- internal/util/reflect: 重写 SafeSetVarValue,修复非指针 src 及 nil 指针字段的 panic
- internal/util/column_map: 恢复非匿名带标签结构体字段的展开逻辑
- schema: 新增 vector 列类型支持
- engine: 补充 DuckDB 相关配置
- dialect/sqlite3/vtab: 完善虚拟表适配器
- 各方言测试改用 sqlmock 虚拟连接
2026-05-20 17:52:28 +08:00

196 lines
6.4 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//go:build sqlite_vtable || vtable
// Package vtab 在 mattn/go-sqlite3 上提供干净的虚拟表框架。
//
// 编译要求:需加 build tag `-tags sqlite_vtable`
//
// 基本用法:
//
// vtab.Register("my_module", &MyModule{})
// db, _ := sql.Open(vtab.DriverName, ":memory:")
// db.Exec(`CREATE VIRTUAL TABLE t USING my_module(arg1, arg2)`)
// db.Query(`SELECT * FROM t WHERE col = ?`, value)
package vtab
import (
"database/sql"
"fmt"
"regexp"
"sync"
"git.fsdpf.net/go/db"
"git.fsdpf.net/go/db/exp"
gosqlite3 "github.com/mattn/go-sqlite3"
)
// DriverName 是支持虚拟表的 SQLite3 驱动名称,同时内置 IF() 函数支持。
// 构建时需指定 tag-tags vtable
const DriverName = "sqlite3_vtab"
// Op 是 WHERE 约束的操作符类型,直接复用 go-sqlite3 的定义。
type Op = gosqlite3.Op
// 操作符常量,与 SQLite C API 值一致。
const (
OpEQ Op = gosqlite3.OpEQ // =
OpGT Op = gosqlite3.OpGT // >
OpLE Op = gosqlite3.OpLE // <=
OpLT Op = gosqlite3.OpLT // <
OpGE Op = gosqlite3.OpGE // >=
OpLIKE Op = gosqlite3.OpLIKE // LIKE
OpREGEXP Op = gosqlite3.OpREGEXP // REGEXP
// OpLIMIT / OpOFFSETgo-sqlite3 尚未导出这两个常量,直接使用 SQLite C API 原始值。
// BestIndex 中将它们标记为 Used=true 后,Filter 可收到 LIMIT / OFFSET 的实际值。
// 注意:这两个约束的 Column 字段为 -1(不对应任何列)。
OpLIMIT Op = 73 // SQLITE_INDEX_CONSTRAINT_LIMIT
OpOFFSET Op = 74 // SQLITE_INDEX_CONSTRAINT_OFFSET
)
// ConstraintInfo 是约束信息,BestIndex 和 Filter 阶段共用。
// - BestIndex 阶段:Value 为 nilUsable 表示 SQLite 是否允许使用该约束。
// - Filter 阶段:Value 为实际约束值(已绑定),Usable 始终为 true。
type ConstraintInfo struct {
Column int // 列索引,对应 DeclareVTab 中列的顺序(从 0 开始);OpLIMIT/OpOFFSET 时为 -1
Op Op // 操作符
Usable bool // BestIndex 阶段有效;Filter 阶段忽略
Value any // Filter 阶段由适配层填充;BestIndex 阶段为 nil
}
// OrderByInfo 是查询中的 ORDER BY 信息。
type OrderByInfo struct {
Column int // 列索引
Desc bool // true 表示降序
}
// IndexOutput 是 BestIndex 的返回值,告知 SQLite 本表能处理哪些约束。
// IdxNum/IdxStr 由 adapter 层根据 Used 约束自动生成,用户无需设置。
type IndexOutput struct {
// Used[i]=true 表示第 i 个约束由本表自行处理。
// 对应约束的值会按原顺序在 Filter.constraintValues 中传入。
// len(Used) 必须等于传入 BestIndex 的 constraints 长度。
Used []bool
// AlreadyOrdered 为 true 时 SQLite 不再对结果二次排序。
AlreadyOrdered bool
// EstimatedCost 扫描代价估算(越小越优先),默认 0 表示交给 SQLite 决定。
EstimatedCost float64
// EstimatedRows 预估返回行数,默认 0。
EstimatedRows float64
}
func DialectOptions() *db.SQLDialectOptions {
opts := db.DefaultDialectOptions()
opts.SupportsReturn = true
opts.SupportsOrderByOnUpdate = true
opts.SupportsLimitOnUpdate = true
opts.SupportsOrderByOnDelete = true
opts.SupportsLimitOnDelete = true
opts.SupportsConflictUpdateWhere = false
opts.SupportsInsertIgnoreSyntax = true
opts.SupportsConflictTarget = true
opts.SupportsMultipleUpdateTables = false
opts.WrapCompoundsInParens = false
opts.SupportsDistinct = false // 设为 false 可全局禁止生成 DISTINCT 关键字
opts.SupportsDistinctOn = false
opts.SupportsWindowFunction = false
opts.SupportsLateral = false
opts.PlaceHolderFragment = []byte("?")
opts.IncludePlaceholderNum = false
opts.QuoteRune = '`'
opts.DefaultValuesFragment = []byte("")
opts.True = []byte("1")
opts.False = []byte("0")
opts.TimeFormat = "2006-01-02 15:04:05"
opts.BooleanOperatorLookup = map[exp.BooleanOperation][]byte{
exp.EqOp: []byte("="),
exp.NeqOp: []byte("!="),
exp.GtOp: []byte(">"),
exp.GteOp: []byte(">="),
exp.LtOp: []byte("<"),
exp.LteOp: []byte("<="),
exp.InOp: []byte("IN"),
exp.NotInOp: []byte("NOT IN"),
exp.IsOp: []byte("IS"),
exp.IsNotOp: []byte("IS NOT"),
exp.LikeOp: []byte("LIKE"),
exp.NotLikeOp: []byte("NOT LIKE"),
exp.ILikeOp: []byte("LIKE"),
exp.NotILikeOp: []byte("NOT LIKE"),
exp.RegexpLikeOp: []byte("REGEXP"),
exp.RegexpNotLikeOp: []byte("NOT REGEXP"),
exp.RegexpILikeOp: []byte("REGEXP"),
exp.RegexpNotILikeOp: []byte("NOT REGEXP"),
}
opts.UseLiteralIsBools = false
opts.BitwiseOperatorLookup = map[exp.BitwiseOperation][]byte{
exp.BitwiseOrOp: []byte("|"),
exp.BitwiseAndOp: []byte("&"),
exp.BitwiseLeftShiftOp: []byte("<<"),
exp.BitwiseRightShiftOp: []byte(">>"),
}
opts.EscapedRunes = map[rune][]byte{
'\'': []byte("''"),
}
opts.InsertIgnoreClause = []byte("INSERT OR IGNORE INTO ")
opts.ConflictFragment = []byte(" ON CONFLICT ")
opts.ConflictDoUpdateFragment = []byte(" DO UPDATE SET ")
opts.ConflictDoNothingFragment = []byte(" DO NOTHING ")
opts.ForUpdateFragment = []byte("")
opts.OfFragment = []byte("")
opts.NowaitFragment = []byte("")
return opts
}
var (
registryMu sync.RWMutex
registry = map[string]Module{}
)
// Register 注册一个虚拟表模块。
// 必须在第一次打开数据库连接之前调用。
// 注册后,可在 SQL 中使用:CREATE VIRTUAL TABLE t USING moduleName(args...)
func Register(moduleName string, m Module) {
registryMu.Lock()
registry[moduleName] = m
registryMu.Unlock()
}
func init() {
sql.Register(DriverName, &gosqlite3.SQLiteDriver{
ConnectHook: func(conn *gosqlite3.SQLiteConn) error {
// 内置 IF(cond, trueVal, falseVal) 函数
if err := conn.RegisterFunc("IF", func(cond int64, trueVal, falseVal any) any {
if cond != 0 {
return trueVal
}
return falseVal
}, true); err != nil {
return err
}
if err := conn.RegisterFunc("REGEXP", func(expr, item string) (bool, error) {
return regexp.MatchString(expr, item)
}, true); err != nil {
return err
}
// 注册所有已登记的虚拟表模块
registryMu.RLock()
defer registryMu.RUnlock()
for name, m := range registry {
if err := conn.CreateModule(name, &moduleAdapter{mod: m}); err != nil {
return fmt.Errorf("vtab: 注册模块 %q 失败: %w", name, err)
}
}
return nil
},
})
db.RegisterDialect("vtable", DialectOptions())
}