docs(G6): 宣称与实现一致性收口 —— priority 真正生效、MVCC/backup/引擎数/打包 全部对齐

独立核验(12 条宣称逐条对源码验证)发现 5 处**硬伤**与 2 处**数字过期**,
本提交按"能改代码就让宣称成立、改不动就如实描述"的原则全部收口。

让实现符合文档(2 处):
1. **插件 priority 此前不生效** — `register()` 虽按 priority 插入数组,但
   `install()` 在 register 内**立即**执行,因此 install 与钩子顺序 = config 数组
   顺序(实测 priority low=1/high=100/mid=50 时钩子按 low→high→mid 触发,
   只有 `getPlugins()` 是 high,mid,low)。而 README/CONTRIBUTING/constants
   一直宣称"越大越先执行"。
   现在 Core 注册前按 priority **稳定降序**排序(同优先级保持数组顺序),
   install 与钩子都按优先级执行 → 宣称成立。新增
   `tests/v080-plugin-priority.test.ts` 锁定 install 顺序、钩子顺序、稳定性、缺省值。
2. **连接池静态方法不在类型系统里** — `MetonaSqlark.connect/disconnect/
   disconnectAll/getActiveConnections` 由 connection-manager 用
   `as unknown as Record<string, unknown>` 注入,README 的连接池表格在
   TypeScript 下全部 TS2339。现在在类上声明为可选静态成员,注入处去掉断言。

如实描述(3 处):
3. **MVCC 快照隔离**(README 三处 + 实现对照)— `snapshotLsn` / `prevVersion`
   只写不读,事务读走 `txnSnapshot`+LSM,commit 即清理版本链,并发
   `beginTransaction` 抛 `TX_ACTIVE`。改为"快照回滚(事务串行,非 MVCC 隔离)",
   并在 README 架构图与维护语句表里同步措辞。
4. **"存储引擎(5 种)"** — 实际是 4 种模式 + 3 种后端,引擎类只有 4 个
   (Memory / KVStore / Hybrid / Aria),OPFS 是后端而非引擎。标题与条目已改写,
   并写明"`disk`/`hybrid` 恒用 KVStore"。
5. **`diskEngine` 生效范围** — 仅 `mode:'aria'` 生效;`constants.ts` 的注释
   此前写成"仅 mode='disk'|'hybrid' 时生效"(正好写反),已改正;README 配置表、
   快速开始示例与 Aria 示例同步标注。

数字口径统一(可复现):
- 测试 1872(90 套件)+ 14 e2e,另 4 个重型套件在独立 CI job 串行运行;
- 覆盖率 语句 90.43% / 分支 82.21% / 函数 94.27% / 行 93.44%;
- README 明确写出**产出这些数字的完整命令**(与 CI 常规 job 一致),
  并要求改动覆盖范围/阈值时同步更新表格(G5)。
- CHANGELOG 0.8.0 条目与 site 首页/文档页同步。

另修 **CONTRIBUTING 的钩子契约**:明确写出"返回值被忽略(不能取消/改写)、
就地改参数在 Table API 生效、抛异常可取消、SQL 路径的 beforeInsert 收到副本"
—— 此前只写 "allow intercepting",容易被理解为返回值可改变行为。

验证:全量 90 套件 / 1872 测试通过(+4 重型套件);覆盖率四项均高于阈值;
typecheck(src+tests)、lint、build 零错误零告警;e2e 14 项通过;dist 已重建。
This commit is contained in:
thzxx
2026-09-15 08:06:23 +08:00
parent d14663ef80
commit 799560ea05
26 changed files with 35817 additions and 106 deletions
+115
View File
@@ -2,6 +2,121 @@
All notable changes to MetonaSqlark will be documented in this file.
## [0.8.0] - 2026-08-16
### 根治性迭代 —— 统一语义 / 消灭复发结构 / 验证基础设施
> 依据 `PLAN-v0.7.5.md` 的三条并行工作流(A 缺陷修复 / B 结构根治 / C 验证基础设施)
> 完成的一次系统性迭代。**这一版的重点不是"再修一批 bug",而是砍掉让同类 bug
> 必然复发的结构**:多处并存的语义实现被收敛为唯一实现,并第一次让崩溃语义、
> 错误码一致性、入口等价性变成可机器验证的门禁。
>
> 测试规模 1304 → **187290 套件)+ 14 项 e2e**(另 4 个重型套件在独立 CI
> job 串行运行)。
### 工作流 B · 结构根治(消除整类缺陷)
- **B-1 唯一校验 choke point** — 此前有**三份**行校验实现,覆盖面各不相同
memory 一份缺 `maxLength`/`min`/`max`Aria 一份有,schema.ts 第三份):
同一份 schema、同一条 INSERT 是否报错取决于选了哪个引擎(A12);四份实现
对未知列一律静默丢弃(A17:INSERT 报成功、`SELECT nope` 报 COLUMN_NOT_FOUND)。
现收敛为 `src/table/validation.ts#compileValidator` 唯一实现,四个引擎新增
`validatePayload` 契约,校验先于任何副作用;未知列、`NaN`/±Infinity
(JSON 无法表示,落盘会变 null)显式拒绝。
- **B-2 唯一值比较与编码** — `sqlCompare`/`sqlCompareOrder`/`encodeValueKey`
成为唯一原语;GROUP BY 键、DISTINCT 键、UNION 去重、聚合去重全部改用它
(此前四份编码并存,对 null/undefined 处理各不相同)。
- **B-3 单管线** — QueryBuilder 此前**自己执行**:无 JOIN 时直通 `engine.find`
写操作直通 `engine.update/delete`,于是"同一条语义"在 TABLE API 与 SQL API
两条路径上规则各写一份(投影、列校验、LIMIT 下推、`maxRowsPerQuery` 全缺失;
`$subquery` 无人解析 → 静默影响 0 行)。现在 builder 只产出 AST,执行一律经
Executor;生命周期钩子由 `Table` 注入、顺序与传参不变。
→ 新增 `tests/v080-single-pipeline.test.ts`(两入口逐值等价,四引擎)。
- **B-4 统一表达式求值** — CASE 此前用**正则**切分 WHEN/THEN/ELSE,不认字符串
字面量与嵌套:嵌套 CASE 返回字符串残片 `"big' END ELSE 'small"`;条件引用
不存在的列时静默把整列变成 ELSE 值;`GROUP BY CASE ... END` 完全不可用
(报"未知列 CASE WHEN ...")。现复用 `sql/lexer` 的 token 流做递归下降,
条件交给与 WHERE 相同的解析器,无法识别的表达式显式报错。
→ 顺带修正 `Token.position` 语义按类型不一致的缺陷(字符串 token 指向引号之内,
导致按位置切片少一个字符)。
- **B-5 输出列序号 + 分隔标识符** — `ORDER BY 1` / `GROUP BY 2` 此前直接
`PARSE_ERROR``SELECT "1"`(列名就叫 1)被当成**常量 1**(与 `SELECT *` 结论相反)。
现支持输出列序号(越界、`ORDER BY 0``GROUP BY <聚合列>` 各自显式报错),
并统一分隔标识符语义:引号只在"解析→执行"边界脱去。
顺带补上 **ORDER BY 的列存在性/歧义校验**(此前 JOIN 里裸写两表同名列既不报错
也不确定按哪列排)。
- **B-6 存储提交点(KVStore** — 两处 P0:① `open()` 遇损坏日志尾部会**清空整个
日志**(写 3 条 → 第 4 条撕裂 → 重开可见 → 再重开全空);② 自动 checkpoint
失败会让**已确认写入**报错(而该写入已在 WAL 中,报错与事实相反)。另修陈旧实例
的 checkpoint 会**静默抹掉**新实例写入(现抛 `STALE_INSTANCE` 拒绝提交)。
### 工作流 A · 缺陷修复(24 项,含 6 项事故级)
- **A15 三值逻辑** — `= NULL` 命中 NULL 行、`!= NULL` 返回所有非 NULL 行、
`NOT LIKE` 把 NULL 判真、**`NOT BETWEEN 1 AND 2` 恒空集**(字段级 `$or` 递归进了
where 子句级求值器)。现 WHERE 只有**一个**递归求值器;`IS NULL`/`IS NOT NULL`
是与比较不同的**谓词**(此前与 `= NULL` 共用同一 AST,语义无法区分)。
- **A9/A10 发布订阅与关联子查询** — `subscribe()` 对本地写入永不触发(仅跨标签页
广播);`WHERE t.x = t.y``WHERE id IN (SELECT ... WHERE o.user_id = u.id)`
静默空结果(引擎层预过滤把逐行谓词判 UNKNOWN → 候选行 0)。
- **A13 自引用外键** — `parent_id REFERENCES node(id)` 的级联被整体跳过:
`DELETE root` 只删根,子树**永久悬挂**(父行已不在,再也无法级联清理)。
`ON DELETE SET NULL` / `ON UPDATE CASCADE` / RESTRICT 预检同样失效。
- **A22/A23/A25/A26/A27/A29/A30/A36 查询层** — GROUP BY 别名、HAVING 未选中聚合、
带前缀聚合参数恒 0、UNION 尾部子句归属、DISTINCT 作用于输出列、
`maxRowsPerQuery` 静默截断写入、INSERT 值多于列、派生表别名引用。
- **A37 列引用** — 未限定列不能作比较操作数(`WHERE x = y` 报 PARSE_ERROR);
`$col` 引用不存在的列**静默返回空集**。
- **A38/A39 Aria 存储** — `compression` 在页面化路径(默认)被静默忽略;
`compressLZ4` 匹配搜索 O(n²)60KB 伪随机 2345ms → 6ms**390×**)。
- **A41 DDL 原子性** — DDL 的 WAL 意图记录写在生效**之后**:`dropTable` 后崩溃 →
重开表又回来了(DROP 被静默撤销);`alterTable` 完全不写 WAL,崩溃丢失结构变更。
现统一为"先写 WAL 意图并刷盘 → 再改内存 → 最后落盘 schema"。
### 工作流 C · 验证基础设施(让门禁真的能拦)
- **真崩溃注入(PC-2** — e2e 的 `crashPage()` 此前只是 `page.close()`
(**优雅关闭**),所有"崩溃恢复"用例测的其实是"正常关闭后重开"。现改用 CDP
`Page.crash` 终止渲染进程,并新增两个真实窗口:`createWritable().write()` 中途、
`close()` 原子替换前(copy-on-write 的核心不变量)。
- **覆盖率门禁真正生效** — `collectCoverageFrom` 不再排除实现文件
(此前 `!src/**/index.ts` 把 2282 行的 AriaEngine 整文件排除在统计外,
于是"90.1% 行覆盖率"是虚高的口径);新增 `coverageThreshold`
statements 90 / branches 82 / functions 94 / lines 93);CI 常规 job 带
`--coverage`lint 去掉 `continue-on-error`;新增 `tests/` 类型检查
(修复 **103 个**被 babel 剥离类型掩盖的测试类型错误);CI 校验 dist 与源码同步。
→ 实测 Statements 90.43% / Branches 82.21% / Functions 94.27% / Lines 93.44%
(命令与 CI 常规 job 完全一致,可复现)。
- **测试介质忠实性修正**(两处同源缺陷,此前让所有多实例/多库验证跑在错误语义上)
- `SharedMemoryBackend` 的读缓存是每实例私有的 → 介质退化为"每实例一份快照",
跨实例写入不可见(这正是陈旧实例覆盖新实例写入那条 bug 起初查不出来的原因);
- OPFS mock 把 `getDirectoryHandle(name)` 的库名**丢弃** → 所有库共用一棵文件树
`open('db-beta')` 能看到 `db-alpha` 的表)。
- **变异验证成为回归套件的标准做法** — 把修复回退到修复前的行为,对应用例必须
失败。本版 A15 / B-6(①②③) / A13 / A37 / A38 / A39 / A41 / B-4 / B-5 全部通过该检查
——这是"测试真能拦住回归"与"测试只是陪跑"的分界线。
### 文档与宣称同步(G6
- 修正 README 全部**不成立的能力宣称**:覆盖率数字改为**四个准确数字 + 明确口径**;
测试规模与套件数更新为实测值;"5 种存储引擎"改为"4 种模式 + 3 种后端"
`backup()` 由"在线一致性快照"改为"全库导出(逐表读取)"并写入已知限制
(引擎层没有跨表快照原语);`db.disconnect()` 真正进入类型系统
(此前仅运行时注入,TypeScript 使用者编译失败)。
- **打包缺陷修复**`package.json``./migration` 子路径此前指向的产物里
**没有** `migrateFromIndexedDB`(主入口未导出该函数)→ 现主入口导出 + 子路径可用;
`./react` / `./vue` 此前指向**裸 TS 源码**且声明类型为 `any`
→ 现构建 `dist/react.js` / `dist/vue.js` 并配套**手写精确类型声明**
`peerDependencies`react / vue,均可选)。
### 已知限制(v0.8.0 新增/变更)
- `backup()` 不是跨表一致性快照(逐表读取)
- 复合主键仍不支持(建表时 `SCHEMA_ERROR`
- 简单 CASE 形式(`CASE <表达式> WHEN <值>`)不支持,仅支持搜索式
`CASE WHEN <条件> THEN ...`
## [0.7.4] - 2026-08-15
### 写语句子查询 / 约束硬化 / 真惰性流式