Files
db/dialect/sqlite3/vtab/vtab.go
T
what ac81f1ff0b feat: 新增 SQLite3 虚拟表框架及方言扩展
- 新增 dialect/sqlite3/vtab 包,提供干净的虚拟表接口(Module/Table/Cursor)
- 适配层自动将 BestIndex 约束与 Filter 值绑定(ConstraintInfo.Value),无需手动编解码 IdxStr
- 支持 OpLIMIT/OpOFFSET 约束下推
- 新增 SupportsDistinct 方言选项,控制 SELECT 级和表达式级 DISTINCT 生成
- sqlite3 方言注册 IF() 函数支持
2026-04-18 13:41:41 +08:00

191 lines
6.2 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"
"sync"
"time"
"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
// 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 本表能处理哪些约束。
type IndexOutput struct {
// Used[i]=true 表示第 i 个约束由本表自行处理。
// 对应约束的值会按原顺序在 Filter.constraintValues 中传入。
// len(Used) 必须等于传入 BestIndex 的 constraints 长度。
Used []bool
// IdxNum 和 IdxStr 是传给 Filter 的不透明标识,用于区分不同查询计划。
IdxNum int
IdxStr string
// 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 = time.RFC3339Nano
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 interface{}) interface{} {
if cond != 0 {
return trueVal
}
return falseVal
}, 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())
}