Files
MetonaSqlark/CHANGELOG.md
T
thzxx cbe407eb49
CI / test (22.x) (push) Successful in 17m24s
CI / e2e (push) Successful in 10m39s
CI / test (18.x) (push) Successful in 19m5s
CI / test (20.x) (push) Successful in 18m4s
CI / test (24.x) (push) Successful in 23m19s
fix: v0.7.1 API 修复与防御统一 — static create / 未 open 防护 / 原型污染 / lint 清零
- MetonaSqlark.create 静态工厂(README 示例在 ESM/Node 下此前 TypeError),
  独立 create 函数委托静态实现
- close 未初始化防御(engine undefined 不再崩溃)
- AriaEngine hasTable/getTableNames/getTableSchema 统一 ensureOpen
- 事务回滚失败不掩盖原始错误
- __proto__ 列名防护:Executor 列映射 Object.create(null) + schema 校验拒绝
- lint 清零(移除 5 处未使用导入)

测试 1147 → 1155(73 套件);行覆盖率 89.8%;版本 0.7.1
2026-08-13 11:14:42 +08:00

889 lines
69 KiB
Markdown
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.
# Changelog
All notable changes to MetonaSqlark will be documented in this file.
## [0.7.1] - 2026-08-13
### API 修复 / 防御统一 / 工程质量
> 深度审计第四阶段:修复 README 示例 API 缺失、未 open 防护不一致、
> 事务错误掩盖、原型污染防护,lint 清零。
### Fixed
- **`MetonaSqlark.create` 静态工厂缺失(API/文档不一致)** — create 此前仅存在于
api 对象与 window 挂载,README/CONTRIBUTING/site 全部示例的
`MetonaSqlark.create({...})` 在 ESM/Node 下是 undefinedTypeError)。
类增加静态 create(与 connect/disconnect 同入口风格),独立 create 函数委托
- **close 未初始化崩溃** — `new MetonaSqlark(cfg).close()`init 失败/未调用)时
`this.engine` undefined → TypeError。close 增加防御
- **AriaEngine 未 open 防护不一致** — hasTable/getTableNames/getTableSchema 未检查
DB_NOT_OPEN(返回空而非报错,与 KVStoreEngine 及自身其余方法不一致)。
统一 ensureOpen
- **事务回滚失败掩盖原始错误** — rollbackTransaction 抛错会替换掉真正导致事务
失败的异常(定位困难)。回滚失败吞掉,保留原始错误
- **`__proto__` 列名原型污染防护** — SQL CREATE TABLE 的列名 `__proto__` 在普通
对象上触发原型 setter 静默丢列。Executor 列映射改用 Object.create(null)
(键可见),schema 校验层显式拒绝该列名(SCHEMA_ERROR
- **lint 清零** — 移除 5 处未使用导入(backend/file_manager/kvstore_engine/parser
### Changed
- 测试 1147 → **1155**73 套件,+8 个 v0.7.1 回归);新增 `tests/v071-hardening.test.ts`
行覆盖率 89.8%lint 0 error 0 warning
---
## [0.7.0] - 2026-08-13
### 参数化查询 / 事务性能 / 语义硬化
> 深度审计第三阶段:能力补全(参数化查询)、大库事务性能、
> 复合主键语义陷阱显式化、EXPLAIN 真实索引信息。
### Added
- **参数化查询** — `db.query(sql, params)` 位置参数(`?`):
- 绑定在词法层完成(仅替换字符串字面量之外的 `?``''` 转义与原样保留)
- 值按 SQL 字面量安全编码(字符串 `''` 转义 / 数字 / 布尔 / NULL),
注入防护从根上成立(`'x'; DROP TABLE users; --` 作为字面量原样入库)
- 参数数量不匹配 → `PARAM_ERROR`;对象/数组参数显式拒绝(SQL 方言无 json
字面量,防静默类型错配);NaN/Infinity → NULL
- **EXPLAIN 真实索引信息** — `usingIndex` 从 'auto' 占位升级为真实命中判定:
主键条件 → `pk`、索引/唯一列条件 → `index:col`、无索引 → `none`
(引擎无关启发式:按 schema 标记判定,与各引擎 tryIndexLookup 路径一致)
### Changed
- **KVStoreEngine 事务增量 flush(性能)** — commit 时此前对每个 dirty 表整表
diff(大表事务改 1 行也 O(表大小))。现在行级变更追踪:普通 insert/update/delete
仅写事务内改动行(单次全表扫描 + 变更集合过滤);主键变更/级联影响表整表 diff
兜底;事务内 clear/drop 只删 KV 行;clear 后写入自动切换兜底。实测:1000 行表
事务改 1 行 commit 日志仅 1 条记录 1 个条目(此前 1000 条目)
- **移除每次 commit 的强制全量 checkpoint** — KVStore 按日志阈值自动 checkpoint
(日志重放保证崩溃恢复),close() 时统一 checkpoint 截断(重开更快);
大库高频事务不再 O(库大小)
- **复合主键显式拒绝(语义陷阱)** — 此前 schema 允许多个 primaryKey 但引擎
静默取第一个(其余标记失效)。createSchema 校验期抛 `SCHEMA_ERROR`
(含明确指引);ALTER TABLE ADD 主键列同样防护(防绕过建表校验)。
复合主键列入 v0.8 路线图
### Fixed
- 事务级联影响表漏写 — delete/update 的级联影响表加入 txFullTables 但未加入
txDirtyTables → commit 循环不处理 → 级联改动(SET NULL 等)不落盘
### Changed(测试)
- 测试 1126 → **1147**72 套件,+21 个 v0.7.0 回归);新增 `tests/v070-features.test.ts`
(复合主键拒绝 ×3 / 参数化 ×7 / 事务增量 flush ×8 / EXPLAIN ×2
---
## [0.6.3] - 2026-08-13
### 原子性 / 一致性 / 资源治理
> 深度审计第二阶段:兑现"单记录真原子"宣称、补齐 WAL 失败可见性、
> 修复级联/索引残留/内存泄漏类问题。
### Fixed
- **KVStore 混合写非单记录原子(P2,宣称失实)** — delete/update 主键变更等路径
putMany 与 deleteMany 分两次调用 = 两条日志记录:崩溃在两条记录之间 → 新旧行并存
(主键变更产生重复行 / SET NULL 重写与父行删除不一致)。新增 `KVStore.writeBatch`
put+delete 编码进同一条日志记录,真全有或全无),KVStoreEngine 全部混合写路径
update/delete/dropTable/alterTable/clear/commitTransaction)统一走 writeBatch
- **WAL full 模式写入失败吞错(P2** — append/appendBatch 失败仅 console.warn
内存已提交而 WAL 缺失,崩溃即丢且调用方无感知。现在失败直接抛出(batch 模式
flush 早已抛错,行为对齐)
- **MemoryEngine SET NULL 级联索引残留(P2** — 手动 `pks.get(v)?.delete(pk)` 遗留
空 Set → checkUniqueness 对旧值永久误报 UNIQUE_VIOLATION(外键列带 unique 约束时)。
统一复用 removeIndexEntries(空 Set 正确清理)+ updateIndexes
- **delete 级联部分执行(P2)** — 多行删除时第 N 行 RESTRICT 抛错 → 前 N-1 行的级联
子行已删除、父行未删(无事务下数据不一致)。改为两阶段:先对全部待删行做 RESTRICT
预检(沿 CASCADE 链递归),任何违规则整体拒绝;Memory/Aria 双引擎对齐
- **BufferPool 驱逐不清理 pages MapP2** — 驱逐仅清 LRU 链表,pages Map 保留全部
历史页面 → 内存无限增长(256 页 ≈ 1MB 预算形同虚设)。EvictionManager 增加
onRemove 回调,驱逐时同步从 pages Map 移除(容量约束真实生效,重载仍正确)
- **MVCC 已提交版本不清理(P2** — commit 仅标记 committedversionStore 随写入量
无限增长(行数据双份常驻)。快照读取已移除(v0.5.1),版本链仅作事务内 undo:
commit 时直接清理本事务版本(rollback/discardVersions 语义不变,gc 保留未提交链裁剪)
- **LSM.flush 重复入链(P2** — freezeMemtable 会把同一张 immutable 再次入链 →
重复 SSTable2 行 flush 出 3 个文件);且 frozenMemtables 在链执行前被清空 →
并发读短暂看不到冻结数据。修复:入链后立即置空 immutable(防重复),
frozenMemtables 保持到链排空后再清理
- **rollbackToSavepoint 不重建二级索引(P2)** — 事务内直写索引 LSMsavepoint 回滚
只还原快照 → savepoint 之后的过期索引条目残留。回滚后重建受影响表索引
### Changed
- 测试 1114 → **1126**71 套件,+12 个 v0.6.3 回归);新增 `tests/v063-fixes.test.ts`
MVCC 语义测试同步改写(commit 清理版本、gc 裁剪未提交链)
---
## [0.6.2] - 2026-08-13
### 深度审计修复(数据正确性专项)
> 全源码通读 + 针对性实验验证,修复 6 个测试盲区中的数据丢失/查询错误/约束缺失问题。
### Fixed
- **KVStoreEngine 数值主键 update 丢行(P0)** — 非主键更新路径把受影响主键 `String()` 化后按
`where { [pkCol]: pk }` 回查内存行,数值型主键(123 !== "123")不命中 → 行被误判删除 →
重启后该行永久丢失。改为单次全表扫描 + 受影响集合过滤(同时消除 O(N×M) 逐主键回查开销),
字符串/数值主键语义统一
- **update 主键变更撞已有主键静默覆盖(P0** — MemoryEngine / AriaEngine 更新主键为目标已
存在值时静默覆盖另一行(数据丢失)。现在抛 `DUPLICATE_KEY`(与 insert 语义对齐),
事务内路径(txnSnapshot + 主 LSM 双查)同样拦截
- **Aria 二级索引范围查询边界算法错误(P1)** — `$gt/$gte/$lt/$lte``Number(v)±1` 构造边界
key:小数数值($gt:2 → "3:",漏 2.5)与字符串("NaN:" 前缀错位,数字/大写开头值被漏)
静默丢数据。改为全索引扫描 + 行级 matchWhere 过滤(与主键范围路径同方案),边界语义与
where-matcher 完全一致;新增小数/字符串/大写/数字开头/整数 flush 前后一致性回归
- **Aria 索引列 IS NULL 返回空(P1** — `$eq: null` 走索引路径时 `String(null)="null"` 查找
返回空并短路全表扫描 → 索引列 IS NULL 恒空。null 等值/含 null 的 IN 列表不再走索引
(回退全表扫描),与全表扫描语义一致
- **AriaEngine unique 约束未强制(P1** — 此前 insert/update 仅检查主键重复,唯一列重复值
被静默接受(MemoryEngine 已强制)。补齐:insert 整批预检(批内互查 + 索引 LSM 前缀扫描,
失败整批不落库);update 排除自身旧索引条目后检查;null 不受唯一约束(对齐 Memory);
新增 LSM.prefetchPrefixRanges(批量前缀范围预加载,一次 drainChain 避免逐行性能悬崖)
- **非主键 update 索引旧值残留** — update 时旧行未传给 `updateSecondaryIndexes`(仅主键变更
时传),索引 LSM 旧条目累积 → 唯一性检查误报 / 索引存储膨胀。统一传旧行清理旧值
- **EXPLAIN 写语句产生真实副作用(P2)** — `EXPLAIN DELETE/UPDATE/INSERT` 此前真实执行语句
(删/改数据)。现在仅 SELECT 类执行(只读),UPDATE/DELETE 用 count 估算影响行数,
INSERT/DDL 仅输出计划
### Changed
- 测试 1092 → **1114**70 套件,+22 个 v0.6.2 回归);新增 `tests/v062-fixes.test.ts`
(数值主键持久化 ×3 / 主键碰撞 ×3 / 索引范围 ×4 / IS NULL ×3 / unique ×7 / EXPLAIN ×2
---
## [0.6.1] - 2026-08-10
### 生产可用性深度审查(异常场景)
> 以"生产可用"标准全面审查:修复级联环无限递归、多表事务原子性缺口、
> 统一未 open 防护,并新增 27 个异常场景测试。
### Fixed
- **页面 id 崩溃回退导致静默丢数据(P0** — FileManager `allocatePageIds` 在分配页面后、
`saveMeta` 持久化 nextPageId 前崩溃 → `__aria_meta` 停留在旧水位 → 恢复时页面 id 复用
(覆盖崩溃前旧页面)→ 恢复期 compaction 按 meta.pageIds 删除"旧"SSTable 时误删被复用的
新数据页面 → 崩溃恢复后静默丢 75%(kv 后端 2 万行 + 加密 + 双索引场景复现 5228/20000)。
修复:`init` 以"现存最大页面 id + 1"为准(单调不回退,绝不复用已存在页面)
- **LSM.flush 剩余数据与 compaction 并发写 metaP0** — `flush()` 对 immutable/memtable
的剩余落盘直接 await(不在链上),与链上 compaction 并发写 SSTable metacompaction 产物
`saveMeta` 后,memtable flush 的 `saveMeta` 读到中间态列表(含 compaction 产物)→ 覆盖产物
引用 → compaction 产物变孤儿 → 主表/索引数据静默丢失(kv 后端优雅关闭后重开丢 75%)。
修复:剩余落盘挂链串行(memtable flush 在 compaction 之后),meta 无竞态
- **生产矩阵审计**(13 个组合全量验收):后端(opfs/kv/memory)× 特性(页面化/加密/压缩/
WAL 开关/同步模式)× 功能(CRUD/双索引/事务/主键变更/级联删除/崩溃恢复)——
新增 `tests/engine/aria-matrix-audit.test.ts`,暴露并修复上述 2 个 P0
- **二级索引范围扫描漏读尾部数据(P0)** — SSTable 索引块记录的是"块内最后一个 key"
builder 约定),读端 `locateBlockLE` 却按块首 key 语义二分:范围查询 endKey 落在
某块尾部时,下一块(尾 key 越界但首 key 仍在范围内)被排除 → 二级索引条件查询
静默丢数据(5 万行写入查 `tag='t5'` 丢 106~771 条,主表全扫描完整)。
修复:rangeScan 的 endBlockIdx 多扫一个块(条目级过滤兜底,不丢不错);
新增 4 个 SSTable 边界回归(跨前缀尾部/精确块尾/超出全量/多前缀一致性)
+ 重建 3 个二级索引大数据量回归(5 万行/高频小批量/多索引列,含崩溃恢复)
- **checkpoint 未落盘二级索引(P1** — checkpoint 只 flush 主 LSM,截断 WAL 后崩溃时
索引 memtable 未落盘、WAL 为空跳过重建 → 二级索引静默丢失最后一批条目。
checkpoint 的 flushAll 同步 flush 全部二级索引 LSM
- **kv 后端页面化缺失** — `pageStorage` 默认只对 opfs 生效;kv 后端整 value SSTable
(4MB)超过 1MB 页面缓存时每次读取全量重载。kv 后端同样启用页面化,
读放大消除;SharedMemoryBackend 改为 chunk 列表(append O(1)+ 惰性拼接缓存
- **MemoryEngine 级联环无限递归(P0** — A→B→A 循环引用 + CASCADE 删除导致栈溢出
RangeError: Maximum call stack size exceeded)—— MemoryEngine.cascadeDelete 无环路保护,
AriaEngine 已有 visited 保护(v0.4.1),Memory 侧缺失。新增 visited 集合(表:主键),
与 Aria 语义对齐;A→B→A 环 + 深链 A→B→C→D 均有回归测试
- **KVStore 快照损坏水位 bugP0** — baseSeq = max(metaSeq, snapshotSeq) 在快照损坏
回退全量日志重放时,meta.seq(最后一次 checkpoint 水位)错误跳过日志中 checkpoint 后
的有效记录 → 静默丢数据。水位只信任快照内嵌 seq
- **多表事务 commit 非原子(P1** — 事务 commit 逐表 flush:中途失败/崩溃 → 部分表已提交。
重构为全部 dirty 表合并单次 putMany/deleteMany(一条日志记录 = 真原子)
- **级联更新/删除非原子(P1)** — update 主键变更/delete 的级联影响表逐表 diff 分多次写。
重构 collectTableDiff 合并到单次原子写(主表 + 全部级联表同一条日志记录)
- **未 open 防护不一致** — getTableNames/hasTable/getTableSchema 未检查 DB_NOT_OPEN
(返回空而非报错,与其余方法不一致)。统一 ensureOpen
- **测试盲区** — 快照损坏测试此前篡改未生效(close 后介质不可读),从未真正覆盖损坏路径;
修复并新增 metaSeq 超前回归测试
- **批量插入性能悬崖(P1)** — insert 循环内逐行 `lsm.prefetchKeys([key])`
每行 `await drainChain()` 排空后台链:后台 compaction 在链上数秒时每行阻塞数秒 →
10 万级插入从 ~5ms/批暴跌到 8~11s/批(kv 后端 10 万行共 353s)。
修复:批级预加载本批全部 PK(一次 drainChain),循环内 `lsm.get`
memtable/flush 产物(自动入缓存)兜底,完整性与批量语义不变。
实测:kv 后端 10 万行 **353s → 12.5s**28 倍),opfs 后端 25s
新增 kv/opfs 双后端 10 万行回归(性能护栏 + 索引完整 + 崩溃恢复)
### Added
- **AriaEngine 可选自研 KVStore 后端**`storageBackend: 'kv'` / `diskEngine: 'kv'`):
- aria 完全跑在自研存储栈上(KVStore 日志+快照),不再依赖浏览器 OPFS APINode/测试用 SharedMemory 介质)
- KVStore 新增 APPEND 日志操作类型:aria WAL 分片追加 O(chunk) 高效,恢复按序拼接,checkpoint 快照含最终值
- writeMany/deleteMany 经 KVStore 单日志记录真原子(此前 OPFS 逐文件写)
- aria 崩溃恢复 = KVStore 双层恢复 + WAL 重放;二级索引/页面化兼容(kv 后端 SSTable 整 value 存储)
- 10 个 aria+kv 集成测试(后端单元/CRUD 持久化/WAL 追加恢复/checkpoint 混合/索引/大数据崩溃/高层 API 事务级联)
- 27 个生产异常场景测试(`tests/production-abnormal.test.ts`):
- KVStore 边界:空 value/空 key、1MB 大 value、中文/emoji/冒号/超长 key、未 open、
重复 open/close 幂等、clear 后写、1 万小 key、写失败后继续可用
- KVStoreEngine:未 open 全操作、重复 close、不存在表、事务嵌套 TX_ACTIVE、
无事务 TX_NONE、commit 失败回滚(磁盘无部分提交)、多表事务原子性、级联环、
深链级联、主键变更+索引一致性、空表操作、5000 行批量删除持久化
- SharedMemoryBackend 全分支:append 拼接、writeMany/deleteMany、close 后安全、跨实例共享
- 日志解析损坏分支:onCorrupt false 停止、keyLen 越界记录丢弃
- AriaEngine 级联环/深链回归测试(与 Memory 对齐)
### Changed
- 测试 1022 → **1049**(65 套件);行覆盖率 89.15% → **89.33%**
- 事务/级联写入路径:逐表 flush → 单条日志记录原子(性能与一致性双提升)
---
## [0.6.0] - 2026-08-10
### 里程碑:完全移除 IndexedDB,自研 KV 事务存储引擎
> 达成"彻底删除 IndexedDB + 自研类似 IndexedDB 的存储后端"目标:
> KVStore 引擎在 OPFS 之上实现多 key 原子事务(IndexedDB 的核心能力),
> disk 模式全面切换,旧库一键迁移。
### Added
- **KVStore 自研 KV 事务引擎**`src/engine/kvstore/`):
- 多 key 原子写:putMany/deleteMany = 单条日志记录(单文件 COW 原子追加)→ 崩溃全有或全无
- 持久化与崩溃恢复:快照(checkpoint)+ 追加日志,两阶段恢复(快照水位跳过)
- 自愈:快照损坏回退全量日志重放;日志损坏截断至损坏处;`repair()` 清理
- 容错时序:checkpoint = 写快照 → 写 meta → 清空日志(meta 先于截断,任何崩溃窗口不丢数据)
- 写操作与 checkpoint 串行队列(无交错窗口);标准 CRC-32 全程校验
- 介质层:OPFS(浏览器)/ SharedMemoryBackendNode/测试,跨实例共享模拟持久化)
- **KVStoreEngine**disk 模式,替代 IndexedDBEngine + OPFSEngine):
- 内存热路径 + KVStore 原子持久化;读 O(1),写增量/整表 diff 分级
- 事务:内存快照 + commit 原子 flush(受影响表),事务内 DDL 支持(schema 一并持久化)
- 外键级联(CASCADE/SET NULL/RESTRICT)持久化、主键变更、二级索引跨重启恢复
- `reload()` 支持多标签页同步重载(Hybrid 场景)
- **迁移工具** `migrateFromIndexedDB()`:一次性把旧 IndexedDB 库(schema/索引/行)导入新引擎;
旧库不存在/aria 私有格式(SSTable/WAL)明确报错;目标库已有表跳过不覆盖
- **可靠性强化**
- 10 万 key 写入+checkpoint+重开全量验证(928ms
- 5 万混合操作+崩溃模拟零丢失
- e2e 新增写入中途崩溃 / checkpoint 前后崩溃注入 / KVStoreEngine 真实环境持久化-索引-事务(12 用例)
- **`MetonaSqlark` 配置**`diskEngine` 类型收敛为 `'opfs' | 'memory'`(默认 'opfs'
### Removed
- `src/engine/indexeddb.ts`840 行 IndexedDBEngine)—— 版本冲突/blocked 重试/onversionchange/flushToIDB 全部消失
- `src/engine/opfs.ts`375 行 OPFSEngine)—— 能力被 KVStoreEngine 覆盖
- `IndexedDBBackend`aria 后端)—— aria storageBackend 收敛为 'opfs' | 'memory'
- 26 个测试文件的 fake-indexeddb 依赖改造为 KVStore SharedMemory / OPFS mock
### Fixed
- **Hybrid 多标签页重载失效**KVStoreEngine 读内存 vs 磁盘引擎读穿透)—— 新增 `reload()` 从介质重载
- **事务内 DDL schema 不持久化**—— commit 时统一 persistSchema(重启后表结构完整)
- **repair 缓存掩盖损坏**—— 先清 BufferPool 再校验(缓存中"完好页面"不再掩盖磁盘损坏)
- **dropInvalidSSTable 孤儿页面**—— 先删数据文件再删 meta(页面化删除依赖 pageIds 定位)
### Changed
- 测试 1009 → **1022**64 套件;+58 新增 kvstore/kvstore-engine/压力/迁移测试,-46 删除/改造 IDB 测试)
- 行覆盖率 87.25% → **89.13%**
- 浏览器支持收敛到 OPFS 矩阵(Chrome/Edge 102+、Firefox 111+、Safari 15.2+);Node 用内存介质
- fake-indexeddb 保留为 devDependency(仅迁移工具测试用)
---
## [0.5.1] - 2026-08-10
### 深度审查:消除假实现/半成品/死代码
> 对全部源码做系统性审查,修复"宣称存在但从未接线"的假实现、补齐无入口的半成品、
> 删除无引用的死代码。
### Added(假实现接线)
- **CRUD 生命周期钩子真实接线** — 此前 `beforeInsert`/`afterInsert`/`beforeUpdate`/`afterUpdate`/`beforeDelete`/`afterDelete`
六个钩子仅存在于类型与文档宣称("14 种钩子"),从未被任何路径触发。现已在两条路径真实触发:
- Table API`table().insert/insertMany/update().execute()/delete().execute()`
- SQL`db.query("INSERT/UPDATE/DELETE ...")`
参数与文档一致(beforeInsert: rows[]、afterUpdate: query, updates, count 等);事务路径不触发(Table 无 hooks 回调,与现状一致)
- **EXPLAIN SQL 入口** — 此前 executor 有 EXPLAIN 分支但 parser 无法解析(无 token),只能手工构造 AST 测试。
补齐 `EXPLAIN <statement>` 语法(tokens/lexer/parser),任意引擎可用
- **ANALYZE / REINDEX / VACUUM SQL 入口** — 此前仅有 AriaEngine 方法(`(engine as any)` 才能调用),README 宣称能力但用户无法触发。
补齐 `ANALYZE [TABLE] name` / `REINDEX [TABLE] name` / `VACUUM` 语法;不支持的引擎抛 `NOT_SUPPORTED`
- **SAVEPOINT SQL 入口** — 补齐 `SAVEPOINT name` / `ROLLBACK TO [SAVEPOINT] name` / `RELEASE [SAVEPOINT] name`
`ROLLBACK [TRANSACTION]` 保持原语义
- **db.backup() 公共方法** — 引擎级 backup(MVCC 一致性视图)此前无公共入口;Aria 走引擎级,其余引擎回退 exportAll
### Removed(死代码)
- **`src/utils.ts` 整文件** — generateId/escapeHTML/deepMerge/debounce/throttle/isBrowser 在 src 内零引用
(v0.0.1 遗留,仅测试锁定);连同 tests/utils.test.ts 删除
- **`estimateQueryCost`(未接线的"查询优化器")** — 实现与测试存在但从未接入查询路径(find 用 tryIndexLookup 硬编码),删除消除"优化器存在但没用"假象
- **MVCC 读侧全套** — readVersion/getLatestCommittedVersions/clearTable/getActiveTxnCount/isActive/getActiveWriteKeys
引擎事务读取走 txnSnapshotMVCC 仅作 undo log),读侧实现从未执行。删除并改写相关测试为验证版本链清理/GC 行为
- **统计/辅助死方法** — EvictionManager.getAllPages/getSize/popLRU/getCapacity、BufferPool.getCachedPageCount/getDirtyPageCount/getCapacity/newPage、
WAL.isEnabled/getLSN、CheckpointManager.forceCheckpoint/setInterval/getOpCount、LSM.isInitialized/getCacheSize/getCacheLimit、
SSTableReader.getMeta/getIndexBlockCount/getStoredChecksum、BloomFilter.getBitSize/getInsertedCount、MemTable.contains、
FileManager.allocateTableRootPage/createEmptyPage、OPFSEngine.loadTableIntoMemory、QueryExecutor.setMaxRowsPerQuery、
ConnectionManager.getRefCount
- **类型/常量死代码** — STORAGE_MODES/DISK_ENGINES、ColumnEncoding/DEFAULT_BLOCK_SIZE/DataBlock、CallbackEntrySource、StatementType、AggregateExpression、SubqueryExpression
- **Crypto 全局兼容层** — initCrypto/encryptPage/decryptPage/closeCrypto/isCryptoEnabledv0.5.0 后引擎走 EncryptedBackend 实例,全局单例无消费者);删除过时的"全局加密落盘"测试(aria-encryption.test.ts 已覆盖等价路径)
### Changed
- 测试 1022 → **1009**(62 套件;删除 13 个死代码测试 + 新增 22 个 hooks/维护语句测试);行覆盖率 86.68% → **87.25%**
- README 补维护语句表(EXPLAIN/ANALYZE/REINDEX/VACUUM/SAVEPOINT SQL 入口)与 `db.backup()` API
- 14 个生命周期钩子现在全部真实触发(site/docs.html 与 CONTRIBUTING 宣称变为事实)
---
## [0.5.0] - 2026-08-10
### 里程碑:存储后端生产级硬化(自研 OPFS 后端全面落地)
> 本次版本对 AriaEngine 存储层做全链路生产级改造:修复"宣称有校验和/加密/页面式存储但实际未实现"的失实实现,
> WAL 重构为分片文件(消除多文件原子性与文件爆炸问题)、SSTable 页面化物理存储(BufferPool/FileManager 真正接入)、
> 多标签页独占锁、repair 自愈增强,并引入真实 Chromium e2e 验证。
### Added
- **全库 AES-256-GCM 加密(真实接线)** — `AriaEngineConfig.encryption.password`
- `EncryptedBackend` 装饰器在 backend 层透明加解密(WAL/SSTable/Schema/元数据全覆盖,每个 value 独立随机 IV + GCM 认证)
- PBKDF2-SHA256100000 迭代)派生密钥,salt + verifier 持久化于 `__aria_keymeta`(密码错误 → `ARIA_DECRYPT_ERROR`
- 加密开关一致性检测:明文旧库 + 加密配置 → `ARIA_ENCRYPT_CONFIG_ERROR`;加密库 + 无密码 → `ARIA_ENCRYPT_REQUIRED`
- clearAll 保留密钥元数据(库身份保留,密码不变)
- **WAL 分片存储重构** — `SegmentedWALStore``__wal_%06d.bin`):
- 取代"每条记录一个 key"旧结构:分片阈值 4MB 自动切换,文件数量可控
- WAL 序号(LSN)内嵌记录字节流,移除 `__wal_count` 键 → append 单文件原子写(createWritable COW
- 空洞检测:分片序号不连续 → 空洞之后保守丢弃(仅可能来自 truncate 部分完成,数据已落盘,无害)
- 旧格式 `__wal_N` + `__wal_count` 兼容:读取迁移重放,checkpoint 统一清空
- **OPFS 后端 v2** —
- `append()` 真追加:`createWritable({ keepExistingData })` + seekO(chunk) 无需读旧内容
- 写队列健壮性:单次任务失败不中断队列链(错误返回给调用方),close 等待队列排空
- open 自动清理崩溃残留临时文件(`.crswap`/`.tmp`
- 原子性文档化:单文件 createWritable 为 copy-on-write 原子替换
- **SSTable 页面化物理存储(BufferPool/FileManager 真正接入)** —
- `PageSSTableStore`SSTable 切分为 4KB 页面,经 FileManager 分配 pageId、BufferPool LRU 缓存
- `save` 语义 = 已持久化(逐页 flushPage 落盘后才返回,WAL checkpoint 截断安全)
- 页面 ID 列表经 `SSTableMeta.pageIds` 持久化;旧 meta(无 pageIds)自动走整 value 读取(兼容)
- OPFS 后端默认启用(`pageStorage` 配置可覆盖);FileManager 批量分配 `allocatePageIds`(一次 meta 持久化)
- 读取缺失页面返回 null(此前返回空页会静默掩盖页面丢失,自愈路径依赖 null 触发清理)
- **多标签页独占锁** — Web Locks APIChrome 69+ / Firefox 96+ / Safari 15.4+):
- `ifAvailable` 模式:第二个标签页打开同一库 → 抛 `ARIA_LOCKED`(不排队挂起)
- 打开失败自动释放锁(不泄漏);close 等待锁真正归还
- 不支持 Web Locks 的环境降级为无锁 + 告警(如实文档化)
- **repair() 自愈增强** — 孤儿页面清理(meta 未引用的 `pg_` 文件)、OPFS 残留临时文件清理、WAL 空洞截断落地
- **LZ4 格式 v2** — 压缩流前置 4 字节原始大小头:解压不再依赖外部估算(高压缩率数据下 `buf.length*2` 估算不足会截断),损坏头显式抛错
- **SSTable 真实 CRC-32 校验和** — footer checksum 字段从写死 0 升级为整文件标准 CRC32(checksum=0 识别旧版文件跳过校验,兼容旧库);打开校验/compaction 加载/运行期预加载三处均校验,损坏文件自动清理自愈
- **WAL 标准 CRC32** — 记录校验从弱滚动校验升级为标准 CRC32;双算法探测(新算法失败再试旧算法)兼容旧 WAL 记录
- **配置透传** — `DatabaseConfig.aria``MetonaSqlark.create()` 可配置 `walSyncMode`/`checkpointInterval`/`encryption`/`pageStorage`/`compression` 等 AriaEngine 专属参数
- **Playwright e2e(真实 Chromium + OPFS)** — 7 用例:持久化(刷新保留)、关闭重开、崩溃恢复(WAL 重放)、多标签页锁、加密密码往返、页面化端到端 + 索引查询、repair 自愈;`npm run test:e2e`
### Fixed
- **加密此前从未接线(严重失实)** — crypto 模块全局单例从未被引擎调用、salt 不持久化重启无法解密;README 宣称的 AES-GCM 实际不可用。改为 EncryptedBackend 装饰器 + keymeta 持久化 + 密码验证
- **SSTable checksum 写死 0(严重失实)** — 数据腐坏(bit flip)完全无法检测。升级为真实 CRC-32,损坏文件打开自愈
- **OPFS writeMany 无原子性(P0** — WAL 记录 + count 多键写入非原子,记录空洞导致后续记录字节错位解析失败。分片重构后 WAL append 单文件原子写 + 空洞检测截断根治
- **OPFS 高频写入文件爆炸(P1)** — 每条 WAL 记录一个文件,8 万条记录 = 8 万次 createWritable。分片存储 + 真追加根治
- **LZ4 高压缩率解压截断(P1)** — `buf.length * 2` 估算原始大小,压缩率 >50% 时截断(SSTable 魔数在文件末尾被丢 → 整库数据丢失)。格式 v2 头部自描述根治
- **OPFS 写队列失败永久中断(P1)** — 队列任务抛错后链 rejected,后续所有写挂起。单次失败不中断链
- **OPFS close 不等挂起写(P1** — close 立即置空目录句柄,挂起写静默丢失。close 等待队列排空
- **Web Locks 方法解构 this 绑定丢失(e2e 发现)** — `navigator.locks.request` 解构后调用抛 Illegal invocation。改用 `.bind(lockManager)` 保持 this
- **页面化 + 崩溃窗口数据丢失(压力测试发现)** — 修复前页面化路径在模拟崩溃测试中丢失已确认写入;根治后 500 随机操作 + 崩溃重放零丢失
### Changed
- `AriaEngineConfig` 新增 `encryption` / `pageStorage``DatabaseConfig` 新增 `aria` 透传
- 页面层精简:删除从未接入主路径的 slot/tuple 模块(假"页面式存储"实现),仅保留生产代码实际使用的 `initPageHeader`/`createPage`;架构宣称如实更新(4KB 页面承载 SSTable 字节切片,非行级 Slotted Page
- LZ4 压缩格式不向后兼容(v0.2.6 已声明 compression 数据需重新导入,本次格式 v2 同理)
- 测试 958 → **1022**(62 套件);行覆盖率 84.2% → **86.68%**;新增 6 套件:crc32 / aria-checksum / aria-wal-crc / aria-wal-segment / aria-opfs-backend / aria-page-store / aria-encryption / aria-locks / aria-repair-hardening / aria-page-core
- 新增 Playwright e2e 基建(`npm run test:e2e`7 用例真实 Chromium
---
## [0.4.4] - 2026-08-09
### Fixed
- **SSTable 编码缺陷导致大内容写入崩溃(P0)** — AriaEngine 保存大段中文内容(如 300KB 笔记)→ close 构建 SSTable 崩溃 → 半写文件 → 下次打开 ARIA_OPEN_ERROR
- **块大小估算用 UTF-16 码元而非 UTF-8 字节** — `computeBlockSize``json.length`(中文 1 字 = 1 码元)估算,实际写入 UTF-8 字节(中文 1 字 = 3 字节)→ 300KB 中文实际 900KB → 缓冲区低估 → `Uint8Array.set` 越界崩溃。改为 TextEncoder 预编码,全部按字节长度计算(切块、块大小、索引块、总大小)
- **valueLen/keyLen 字段 u16 截断** — 长度字段 u16 上限 64KB,大 value 被截断 → 线性格式整体错乱。格式 v2magic "SSTC")升级为 u32 长度字段
- **大 value 块切分** — 单条 value 超过块大小(4096)时独立成块(基于字节估算正确切分)
- **格式兼容** — reader 支持 v1"SSTB"u16)与 v2"SSTC"u32)双格式:旧库 v1 文件仍可读(小 value 场景无缺陷),新写入用 v2,旧库数据不丢失
- 连带受益:中文主键 key、大内容二级索引列(索引 key 含列值)同样支持 u32 长度字段
### Changed
- 测试 950 → **958**52 套件),新增 `tests/v045-hardening.test.ts`8 条:300KB 中文 builder/端到端跨重启、>64KB value、大 value 独立成块、中文 key、v1 兼容、v2 魔数)
- 版本号升至 v0.4.4
---
## [0.4.3] - 2026-08-09
### Fixed
- **close 后后台 compaction 吞错并在 backend 关闭后执行(P0)** — `scheduleCompact``setTimeout(0)` 调度:close 时未触发的定时器在 `backend.close()` 后执行,用已关闭的存储写(错误被吞);close 后立即 reopen 时旧闭包引用新 backend 交叉污染。改为 compaction 直接挂 flushChain 串行执行,close 的 `drainChain()` 排空全部级联任务后再关闭存储
- **后台 flush/compaction 失败完全静默(P0)** — 吞错防死锁的同时记录 `lastBackgroundError`,显式 `flush()`/`close()` 时抛 `ARIA_BACKGROUND_ERROR`(消费一次),不再永久不可见
- **prefetch 与 compaction 竞态丢数据(P0)** — 后台任务在链上动态增长,`prefetchRange/prefetchKeys` 单次 `await flushChain` 后 compaction 仍可能合并 levels,扫描时新 meta 缓存未命中 → 跳块丢数据(缓存驱逐场景 UPDATE/DELETE 少删行)。改为等待链稳定(`drainChain`)再预加载
- **commitTransaction 提交顺序(P1** — 先合并快照后写 WAL COMMIT:WAL 写失败时内存已提交但持久化缺失,崩溃重启丢失。改为先持久化 WAL COMMIT 再合并快照(WAL 始终领先,崩溃恢复一致)
- **OPFS close 不等挂起写(P1)** — 写操作整体串行入队(内存写 + 快照 + 文件持久化),close/repair/clearAll 先等队列排空再释放目录句柄,避免 close 后挂起写读旧数据
- **IndexedDBEngine close 活跃事务防御** — close 时自动回滚活跃事务(避免对已关闭连接操作报错)
### Changed
- 测试 944 → **950**51 套件),新增 `tests/v044-hardening.test.ts`(6 条:close 后台任务排空、reopen 无污染、后台失败可见、链不卡死、WAL 提交顺序、OPFS close 等待)
- 版本号升至 v0.4.3
---
## [0.4.2] - 2026-08-09
### Fixed
- **AriaEngine 残缺 SSTable 导致打开崩溃(P0)** — 异常退出后下次打开抛原生 `RangeError: offset is out of bounds`,库打不开只能删库重建。三处根治:
- `SSTableReader` 读路径防御:块偏移/大小越界时跳过该块(get/rangeScan/scanAll 返回空或继续),索引块/块内条目解析全程边界检查,不再抛 RangeError
- 打开时完整性校验:`LSM.init()` 校验 meta 引用的文件存在、长度 ≥ 头、可解析,残缺的忽略并清理 meta/文件(自愈,不阻塞打开)
- WAL 原子写入:`IndexedDBBackend` 新增 `writeMany`/`deleteMany`(单事务批量写删),WAL 记录与 count 计数同事务提交,中断整体回滚不留半写
- **IndexedDB 版本管理缺陷导致重启必报 VersionErrorP0** — 建表每张版本号 +1 而 `open()``config.version`,重启后版本过低直接失败。`open()` 遇 VersionError 自动用无版本参数探测库当前实际版本并以该版本重开
- **`create({ version: 0 })` 抛原生 TypeErrorP0** — 版本 < 1 统一归一化为 1,不再直接炸 `indexedDB.open(name, 0)`
- **WAL 写丢失(P1)** — 连续快速写入 200 条 close+重开实测丢 4 条。WAL 恢复不再依赖 count 计数,改为扫描全部 `__wal_*` 键(数值排序拼接),count 键只作序号分配器;配合原子写入彻底消除 count/记录竞态
- **AriaEngine.close() 不 checkpointWAL 无限重放(P1** — close 前补 `wal.checkpoint()` 截断,下次打开不再重放已落盘的历史记录
- **引擎内部错误不包装 DatabaseErrorP1** — AriaEngine `open()` 整体 try/catch 包装为 `ARIA_OPEN_ERROR`IndexedDBEngine 打开路径统一 `IDB_OPEN_ERROR`/`IDB_VERSION_ERROR`/`IDB_BLOCKED`,应用层可拿 code 分类处理
- **`migrateTo` 版本门槛与引擎版本脱节(P2)** — 迁移版本持久化到库内(`IStorageEngine.getMeta/setMeta` 可选接口,Aria 存 backend、IndexedDB 复用 `__metona_schema` store、OPFS 存元数据文件、Hybrid 委托磁盘引擎),重启后从持久化版本继续执行,不重跑不跳跑
- **IndexedDB `onblocked` 直接 reject(P2)** — 改为等待后重试(阻塞解除后 success 仍触发,仅超时判失败,并防止连接泄漏),多次重试仍阻塞才抛 `IDB_BLOCKED`
- **缺少官方崩溃恢复/自愈接口(P2)** — 新增 `db.repair()`(校验清理损坏数据并恢复一致性)与 `db.clearAll()`(清空全部数据与表结构,保留库),全部引擎(Aria/IndexedDB/OPFS/Memory/Hybrid)实现,无需删库重建
- **IndexedDB `__metona_schema` store 缺失时元数据无法落盘** — `open()` 时确保 schema/meta store 存在(缺失则一次版本升级创建)
### Fixed(深度审计第二轮)
- **AriaEngine 二级索引跨重启丢失(严重)** — 重开只恢复 schema 不恢复索引 LSM:索引查询静默回退全表、`createIndex` 因 schema 标记已存在而直接 return → 索引永久缺失;且 WAL 崩溃恢复只回放主 LSM,最后一批写入的索引缺失 → 索引查询丢行。三处根治:open 时按 schema 标记重建索引 LSM(数据已持久化直接加载)、close 时同步落盘全部索引 LSM、WAL 恢复完成后全量重建索引
- **AriaEngine Compaction 依赖缓存丢数据(严重)** — `compactLevelAsync` 仅从 LRU 缓存读 SSTable,缓存未命中(单文件超缓存上限被驱逐、频繁 flush)时跳过全部文件并从 levels 移除 → 运行中数据全部不可见(重启才恢复)。改为从存储兜底加载;无有效数据时将文件放回 levels 不删除
- **AriaEngine flush/compaction 失败卡死写路径(严重)** — flushChain 链上任务抛错后永久 rejected,后续所有 flush/compaction 挂起。链上任务统一吞错恢复(记录告警,链继续)
- **ALTER TABLE 在 IndexedDB/Hybrid/OPFS 引擎不持久化** — v0.4.1 只给 Aria 加了引擎级 `alterTable`,其余引擎走通用路径只改内存 schema 引用:重启后 ADD/DROP 全部回退,DROP 的行数据也残留。为 IndexedDB(持久化 schema + 重写存储行)/ Hybrid(双引擎)/ OPFS / Memory 统一实现引擎级 `alterTable`
- **IndexedDB 事务内 DDL 崩溃(严重)** — 事务内建表 commit 时 IDB 无对应 store 报错;事务内删表 IDB store 残留 → 重启幽灵表。`flushToIDB` 提交时 diff 内存表与 IDB store:缺失的创建(含 schema 持久化)、多余的删除(含 schema 记录清理);commit 顺序调整为"先刷盘后提交内存快照",失败可回滚
- **Hybrid 内存/磁盘引擎共享 schema 引用污染** — `reloadMemoryFromDisk` 把磁盘引擎的 schema 对象直接存入内存引擎,任一引擎 ALTER 都改到对方。`MemoryEngine.createTable` 深拷贝 schema(全引擎受益)
- **Hybrid commit 失败路径错误掩盖** — 磁盘已提交后内存提交失败时调 `diskEngine.rollbackTransaction()` 抛 TX_NONE 掩盖原错误;如实上报磁盘已提交状态
- **AriaEngine.close 运行期状态残留** — close 后 MVCC/事务快照/savepoint 残留,重开后 `beginTransaction` 报 TX_ACTIVE。close 统一清理
- **VACUUM 对 2-3 个文件层级不压缩** — `compactLevel` public 门槛与内部自动调度门槛混用(内部 4 / VACUUM 期望 2)。分离参数化
### Fixed(生产就绪审计第三轮)
- **Aria 事务进行中 checkpoint 截断 WAL(严重,P0** — checkpointtick/forceCheckpoint)在活跃事务中途执行时 truncate WALBEGIN/INSERT 记录被截断,事务 COMMIT 后崩溃恢复丢失整个事务数据。CheckpointManager 包装 WAL:事务活跃时跳过截断(close/repair/clearAll 仍正常截断)
- **Aria dropTable / DROP_TABLE 恢复不清理二级索引(P1)** — 索引 LSM 孤儿残留,重建同名表后旧索引数据污染新表(按旧值索引查询返回不匹配行)。新增 `cleanupTableIndexes`dropTable、崩溃恢复、ALTER DROP 索引列统一清理
- **Aria ALTER TABLE DROP 索引列残留(P1)** — 删列但索引 LSM 保留,后续同名列索引数据脏
- **Aria freezeMemtable 阈值逐次衰减(P2** — 新 memtable 用旧表已用大小当阈值,手动 flush 后阈值塌缩 → 频繁小文件。改回配置阈值
- **Aria 事务中 DDL 静默不一致(P2)** — 事务快照只覆盖行数据,createTable/dropTable/alterTable 无法回滚;与 Memory/IndexedDB 可回滚行为不一致 → 显式抛 `NOT_SUPPORTED`(不再静默)
- **ON UPDATE 外键级联未实现(P2,声称支持但静默忽略)** — 实现 `ON UPDATE CASCADE/SET NULL/RESTRICT`Memory + Aria 双引擎):更新主键时级联更新/拒绝,含二级索引与 WAL 记录,先全量 RESTRICT 检查再执行防部分修改;update 同时支持主键变更(旧键删除 + 新键落表)
- **MVCC commit/rollback 全库版本遍历(P2 性能)** — 大表事务 commit O(全库版本数)。按事务记录写入的 key 精准清理(O(写入数))
- **OPFS 空表重启消失 + schema/索引丢失(P1** — 表 schema 持久化到元数据文件:createTable/dropTable/alterTable/createIndex 同步;重启从 schema 恢复(空表保留、主键/索引标记/约束完整),无 schema 的旧库回退数据推断(兼容)
- **OPFS dropTable 不清理 schema 记录(P1)** — 重启恢复幽灵表;删表同步清理
- **OPFS 并发写验证** — 审计确认内存写同步、持久化快照总是最新(单标签页无丢更新竞态),新增并发写回归测试锁定行为
- **VACUUM 返回值语义** — `gcVersions` 返回活跃事务数改为全局提交序列号
### Changed
- 测试 894 → **944**50 套件),新增 `tests/v042-fixes.test.ts`23 条问题清单回归)、`tests/v042-hardening.test.ts`12 条深度审计回归)与 `tests/v043-hardening.test.ts`(15 条生产就绪回归:事务×checkpoint、DDL 索引清理、OPFS 持久化、memtable 阈值、红黑树 5000 次随机压力、onUpdate 级联)
- 版本号升至 v0.4.2
---
## [0.4.1] - 2026-08-08
### Added
- **AriaEngine 外键级联删除** — 对齐 Memory 引擎:`CASCADE`(递归多层)、`SET NULL``RESTRICT`(抛 `FOREIGN_KEY_VIOLATION`),含二级索引清理、WAL 记录、事务内可提交/回滚、环路保护
- **`AriaEngine.clearAll()`** — 重置数据库(清空后端存储/LSM/WAL/MVCC/二级索引),实例可继续使用;供演示页刷新场景与 API 用户使用
- **演示页引擎切换器** — 右上角 `⚡ Memory` / `🌲 Aria` 一键切换,Aria 模式每次加载 `clearAll()` 保证演示确定性;aria 预设文案更新(数据库即 Aria)
### Changed
- 版本号升至 v0.4.1
- 测试 880 → **894**46 → 47 套件)
- `AriaEngine` 级联行为与 MemoryEngine 对齐:`SET NULL` 不影响删除返回行数
### Fixed
- **JOIN 主表 WHERE 不下推** — `SELECT ... FROM orders o JOIN users u ... WHERE o.user_id = '1'` 的 WHERE 只在 JOIN 后过滤,二级索引形同虚设。v0.4.1 将主表前缀等值条件下推到引擎(`extractPushableWhere`),索引真正生效
- **DROP INDEX 不存在的索引静默成功** — Memory/Aria 引擎 `dropIndex` 对无索引列静默返回,改为抛 `INDEX_NOT_FOUND`
- **Memory 索引查找不支持 `$eq` 对象形式** — SQL 解析器生成的 `{ $eq: value }` 等值条件不走哈希索引(仅简单值走),补 `$eq` 分支
- **演示页预设数据被前序预设污染** — 连续点击预设时,cascade(删除 Alice/重建 orders)、truncate、alter 等会修改种子数据,后续预设(如 index)查询返回空。`loadPreset` 点击预设前自动重置数据库到初始状态,每个预设可预期演示
- **Aria ALTER TABLE 不生效(严重)** — `DROP COLUMN` 仅改 schema 引用,`find()` 返回行副本无法就地删除,存储行残留已删列值(SELECT * 仍显示);且 ALTER 不持久化 schema,重启后列定义回退。新增引擎级 `alterTable``IStorageEngine` 可选接口):Aria 重写主 LSM 移除列值 + `persistSchemas` + WAL UPDATE 记录,崩溃恢复后 schema 与行一致
- **裸布尔列条件不支持(严重)** — `WHERE done` / `CASE WHEN done THEN` 解析失败(无比较运算符即抛错),条件被忽略导致 CASE 恒走 ELSE。parser 支持裸列真值判断(`{ col: { $eq: true } }`
- **Aria $in 返回重复行** — IN 子查询含重复值(如 Alice 两条订单)时 PK/二级索引 `$in` 逐值查找重复 push,按主键去重
---
## [0.4.0] - 2026-08-08
### Added
- **流式查询 API** — `db.queryStream(sql, onRow)` / `db.table(name).stream(onRow)` / `IStorageEngine.findStream()`
- Aria 引擎走 LSM `rangeScanLazy` 惰性扫描,逐行回调不物化结果数组(大表友好)
- Memory/Hybrid/OPFS 逐行迭代;IndexedDB 批量读入后逐行回调(保持接口一致)
- 支持 WHERE/LIMIT/OFFSET/列投影;JOIN/GROUP BY/UNION/聚合/ORDER BY 自动回退物化
- 用户 async 回调自动回退物化(同步扫描无法 await,避免吞 Promise
- **多列 ON 哈希连接** — 复合等值条件(`ON a.tenant = b.tenant AND a.key = b.key`)走哈希连接(含顶层 `$and` 展开),任一右列为索引/主键即可;不适用自动回退嵌套循环
- **FROM 子查询(派生表)** — `SELECT ... FROM (SELECT ...) AS alias WHERE ...`,子查询结果作为行源,支持 WHERE/ORDER BY/LIMIT 与 JOIN 组合
- **COUNT(DISTINCT col)** — 聚合去重(COUNT/SUM/AVG/MIN/MAX 均支持),任意类型去重
- **NULLS FIRST / NULLS LAST** — `ORDER BY col ASC NULLS FIRST` 等,NULL 位置固定不受升降序反转
- **普通列别名** — `SELECT name AS n` / `SELECT name n`(此前仅聚合/CASE 支持别名),别名可用于 ORDER BY
- **无表查询与字符串常量列** — `SELECT 1` / `SELECT 'lit' AS x`(无需 FROM),`''` 转义与常量列投影
### Fixed
- **AriaEngine 崩溃恢复后已删除表/数据复活(严重,P0)** — WAL 回放忽略 `DROP_TABLE``CREATE_TABLE` 无条件恢复 schema,删表后崩溃重启表+数据全部复活。v0.4.0 新增 `applyDropTableRecovery()` 回放删除 schema + 清除 LSM 残留
- **WAL 恢复后不截断** — 每次重启重复回放 + WAL 无限膨胀。恢复完成将回放数据落盘后 `wal.checkpoint()` 截断
- **MemoryEngine update 不维护索引(P0** — `update()` 后唯一约束可被绕过、按新值索引查询丢行。`removeIndexEntries()` 在 update/delete/级联删除/SET NULL 前清理旧值索引
- **Aria 事务内读不到自己写入的行** — `update/delete/count/clear``getAllRows()` 不合并 `txnSnapshot`,事务内先 insert 再 update/delete 失效;统一 `mergeTxnSnapshot()`
- **Aria TRUNCATE 不写 WAL** — `clear()` 崩溃恢复后丢失;事务内清空走快照删除标记,提交生效
- **WAL 大小阈值在 full 模式永不触发** — `getBufferedBytes()` 跟踪真实字节数替代缓冲计数估算
- **rollback 后二级索引残留** — 事务内直接写入索引 LSM,回滚后对受影响表全量重建索引
- **Savepoint 回滚 MVCC 不一致** — `discardVersions()` 清理版本链保留事务登记
- **PK 范围查询字符串算术 bug** — `$gt/$gte/$lt/$lte``Number(v)+1` 构造 key 对字符串 PK 失效;改为主 LSM 前缀扫描 + 条件过滤;主键列不再建冗余二级索引(PK `$in` 走多次精确查找)
- **SQL 字符串 `''` 标准转义** — 双引号转义此前从未生效(lexer 条件先于转义分支退出);重写 `readString`
- **INSERT INTO ... SELECT 位置错位(严重)** — 源行 `validateRow` 跳过 undefined 值导致行键缺失/乱序(如 ALTER 加列后省略列插入的行),`Object.values` 位置映射把 age 数字填进 email 列报类型错。改为按源表 schema 列顺序/SELECT 列列表对齐,缺列不填
- **ORDER BY / GROUP BY 表前缀列** — `ORDER BY o.amount` / `GROUP BY u.name` 解析失败(`Expected ';' ... got "."`),parser 支持 `table.column` 引用,executor 非 JOIN 路径剥离前缀
- **关联 EXISTS 绑定失效(严重)** — `SELECT u.name ... WHERE EXISTS (SELECT 1 FROM o WHERE o.user_id = u.id)` 返回空:引擎按 SELECT 列投影后外层行缺 `id` 键,`$col` 绑定为 null。关联子查询路径强制 `plan.columns=['*']` 取完整行;非 JOIN 路径 SELECT 列带表前缀(`u.name`)统一剥离
- **HAVING 标量子查询失效(静默返回空)** — `HAVING SUM(o.amount) > (SELECT AVG(amount) FROM orders)``$subquery` 未解析 + 聚合表达式键(`SUM(o.amount)`)与别名键(`spent`)不匹配。HAVING 先 `resolveSubqueries`,GROUP BY 结果维护「表达式键→别名键」映射做归一化
### Changed
- 版本号升至 v0.4.0
- 测试 837 → **876**(44 → 46 套件),覆盖率 81.1% → **81.5%**Lines 84.7%
---
## [0.3.2] - 2026-08-08
### Added
- **CASE WHEN 用于 WHERE** — `WHERE CASE WHEN cond THEN a ELSE b END = 'x'` 逐行求值(含 NOT/AND/OR 组合)
- **CASE WHEN 用于聚合** — `SUM(CASE WHEN age > 18 THEN 1 ELSE 0 END)``COUNT/AVG/MIN/MAX(CASE...)``GROUP BY + CASE` 非聚合列
- **JOIN 哈希连接** — 等值 ON + 右表主键/索引列时,收集左表连接值 → 一次 `$in` 查询 → 哈希映射匹配,替代嵌套循环(INNER/LEFT JOINO(N+M));不适用自动回退
- **多标签页同步** — `multiTabSync: true` 配置 + BroadcastChannel
- SQL 写语句(INSERT/UPDATE/DELETE/DDL)自动广播表变更
- Table API 写入(insert/insertMany/update/delete/clear/drop)自动广播
- 其他标签页收到 `external` 变更事件(订阅者可见)+ Hybrid 内存自动重载
- `db.broadcastChange(table)` 手动广播
- **IndexedDB schema 持久化** — `__metona_schema` store 保存完整列定义,reopen(页面刷新/重连)后恢复;旧数据回退为"主键 + 索引 + 样例推断"
### Fixed
- **IndexedDB reopen 后 schema 丢失(严重)** — schema 仅存内存缓存,close 后重开连接表结构即丢失(INSERT 列映射截断、UPDATE 全列失效)。v0.3.2 持久化 + 重建
- **where-matcher 顶层 $not 不生效** — `NOT (expr)` 解析为顶层 `{ $not }` 但被当作字段匹配,始终返回 true
- **CASE 剥离后空 `$not` 恒假** — 引擎层执行 `NOT(true)` 过滤掉所有行
### Changed
- 版本号升至 v0.3.2
---
## [0.3.1] - 2026-08-08
### Added
- **CASE WHEN 表达式(SELECT 列)** — `CASE WHEN cond THEN value [WHEN ...] [ELSE value] END [AS alias]`
- 多 WHEN 按顺序匹配、ELSE 缺省返回 null
- THEN/ELSE 值支持字面量(字符串/数字/布尔/null)与列引用
- 条件支持 AND/OR/比较运算符/IS NULL 等完整 WHERE 语法
- 引擎层返回原始行供求值(避免投影丢失条件列),与普通列混合投影
- **JOIN + 关联子查询** — JOIN 结果的 WHERE 支持 `EXISTS (SELECT ... WHERE o2.x = u.y)``$col` 引用外层行,逐行绑定求值
- **WAL 批量组提交** — `WAL.appendBatch()` 合并多条记录为一次底层写入;引擎 insert/update/delete 批量记录后一次落盘(写放大从 O(N) 降为 O(1))
- **`parseWhereCondition()` 导出** — 独立条件表达式解析(CASE WHEN 求值内部使用)
### Fixed
- **LSM pending frozen 可见性** — 异步 flush 链期间,中间冻结的 MemTable 仅存在于闭包中,读取路径不可见导致数据缺失。新增 `frozenMemtables` 列表,get/rangeScan/getAllEntries 遍历所有 pending frozen(新→旧)
- **flush 缓存裁剪与预加载竞争** — flush 完成时的 LRU 裁剪会驱逐 prefetch 刚加载的 SSTable,导致扫描静默丢数据。prefetchRange/prefetchKeys 先等待 flush 链完成再加载
- **CASE 值解析顺序** — `true`/`false`/`null` 字面量被误判为列引用
### Changed
- 版本号升至 v0.3.1
---
## [0.3.0] - 2026-08-08
### Added
- **SQL 多语句支持** — `parseAll()` 解析分号分隔的多条语句,`db.query()` 顺序执行并返回最后一条结果(`parse()` 保持单语句兼容)
- **事务语句(TCL** — `BEGIN` / `COMMIT` / `ROLLBACK`(支持可选 TRANSACTION 关键字),直接驱动引擎层事务,五引擎可用
- **INSERT INTO ... SELECT** — 支持 `INSERT INTO t (cols) SELECT ...`(含 WHERE 过滤、列映射)
- **UNION / UNION ALL** — 支持去重合并与链式组合(`A UNION B UNION C`),右侧结果按位置投影到左侧列结构
- **CREATE INDEX / DROP INDEX** — 动态二级索引:
- `CREATE [UNIQUE] INDEX idx ON table (col)` / `DROP INDEX idx ON table (col)`
- 五引擎实现:Memory 哈希索引 / IndexedDB 版本升级建 `idx_col` / OPFS 委托 / Hybrid 双写 / Aria 动态 LSM 索引
- Aria 主键索引受保护(不可 DROP);CREATE INDEX 对已有数据立即建索引(回填),后续写入自动同步
- **EXISTS / NOT EXISTS** — 支持关联子查询(`WHERE EXISTS (SELECT 1 FROM o WHERE o.user_id = u.id)`),逐行绑定外层上下文求值
- **别名 WHERE 修复** — `WHERE u.age > 20`(主表别名前缀)此前不工作,v0.3.0 字段名规范化(JOIN 路径不受影响)
- **`SELECT 1` 常量列** — 数字字面量可作为 SELECT 列(EXISTS 子查询常见用法)
- **benchmark 页面** — `site/benchmark.html` 浏览器内实测 Memory / Aria 引擎 × 1K/10K/50K 行的 INSERT/PK 查询/索引查询/UPDATE/DELETE 吞吐(ops/sec
- **`parseAll` / 新 AST 类型导出** — `SelectUnionStatement` / `CreateIndexStatement` / `DropIndexStatement` / `BeginTransactionStatement`
### Changed
- 版本号升至 v0.3.0
### Fixed
- `NOT EXISTS` 解析丢 EXISTS token 导致 parse 失败
---
## [0.2.6] - 2026-08-08
### Fixed
- **AriaEngine 二级索引 SSTable 互相覆盖(严重)** — 主 LSM 与二级索引 LSM 此前共享同一 `sstableStore`id 空间 + meta 列表),`allocateId()` 返回 `Date.now()` 且各 LSM 从同一 meta 恢复 `nextSSTableId`,高频写入时 id 冲突导致索引数据文件覆盖主数据、meta 与数据错配。v0.2.6 按命名空间隔离(文件前缀 + meta key + 独立 id 序列),索引 LSM 各持独立 store
- **LSM 多版本读取顺序错误(严重)** — `levels[]` 数组按 `push` 追加(新文件在尾部),但 `get()`/`rangeScan` 从头遍历(旧文件优先),同一 key 跨多次 flush 更新后读到旧值;`MergeIterator` 去重只取"先出堆者",不保证最新来源。v0.2.6 改为"数组头部即最新"(`unshift` + 按 id 降序加载),MergeIterator 显式取 sourceIndex 最小者
- **LZ4 往返不一致** — ① 匹配长度超过 19 字节被截断但 `si` 跳过全部匹配,解压丢数据;② matchField=0 的组合 token 与纯字面量 token 格式歧义;③ 字面量累积超过 15 字节后组合 token 永远无法输出;④ 输出缓冲区 `maxOut` 不足导致越界写入;⑤ 压缩失败时返回原样 input,解压端无法区分。v0.2.6 重写压缩器(长匹配分段、纯字面量 token 语义明确、动态上限)
- **React/Vue 集成运行时 ReferenceError** — `useDatabase`/`useSqlarkDatabase``import type` 导入 `MetonaSqlark` 但运行时 `new MetonaSqlark()``@ts-nocheck` 掩盖此 bug,实际使用必炸。改为 value import
- **sstableCache 无限增长** — SSTable 缓存无内存上限且 `init()` 预加载全部数据,大库 OOM。v0.2.6 引入 LRU 容量上限(`bufferPoolPages` × `pageSize`),查询前异步预加载兜底(修复缓存未命中静默返回 null 丢数据的隐患)
- **integrations 子路径缺失** — README 宣称 `metona-sqlark/react``metona-sqlark/vue` 可用但 `exports` 未声明,现已补齐(react/vue 为 peer dependencyrollup external
### Changed
- **Buffer Pool 配置真正生效** — `bufferPoolPages` 从无效配置变为 SSTable 缓存容量上限(默认 256 页 ≈ 1MB 可控内存)
- **LZ4 格式变更** — 旧版本压缩数据与新解压器不兼容,使用 `compression: true` 的用户需重新导入数据
- **SSTable 持久化串行化** — flush/compaction 改为串行链(`flushChain`),id 分配与持久化顺序一致,消除 fire-and-forget 竞态
- 文档数据修正 — 测试 761(41 套件)、覆盖率 81.1%(此前 91.0% 为排除 Aria 模块的陈旧数据)、体积 ~105KB / gzip ~27KB
### Added
- **AriaEngine 缓存内存上限测试** — 9 个测试:LRU 上限约束、驱逐后全表/PK/索引查询完整性、UPDATE/DELETE 全量作用、写路径上限、命名空间隔离回归、多版本回归、tombstone 回归
- **LZ4 往返一致性测试** — 6 个往返测试(重复/文本/随机/边界长度/15 字节边界/长匹配)
- **CryptoManager 加解密测试** — 7 个测试(往返/错误密码/不同 salt/实例隔离/全局兼容层/4KB 页面)
- **React/Vue hooks 集成测试** — 16 个测试(jest.mock 零依赖:mount 执行、错误路径、refresh、注入防护、数据库初始化)
- **jest.setup.js WebCrypto polyfill** — jsdom 环境补齐 `crypto.subtle` 支持 AES-GCM
---
## [0.2.5] - 2026-07-29
### Fixed
- **版本号统一** — `constants.ts` VERSION 从 `'0.2.0'` 更新为 `'0.2.5'`,修正 `index.ts`/`utils.ts`/`CONTRIBUTING.md` 中过时的版本注释和数据
- **AriaEngine OPFS 后端映射** — `core.ts``mode: 'aria'` + `diskEngine: 'opfs'` 时实际使用 Memory 后端的 bug 已修复
- **`_onError` 接入执行路径** — `query()`/`defineTable()`/`dropTable()`/`transaction()`/`importTable()` 的 catch 路径现在调用 `_onError` 全局错误回调
- **`maxRowsPerQuery` 生效** — `QueryExecutor` 构造时接收 `maxRowsPerQuery` 参数,SELECT 结果在返回前截断
- **WAL full 模式真正同步** — `WAL.append()` 改为 `async``full` 模式下 `await this.store.append()` 真正等待写入完成,不再 fire-and-forget
- **PluginManager.install 传 db 实例** — `register()` 增加可选 `db` 参数,`core.ts` 初始化时传入 `this`,插件可获取 db 引用
### Changed
- **SSTableReader 二分查找统一** — `locateBlockGE`/`locateBlockLE` 从线性扫描改为二分查找,rangeScan 性能在大型 SSTable 下不再退化
- **crypto 实例化** — 全局状态改为 `CryptoManager` 类,每个 AriaEngine 实例可拥有独立加密配置,保留全局函数向后兼容
- **compactLevelSync 接口公开化** — LSM 新增 public `compactLevel()` 方法,`vacuum()` 不再使用 `as any` 绕过 private 访问
- **WAL 大小阈值接入 checkpoint** — `CheckpointManager` 接收 `walSizeThreshold` 参数,WAL 缓冲超阈值时自动触发 checkpoint
### Added
- **ALTER TABLE 语法** — 支持 `ALTER TABLE ... ADD COLUMN` / `DROP COLUMN`(含可选 COLUMN 关键字)
- **TRUNCATE TABLE 语法** — 支持 `TRUNCATE TABLE name` 快速清空表数据
- **MVCC 接入读写路径** — 事务内 insert/update/delete 调用 `mvcc.writeVersion`/`mvcc.deleteVersion`,版本链作为 undo log
- **IndexedDB 索引利用** — `IndexedDBEngine.find` 等值查询时优先使用 IDB 索引(`idx_col` 命名约定),避免全量 getAll
- **SQL 注入防护** — React `useTable` / Vue `useSqlarkTable` 增加表名合法性校验(`/^[a-zA-Z_][a-zA-Z0-9_]*$/`
---
## [0.2.4] - 2026-07-27
### Added
- **二级索引** — 每列可独立维护 LSM Tree 索引,`$eq`/`$in`/`$gt`/`$lt` 走索引 O(log n)
- **MVCC 接入引擎** — MVCCManager 正式投产,替换 ad-hoc txnSnapshot
- **MVCC 自动 GC** — 每 10 次 checkpoint 自动回收过旧版本(保留最新 100 个)
- **Bloom Filter 序列化** — 写入 SSTable footer + 读取时加载 + 查询时 probe 快速否定
- **WAL 大小阈值** — `walSizeThreshold` 配置(默认 16MB),超阈值强制 checkpoint
- **内存预算** — `maxMemoryMB` 配置(默认 64MB
### Changed
- **WAL 默认同步模式** — `walSyncMode``'batch'` 改为 `'full'`,消除 crash 丢数据风险
- **查询优化** — `tryIndexLookup` 扩展支持非 PK 列索引查找
---
## [0.2.3] - 2026-07-27
### Fixed
- **RB-Tree fixDelete 完整实现** — 补全标准红黑树删除修复,保证 O(log n)
- **LSM SSTable 缓存预热** — `init()` 预加载所有 SSTable,消除 cache miss
- **OPFS 数据恢复** — `open()` 自动从 OPFS 文件加载已有表数据到内存
- **Aria WAL 事务恢复** — 两阶段恢复:仅回放已提交事务,未提交数据不回放
- **LZ4 格式修复** — 重写 token 格式,消除中间字面量 token 歧义
---
## [0.2.2] - 2026-07-27
### Added
- **OPFS 自研存储后端** — AriaEngine 新增 `OPFSBackend`,纯浏览器文件系统 API,零 IndexedDB 依赖
- 每个 key 对应一个二进制文件,存储在 `navigator.storage.getDirectory()`
- 支持 read/write/delete/list/exists/clear 完整接口
- 页面文件(4KB)、WAL 日志、Schema、SSTable 全部存储为独立文件
- `{ dbName }/pg_1`, `{ dbName }/__wal_0`, `{ dbName }/__aria_schemas` ...
### Changed
- `AriaEngineConfig.storageBackend` 已支持 `'opfs'``AriaEngine.open()` 自动选择 OPFSBackend
---
## [0.2.1] - 2026-07-27
### Fixed
- **WAL CRC 校验验证** — 恢复时计算并验证 CRC32,损坏记录自动跳过并告警
- **Hybrid 引擎提交顺序** — commit 先写磁盘再写内存,磁盘失败回滚内存,消除数据不一致风险
- **RESTRICT 外键行为修正** — 检测到引用行时抛出 `FOREIGN_KEY_VIOLATION`,不再静默保留孤儿
- **SSTable rangeScan 边界保护** — 空索引或 startBlockIdx > endBlockIdx 时安全返回
### Added
- **ColumnDef 约束激活** — `maxLength`/`min`/`max` 约束在 `checkFieldType` 中正式生效
- **查询结果上限** — `DatabaseConfig.maxRowsPerQuery`(默认 0 不限制),防止超大结果集 OOM
- **onError 全局回调** — 配置中的 `onError` 回调实际接入 CRUD 异常路径
- **debug 调试模式** — `DatabaseConfig.debug: true` 输出 `[MetonaSqlark:name]` 前缀的结构化日志
- **浏览器兼容性声明** — README 增加 Chrome 80+/Firefox 80+/Safari 14+/Edge 80+ 支持矩阵
### Changed
- MemoryEngine RESTRICT 外键行为:从"不级联保留孤儿"改为"禁止删除抛异常"
- Hybrid 引擎 commitTransaction 磁盘优先于内存
- DB_DEFAULTS 新增 `maxRowsPerQuery: 0``debug: false`
---
## [0.2.0] - 2026-07-27
### Added
- **AriaEngine 自研存储引擎** — 基于 LSM-Tree 的页面式存储引擎,19 个新模块,~3500 行 TypeScript
- **页面格式层** (`page/`): 4KB Slotted Page 编解码、Tuple 二进制序列化、CRC32 校验
- **Buffer Pool** (`buffer/`): LRU 页面缓存 + 驱逐策略,可控内存占用
- **LSM-Tree 索引** (`index/`): MemTable (红黑树) + 多级 SSTable + Leveled Compaction
- **Bloom Filter** (`index/bloom.ts`): FNV-1a + Murmur 双哈希,快速键否定判定
- **SSTable 构建器/读取器** (`index/sstable_builder.ts`, `sstable.ts`): 二分查找 + 范围扫描
- **Merge Iterator** (`index/merge_iterator.ts`): 最小堆多路归并,去重保留最新值
- **WAL** (`wal/`): 二进制日志格式 (LSN/type/txnId/table/key/json/CRC) + Checkpoint 管理
- **MVCC** (`transaction/mvcc.ts`): 版本链 + 快照隔离 + GC
- **存储后端** (`store/`): IndexedDB / Memory 双后端抽象
- **LZ4 压缩** (`compression/lz4.ts`): 简易页面级压缩
- **`mode: 'aria'`** — 新增存储模式,可通过 `MetonaSqlark.create({ mode: 'aria' })` 激活
- **Schema 持久化** — 表结构自动保存到 `__aria_schemas`,重启自动恢复
- **SSTable 元数据管理** — SSTable 索引信息持久化,启动时自动扫描加载
- **事务感知 CRUD** — insert/update/delete 在事务中缓冲到 snapshotcommit 批量写入 LSM
- **244 个 AriaEngine 专项测试** — 覆盖生命周期/表管理/CRUD/事务/持久化/SQL 集成/页面格式/LSM/压缩
### Changed
- `StorageMode` 类型新增 `'aria'`
- `STORAGE_MODES` 数组新增 `'aria'`
- `createEngine()` 支持 `mode: 'aria'` 分支
- 测试从 318 → **526**,套件从 20 → **27**
- 新增 7 个模块级测试文件:`aria-page``aria-index``aria-sstable``aria-buffer``aria-wal-mvcc``aria-compress``aria`
- LRUList 修复 size 追踪 bug
- WAL 存储改为按记录独立 key(避免拼接缓冲区越界)
- CheckpointManager 解耦 BufferPool 依赖
### Fixed
- **LZ4 压缩无限循环** — 字面量分支在发现匹配后回退导致 litLen=0 死循环,CI 卡死根因
- **SSTableReader.get() 自比较 bug** — 参数 key 被循环变量遮蔽导致永远返回第一条
- **CheckpointManager 测试 null 引用** — 改为 Mock 对象避免 TypeError 导致进程无法退出
- **IndexedDB 持久化测试** — 替换为 Memory Backend 验证,消除 fake-indexeddb timer 堆积
- **LSM.flush() 不必要 setTimeout** — 替换为 Promise.resolve(),消除额外 timer 延迟
---
## [0.1.14] - 2026-07-26
### Fixed
- **IndexedDB 事务原子性**: `flushToIDB` 改为单 IDB 事务包裹 clear+insert,消除崩溃丢数据风险
- **多标签页冲突**: `open()` 添加 `onversionchange` 监听,其他标签页升级版本时自动关闭过期连接
- **Memory 引擎幂等**: `open()` 重复调用安全无副作用
- 关闭时清理 `onversionchange` 监听器,防止内存泄漏
---
## [0.1.13] - 2026-07-26
### Added
- **事务回滚机制**: `IStorageEngine` 新增 `beginTransaction/commitTransaction/rollbackTransaction` 接口
- MemoryEngine: 快照式回滚(深拷贝 tables/schemas/indexes
- IndexedDBEngine: 延迟写入策略(事务中仅写内存,commit 批量刷 IDB)
- OPFSEngine: 快照式回滚(commit 批量写文件)
- HybridEngine: 同时代理内存+磁盘引擎事务
- **子查询支持**: SQL Parser + Executor 支持 `IN (SELECT ...)``op (SELECT ...)` 子查询
- AST 新增 `SubqueryExpression` 类型
- Parser 在 IN 和比较运算符后检测子查询
- Executor 新增 `resolveSubqueries()` 递归解析,自动执行子查询并替换为具体值
- **外键级联操作**: ColumnDef 新增 `onDelete/onUpdate` 选项
- 支持 `CASCADE`(递归级联删除)、`SET NULL``RESTRICT`
- SQL Parser 解析 `REFERENCES table(col) ON DELETE CASCADE ON UPDATE CASCADE`
- MemoryEngine 内置 `cascadeDelete()` 递归级联逻辑
- **连接池管理**: `MetonaSqlark.connect()` 静态方法
- 同名数据库复用已打开实例,引用计数管理
- `db.disconnect()` 释放连接,归零自动 close
- `MetonaSqlark.disconnectAll()` 强制关闭所有连接
### Changed
- `TransactionManager.execute()` 调用引擎层 begin/commit/rollback 实现真正原子性
- IndexedDBEngine CRUD 事务感知:活跃事务中延迟 IDB 写入
- ASTColumnDef / astColumnToColumnDef 支持 references/onDelete/onUpdate 透传
### Fixed
- IndexedDBEngine 事务中 find/count 从内存缓存读取(保证读到未提交变更)
- SQL Parser parseColumnDef 循环条件扩展,避免 REFERENCES 语法解析中断
---
## [0.1.12] - 2026-07-26
### Added
- `MeSqlark` 别名导出,与 `MetonaSqlark` 完全等价
- `metona-sqlark.cjs.js` CommonJS 构建产物
- 完善的 `where-matcher.ts` 消除 220+ 行重复代码
- `$col` 列引用支持 JOIN ON 条件
- `$and/$or` 嵌套支持(字段级 + 顶层)
- `$not` 逻辑非操作符
- `$like` 正则缓存加速
- `projectColumns` 列投影支持 `table.column` 格式
- `DISTINCT` 去重支持(O(n) 时间,列值拼接优化)
- 测试覆盖率提升至 93.46%(264 测试/15 套件)
### Changed
- `where-matcher` 统一 MemoryEngine / IndexedDBEngine / Executor 的 WHERE 逻辑
- JOIN ON 匹配支持 `$col` 语法
- LIKE 正则改为缓存式编译
- Executor DISTINCT 用列值拼接代替 JSON.stringify
- JOIN 嵌套循环连接优化:避免 ON 时对象扩散
### Fixed
- IndexedDBEngine `update/delete` 方法正确同步内存缓存
- OPFSEngine `getTableNames` 兼容 `entries()` 返回值
- SQL Parser 正确处理字符串转义字符
- SQL Parser 正确处理表别名(无 AS 关键字)
- SQL Parser 解析 `IS NULL` / `IS NOT NULL` / `NOT LIKE`
- `peekTokenIs` 方法正确暴露给语法分析
- 多处边界条件空值/假值处理
---
## [0.1.11] - 2026-07-25
### Added
- React 集成 hooks: `useQuery`, `useTable`, `useDatabase`
- Vue 集成 composables: `useSqlarkQuery`, `useSqlarkTable`, `useSqlarkDatabase`
- 发布订阅系统: `subscribe()`, `emit()`
- 数据迁移系统: `addMigration()`, `migrateTo()`
- 导入导出: `exportTable()`, `exportAll()`, `importTable()`
- 完整 SQL 解析器: `Lexer` + `Parser`(递归下降)
- SQL 支持: SELECT, INSERT, UPDATE, DELETE, CREATE TABLE, DROP TABLE
- JOIN 支持: INNER, LEFT, RIGHT, CROSS
- GROUP BY + HAVING + 聚合函数 (COUNT, SUM, AVG, MIN, MAX)
- DISTINCT, ORDER BY, LIMIT, OFFSET
- WHERE 条件: AND, OR, NOT, IN, LIKE, IS NULL
- Query Builder 链式 API: `select().where().orderBy().limit().execute()`
- 插件系统: 14 种生命周期钩子 + PluginManager
- Metadata 系统: ColumnDef 完整约束 (type/primaryKey/required/unique/index/default/references/maxLength/min/max)
### Changed
- 架构重构为分层设计:Engine → QueryExecutor → Table/QueryBuilder → SQL Parser → Public API
- AST 作为统一中间表示,SQL 和 QueryBuilder 行为完全一致
- 测试框架完善:core / edge / groupby / join / query-system / sql / table / engine / transaction / plugin / hybrid 全覆盖
---
## [0.0.1] - 2026-07-24
### Added
- 初始项目骨架
- TypeScript 配置(严格模式)
- Rollup 构建(UMD / ESM / CJS / min / .d.ts
- Jest + jsdom 测试环境
- ESLint + @typescript-eslint
- Gitea Actions CI 工作流
- 示例站点(index / demo / docs
- 构建脚本 build.sh