# database/sql 驱动(vrtxsql) `DB/driver` 包实现了 Go 标准库 `database/sql/driver` 接口,让任意基于 `database/sql` 的工具与 ORM(xorm、sqlx、自研 ORM 等)直接对接 VRTX 引擎, **无需引入任何第三方依赖**(解析器为内置的 ORM 子集翻译器)。 ```go import ( "database/sql" _ "git.ycl.pub/yl32940/TsDB2/DB/driver" // 注册 "vrtx" 驱动 ) db, err := sql.Open("vrtx", "/path/to/data.db") // DSN = 数据库文件路径 if err != nil { ... } defer db.Close() db.SetMaxOpenConns(1) // 引擎单写者模型: 1 路连接即可 (见下文"连接与并发") ``` 同一进程内相同路径的多个 `sql.DB` **共享同一个引擎实例**(注册表 + 引用计数), 不会出现一个库被打开成两个实例的撕裂。 完整可运行示例见 `demo/sql_driver`。 --- ## 支持的语句 ### SELECT ``` SELECT * FROM t SELECT 列1, 列2 FROM t -- 投影 (AS 别名可写, 展示以表列名为准) SELECT ... FROM t WHERE 条件 SELECT ... FROM t WHERE 条件 ORDER BY 列 [ASC|DESC], ... LIMIT n [OFFSET m] SELECT COUNT(*) | COUNT(列) | SUM(列) | MIN(列) | MAX(列) | AVG(列) FROM t [WHERE ...] ``` **JOIN**(INNER / LEFT [OUTER],等值条件,链式多表): ``` SELECT 列清单 FROM 表1 [AS] a JOIN 表2 [AS] b ON a.x = b.y -- INNER JOIN 可省 INNER LEFT JOIN 表3 [AS] c ON b.m = c.n [WHERE 条件] [ORDER BY ...] [LIMIT n [OFFSET m]] 列清单: * | t.* | 列名 | t.列名 -- WHERE/ORDER BY 同样支持限定名 ``` 执行策略:左表全扫描(本表条件下推索引)→ 逐行取 ON 左列值对右表做 `Eq` 索引点查的嵌套循环;INNER 无匹配丢行、LEFT 补全 NULL;跨表条件 (列比较 / 跨表 OR)作残余谓词在组合行上过滤;投影后内存排序再 LIMIT。 非等值 ON / RIGHT / FULL JOIN 明确报错;JOIN+聚合暂不支持。 WHERE 条件族(`?` 为参数占位符,字面量同样可用): | 语法 | 说明 | |------|------| | `= != <> < <= > >=` | 比较;`NULL` 参与比较恒不成立(SQL 语义) | | `AND OR NOT` | 逻辑组合,支持 `( ... )` 分组 | | `列 IN (v1, v2, ...)` / `NOT IN (...)` | 走索引展开定位,不退化全表扫描 | | `列 IS NULL` / `列 IS NOT NULL` | 按行内 NULL 位图判定 | | `列 LIKE '前缀%'` / `NOT LIKE` | 通配符 `%` `_` | ### INSERT ``` INSERT INTO t VALUES (...) INSERT INTO t (列...) VALUES (...), (...) -- 多行 INSERT INTO t (列...) VALUES (?, ?) -- 参数占位 ``` - 提供了主键列的整数值 → 该行就使用该主键号(`LastInsertId` 返回它); 未提供 → 引擎自增分配。 - **省略的列自动置 NULL**:可空列写入 NULL 位;非空列缺值报错(与 SQL 一致)。 ### UPDATE / DELETE ``` UPDATE t SET 列=值, ... [WHERE 条件] DELETE FROM t [WHERE 条件] ``` WHERE 定位到行号后逐行生效,`RowsAffected` 真实。省略 WHERE = 作用于全表。 ### DDL ``` CREATE TABLE [IF NOT EXISTS] t ( 列 类型 [(参数)] [NULL|NOT NULL] [UNIQUE] [PRIMARY KEY] [DEFAULT NULL] ..., [PRIMARY KEY (列)], [KEY|INDEX 名 (列)], [UNIQUE KEY 名 (列)] ) CREATE [UNIQUE] INDEX 名 ON t (列) DROP TABLE [IF EXISTS] t ``` 列类型映射: | SQL 类型 | 引擎类型 | 备注 | |----------|----------|------| | `BIGINT` (+`UNSIGNED`) | `Int64` / `Uint64` | 主键列必须是整数类型 | | `INT/INTEGER` `SMALLINT` `TINYINT` (+`UNSIGNED`) | `Int32/16/8` / `Uint*` | | | `DOUBLE` `FLOAT` | `Float64/32` | | | `BOOL` `BOOLEAN` | `Bool` | | | `VARCHAR(n)` `CHAR(n)` | `String`(定长档) | n ≤ 2048;超出请用 TEXT | | `TEXT` | 动态列 | 不可建索引;单值上限 16360 字节,超出请用 BLOB | | `BLOB` | Blob 列 | 行内 8B 引用 + 链页(单值 ~4GB);SELECT 自动物化读出内容字节 | | `DECIMAL(p,s)` | `Decimal` | s 落列级参数 | | `DATETIME` `TIMESTAMP` | `Uint64` | 约定:毫秒时间戳 | | `UUID` | `UUID` | | 列级 `AUTO_INCREMENT` / `COMMENT '...'` 可写(被忽略);`DEFAULT` 仅支持 `DEFAULT NULL`,其它默认值报错(避免静默错配)。 ### 维护语句 ``` VACUUM ``` 整库重建:回收孤儿页 / 重建全部索引 / 收缩文件。行数据从旧库逐行复制, 索引从行数据重新生成,所以它也是"索引出问题时"的修复手段(等价于 SQLite 的 `REINDEX` + `VACUUM` 合体)。注意: - 走 `Exec` 执行(`db.Exec("VACUUM")`),`RowsAffected` 返回复制的行数; - 执行期间引擎会做 Close/Open 句柄切换,**并发语句自动排队**(驱动串行锁),无需停应用; - **活跃事务中拒绝**(`ErrTxnActive`)——先提交或回滚再执行; - 只读(force-inspect)模式下报错,不会动文件。 --- ## 不支持的语句(明确报错,不静默) | 类别 | 内容 | |------|------| | 多表 | 除 INNER/LEFT 等值 JOIN 外的连接形式(RIGHT/FULL JOIN、笛卡尔积) | | 嵌套 | 子查询、`INSERT ... SELECT` | | 分组 | `GROUP BY`、`HAVING`、`DISTINCT` | | 函数 | WHERE/SELECT 中的任意函数表达式(`UPPER`/`CASE WHEN`/`COALESCE(SUM(..))` 等) | | 索引 | 多列(复合)索引、复合主键 `PRIMARY KEY (a, b)` | | 变更 | `ALTER TABLE` 全部 | | 语法糖 | `BETWEEN`(用 `>= AND <=` 代替)、`ON DUPLICATE KEY UPDATE`、`REPLACE INTO` | | DDL 附加 | `DEFAULT` 非 NULL 值、VIEW/TRIGGER/过程 | | 其它 | 事务隔离级别设置、`||` 字符串拼接(SQLite 方言,用 `LIKE` 或多条件代替)、`ON CONFLICT`/`ON DUPLICATE KEY UPDATE`(UPSERT)——驱动会返回明确报错,GORM 侧请用 `Updates` 代替 `Save` | --- ## 语义与类型约定 - **NULL**:省略列 = NULL;`NULL` 参与任何比较恒不成立;聚合的 `AVG` 分母 是"该列非 NULL 的行数";`COUNT(*)` 数全行。字符串结果统一以 `[]byte` 返回 (`Scan` 到 `string` / `[]byte` 均可),NULL 扫描为 `nil`。 - **时间**:`time.Time` 参数按毫秒时间戳存入 `DATETIME` 列;读出走引擎类型。 - **事务**:`db.Begin()` / `tx.Commit()` / `tx.Rollback()`,语义即引擎的 `BeginTransaction`;BEGIN 会把此前自动提交挂起的修改先落盘(SQL 标准语义: BEGIN 是"之前工作已提交"的边界)。事务内的 DDL 报错(引擎 DDL 隐式提交语义)。 - **错误传播**:唯一索引冲突、非空约束、语法错误、不支持的语句——全部以 error 返回,错误信息含具体列/索引/语法点。 ## 连接与并发 引擎是**单写者模型**。驱动内所有操作经一把全局锁串行化:同一时刻只有一个 操作在跑,事务期间其它连接的操作排队等待(拥有事务的连接自身不会被卡死)。 因此 `SetMaxOpenConns(1)` 是最优配置;不设也能正确运行,只是并发请求会排队。 ## 已知限制 - SELECT 结果集**全量物化**进内存(大结果集占用内存,后续可换流式)。 - **动态列单值上限 16360 字节**(`types.MaxStringSize`):TEXT 列的内容整块落进一个 堆页,更大的值写入即报错。两条出路:新表大文本直接建模成 BLOB 列;存量库走 `migrate` 迁移时,采样发现 TEXT 源列超过该上限会**自动落成 Blob 链式列**(行内 8B 引用 + 链页,单值 ~4GB),并在迁移报告里写明。 - **BLOB 列 SELECT 自动物化**:行内只有 8B 链引用,驱动在持锁期间读链页把内容 字节放进结果集(单表流式与 JOIN 两条路径都接上了),`Scan` 到 `[]byte`/`string` 均可;压缩标志不持久化,驱动按不压缩落链、同值读出(聚合 `MIN/MAX` 命中 BLOB 列仍明确报错)。引擎层面空 blob 与 NULL 同态(行内位图承载 NULL,空引用视为 "读不到"),读出均为 NULL。 - 时间值约定:驱动与 gormvrtx 的 `DATETIME` 列是引擎的 `DateTime` 类型 (行内 uint64 毫秒);GORM 把时间值内联成 SQL 字面量传入,驱动会解析 `'YYYY-MM-DD HH:MM:SS[.fff]'` 形式,**零时间(`0001-01-01...`)按 NULL 写入**(软删除 `DeletedAt` 语义);纯毫秒数值同样接受。 - 时序表(行存储为时序索引)同样可经驱动写入:主键列声明 `DATETIME` 且按时间非递减写入即可。 ## GORM 接入(gormvrtx 模块) 仓库提供官方风格的 GORM Dialector:`gormvrtx/` 是独立子模块(自带 `go.mod`,主仓库保持零依赖): ```go import ( "gorm.io/gorm" vrtx "git.ycl.pub/yl32940/TsDB2/gormvrtx" ) db, err := gorm.Open(vrtx.Open("/path/to/data.db"), &gorm.Config{}) ``` - **CRUD / 查询 / 事务 / Preload**:走 GORM 生成 SQL → vrtx 驱动执行, `Joins()`(等值)同样可用。 - **AutoMigrate 真正可用**:`gormvrtx` 实现了 GORM 的 Migrator 接口, schema 自省与变更**直连引擎元数据 API**(不经 SQL):建表 / 加列 / 删列 / 唯一与普通索引的自动比对与补齐都能跑通(`AlterColumn` 改类型、 重命名等少数操作返回"不支持")。**无 size 的 string 字段默认落成 BLOB 链式列** (不定长大文本不再撞 16360 上限;代价是失去 LIKE/索引能力,需要可搜索文本时 给 `size:1..2048` 走定长档);带索引标签却落成不可索引列(动态列 / Blob 列)的字段,建表阶段即给出可操作的中文提示。 - 整型主键零值自动预分配并回填对象(对齐自增语义)。 - **版本与 `Save`**:按 gorm.io/gorm v1.31 开发验证;`Save()` 在 v1.31 生成 `INSERT ... ON CONFLICT` upsert 语法,驱动不支持(明确报错), 请用 `Select("*").Updates(...)` 代替。 演示:`cd gormvrtx/demo && go run main.go`。 ## 测试与演示 - 驱动测试:`go test ./DB/driver/`(CRUD/查询全链路、事务、不支持语法、 共享引擎共 4 组)。 - 演示:`cd demo/sql_driver && go run main.go` —— M1/M2/M3 全特性走查。