//go:build sqlite_vtable || vtable package vtab import ( "fmt" "strings" "time" 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(只读)──────────────────────────────────── // planKey 是查询计划的唯一标识,由 BestIndex 写入、Filter 读取。 // 两个字段均由用户实现的 BestIndex 返回,SQLite 原样透传给 Filter, // 组合唯一对应一份约束元数据列表(列索引 + 操作符)。 type planKey struct { idxNum int // 对应 IndexOutput.IdxNum,用于区分不同查询计划 idxStr string // 对应 IndexOutput.IdxStr,与 idxNum 配合进一步区分计划。 // 与 SQL 字段名无关,是 BestIndex → Filter 之间的自由通信通道,常见用途: // 1. 传递索引名(如 "idx_name"),告知 Filter 按哪个索引逻辑过滤; // 2. 序列化约束条件,Filter 直接解析,省去查 plans map 的步骤; // 3. 传递排序方向(如 "asc"/"desc")。 // SQLite 不解释其含义,仅原样透传给 Filter。当前实现固定返回 "",未使用。 } type baseVtabAdapter struct { table Table // plans 保存每个查询计划中 Used=true 的约束(按值传入顺序), // BestIndex 写入,Filter 通过 (idxNum, idxStr) 查询后绑定值。 plans map[planKey][]ConstraintInfo } // BestIndex 是适配层的 xBestIndex 实现,将 go-sqlite3 的 C 结构转换为 Go 接口, // 调用用户实现的 table.BestIndex,再把结果保存为"查询计划"供 Filter 阶段使用。 // // 调用时机:每次 SQLite 准备执行针对此虚拟表的查询时调用,可能调用多次(不同约束组合)。 // 执行顺序:BestIndex → (SQLite 选定计划)→ Filter(实际执行查询)。 // // SQLite 对同一个 prepared statement 可以不重新调用 BestIndex func (v *baseVtabAdapter) BestIndex(csts []gosqlite3.InfoConstraint, obs []gosqlite3.InfoOrderBy) (*gosqlite3.IndexResult, error) { // 将 go-sqlite3 的 C 结构转换为 Go 的 ConstraintInfo,传给用户实现。 // 每个 constraint 对应 WHERE 子句中的一个条件(列、操作符、是否可用)。 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 } // adapter 统一接管所有 Usable 的约束,用户无需在 IndexOutput 声明 Used。 // Filter 阶段 SQLite 只传入 Used=true 的约束值(argv), // 需要靠这里保存的顺序和列信息才能还原出完整的 ConstraintInfo。 used := make([]bool, len(ci)) var usedCi []ConstraintInfo for i, c := range ci { if c.Usable { used[i] = true usedCi = append(usedCi, c) } } // 用约束的列索引+操作符自动生成唯一 planKey,确保不同查询类型的计划互不覆盖。 parts := make([]string, len(usedCi)) for i, c := range usedCi { parts[i] = fmt.Sprintf("%d:%d", c.Column, int(c.Op)) } idxStr := strings.Join(parts, ",") idxNum := len(usedCi) if v.plans == nil { v.plans = make(map[planKey][]ConstraintInfo) } v.plans[planKey{idxNum, idxStr}] = usedCi return &gosqlite3.IndexResult{ Used: used, IdxNum: idxNum, IdxStr: 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, plans: v.plans}, 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 // plans 是 Open() 时从 adapter 复制的计划快照,与后续 BestIndex 调用隔离, // 防止 list/item 等不同查询的 BestIndex 相互覆盖导致 Filter 拿到错误的列信息。 plans map[planKey][]ConstraintInfo } 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() } // Filter 是适配层的 xFilter 实现,将 SQLite 传入的约束值与 BestIndex 保存的计划合并, // 还原出完整的 ConstraintInfo 列表后调用用户实现的 tCursor.Filter。 // // 调用时机:每次实际执行查询时(包括 IN 展开的每一次子查询)。 // 执行顺序:BestIndex(保存计划)→ Filter(绑定值、执行查询)。 // // Filter 的本质是把两份分离的信息合并成一条完整的过滤条件: // ci[i].Column + ci[i].Op ←(BestIndex 存的:哪列、什么操作) // + // vals[i] ←(SQLite 传的:过滤值) // = // WHERE column OP value ←(最终传给 tCursor.Filter 的约束) // 合并完之后交给 tCursor.Filter,再转成 fiter map,最终作为参数传给 vt.Select 去调 API。 func (c *cursorAdapter) Filter(idxNum int, idxStr string, vals []any) error { // 通过 (idxNum, idxStr) 找到 BestIndex 阶段保存的约束元数据(列索引、操作符)。 // vals 只含约束的值,没有列和操作符信息,必须与 ci 对应位置合并才能还原完整约束。 ci := c.plans[planKey{idxNum, idxStr}] // vals 长度 = BestIndex 中 Used=true 的约束数量(SQLite 保证一一对应)。 // ⚠️ SQLite 3.38+ IN 约束场景下,ci 可能比 vals 短(计划被 Usable=false 调用冲掉时)。 // 此时超出 ci 长度的约束 Column/Op 会是零值,由 tCursor.Filter 根据 Op 类型决定是否使用。 constraints := make([]ConstraintInfo, len(vals)) for i, val := range vals { constraints[i].Value = val // 绑定 SQLite 传入的约束值 if i < len(ci) { constraints[i].Column = ci[i].Column // 对应列索引,用于 GetColField constraints[i].Op = ci[i].Op // 操作符,用于映射到 exp.BooleanOperation constraints[i].Usable = true } // i >= len(ci):计划缺失,Column=0/Op=0(OpIN)/Usable=false, // tCursor.Filter 需针对 OpIN 单独放行(不依赖 Usable 判断)。 } 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 time.Time: ctx.ResultText(v.Format("2006-01-02 15:04:05")) case string: ctx.ResultText(v) case []byte: ctx.ResultBlob(v) default: ctx.ResultText(fmt.Sprint(v)) } }