Files
MetonaSqlark/CHANGELOG.md
T

1091 lines
83 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.4] - 2026-08-15
### 写语句子查询 / 约束硬化 / 真惰性流式
> 深度审计第七阶段:修复 UPDATE/DELETE WHERE 子查询静默 0 行(P1,四引擎)、
> 主键 NULL 静默入库(P1)、DROP INDEX 解除建表 UNIQUE 约束(P2)等 8 项正确性问题;
> Aria findStream 迭代器化落地"真惰性"宣称;REINDEX 单次扫描性能优化。
### Fixed
- **UPDATE/DELETE WHERE 子查询静默 0 行(P1,四引擎)** — `executeUpdate/executeDelete`
直接 compileStatement 调引擎,`$subquery` 未解析:引擎层 matchWhere 的 `$in/$nin`
遇对象恒 false → 所有行不匹配,写语句静默影响 0 行(与 v0.7.3 修的 queryStream
同类)。executor 写路径先 `resolveWriteWhere` 解析非关联子查询(IN 列表/标量),
EXPLAIN 的 estimatedRows 同步修复;关联引用($col / 关联 EXISTS)显式
`NOT_SUPPORTED`(写语句无法逐行绑定外层上下文);QueryBuilder 直通引擎路径
由引擎层 `containsUnresolvedSubqueries` 防御(Memory/Aria 双引擎)
- **主键 NULL/undefined 静默入库(P1,四引擎)** — 主键列缺失/置 null 时
`String(undefined)``"undefined"``String(null)``"null"` 作为主键落库。
validateRow 层强制主键非空(SQL 语义 PK 隐含 NOT NULL)抛 `VALIDATION_ERROR`
default 值生效时仍可省略主键(语义不变)
- **DROP INDEX 解除建表 UNIQUE 约束(P2** — `dropIndex` 此前 `colDef.unique = false`
静默解除建表约束(aria 侧 persistSchemas 后重启约束永久消失)。现在建表 UNIQUE
列 DROP INDEX 抛 `NOT_SUPPORTED`(对齐 SQLite:需重建表解除);仅
`CREATE UNIQUE INDEX` 添加的约束可随索引删除(引擎内 `uniqueIndexCols` 跟踪来源,
重启后 schema 中的 unique 一律按建表约束保护)
- **GROUP BY 把 null 与字符串 'null' 合并(P2** — 分组键 `String(row[col] ?? 'null')`
使两类值合并为一组;DISTINCT/UNION 去重键 `String(v ?? '\0')` 同类吞并。
统一类型安全编码(类型前缀),仅同类型同值合并(与 where-matcher 的 === 语义一致)
- **UPDATE 未知列静默写入存储行(P2)** — `SET nonexistent = ...` 的脏列残留在行内
并随持久化落盘。预检阶段显式 `COLUMN_NOT_FOUND`SQL + Table API 双路径,
Memory/Aria 双引擎)
- **queryStream 多语句语义不一致(P2** — `parseAll(sql)[0]` 静默忽略后续语句:
可流式时不执行(如 DELETE),不可流式时回退 query() 却执行全部语句。
多语句显式 `PARSE_ERROR`(流式 API 要求单条 SELECT
- **KVStore lastBackgroundError 跨生命周期残留(P3** — close/open 未清理,
重开后首次 checkpoint 抛出上一次生命周期的旧错误。open/close 统一清零
- **Hybrid beginTransaction 部分成功泄漏(P3** — 内存 begin 成功 + 磁盘 begin
抛错 → 内存快照泄漏(后续事务永久 TX_ACTIVE)。磁盘失败补偿回滚内存
- **MemTable RB-Tree 删除双黑修复边界(P3)** — 两子节点删除时 successor 右子
为 null 的占位 parent 传 null → fixDelete 直接跳过修复(树失衡)。
修复 parent 传递(直接右子传 successor,间接右子传 successor 原父)
- **死代码清理** — 移除 LSM.getAllEntries(无调用者;其 frozen 遍历顺序与 `get()`
相反属死代码路径)
### Changed
- **Aria findStream 真惰性(宣称落地)** — 此前 `rangeScanLazy` 内部
`mergeIter.drain()` 全量物化,与"流式不物化"宣称不符。重写为真惰性:
MemTable 红黑树显式栈生成器 + SSTableReader 块级生成器 + GeneratorEntrySource
接入 MergeIterator 逐条拉取;callback 返回 false 提前终止(limit 达成时
未消费块/子树不再解析),大表流式内存 O(1)
- **REINDEX 单次全表扫描(性能)** — `reindexTableInternal` 此前每个索引列一次
getAllRowsN 列 × M 行 + 每列一次 drainChain),改为单次扫描重建全部索引列
- VERSION 0.7.3 → **0.7.4**
### Changed(测试)
- 测试 1256 → **1304**76 套件,+48 个 v0.7.4 回归);新增 `tests/v074-fixes.test.ts`
(写语句子查询 ×10 / 关联引用 ×3 / EXPLAIN ×1 / 主键非空 ×8 / DROP INDEX unique ×5 /
分组键编码 ×4 / 未知列 ×4 / queryStream ×2 / KVStore 生命周期 ×1 / Hybrid 补偿 ×1 /
真惰性流式 ×2 / REINDEX ×2);VERSION 断言同步
- 行覆盖率 90.0% → **90.1%**
---
## [0.7.3] - 2026-08-14
### INSERT 语句级原子性补全 / 索引一致性 / 流式查询回退安全
> 深度审计第六阶段:修复 INSERT 语句级部分提交(三引擎 + Aria PK 批内重复)、
> 索引列 IS NULL 恒空、delete RESTRICT 预检破坏索引、queryStream 子查询静默空结果
> 等 10 项问题,四引擎语义对齐。
### Fixed
- **INSERT 语句级部分提交(P1Memory/KVStore/Hybrid** — 第 N 行主键重复/唯一冲突
抛错时前 N-1 行已提交(v0.7.2 只修了 UPDATEINSERT 漏修)。改为两阶段:
先全量预检(主键批内 Set 互查 + 索引查 + 唯一批内互查),任何一行失败整句不执行
- **Aria insert 批内主键重复部分提交(P1)** — PK 重复检查在写入循环内:第 N 行
重复抛错时前 N-1 行已 put LSM 且其 WAL 记录随 appendBatch 一起丢失 → 部分提交 +
内存/WAL 不一致。PK 检查移入批预检阶段(与 v0.6.2 的 unique 预检同一阶段)
- **索引列 IS NULL 恒空(P1Memory/KVStore/Hybrid** — `tryIndexLookup`
`colIndex.get(null)` 恒 undefined → `return []` 短路全表扫描(AriaEngine v0.6.2
已修,Memory 漏修)。null/undefined 条件跳过索引路径回退全表扫描
- **delete RESTRICT 预检破坏索引(P1Memory/KVStore/Hybrid** — `removeIndexEntries`
在收集阶段(RESTRICT 预检前)执行:预检抛错时行未删但索引条目已删 → 唯一约束
永久失效 + 索引查询丢行。索引清理移到预检通过之后(与 AriaEngine 对齐)
- **queryStream 子查询静默空结果(P1** — WHERE 含 `$subquery`/`$exists`/`$col`
未回退物化:引擎层 matchWhere 的 `$in` 遇未解析的 `$subquery` 对象返回 false →
所有行被静默过滤(`$col` 会抛 QUERY_ERROR)。streamable 判定增加递归检测,
回退物化路径(resolveSubqueries 正确解析)
- **ALTER DROP 索引列残留(P2Memory/KVStore** — 删列只删 schema.columns
indexes Map 中该列条目残留 → 查询已删列走旧索引(不含新行)→ 结果不完整。
DROP 时同步清理索引 Map(对齐 AriaEngine cleanupTableIndexes
- **CREATE UNIQUE INDEX 存量重复数据静默成功(P2)** — 回填不校验存量唯一性
SQLite 语义应报错)。Memory/Aria 回填时检查重复 → 抛 `UNIQUE_VIOLATION`
失败路径清理半初始化索引(标志未落、索引 Map/LSM 移除),保持原子语义
- **`SELECT *, col AS alias` 解析与投影(P3** — 此前 parser 的 '*' 独占分支使
`SELECT *, name AS nick` 直接 PARSE_ERRORexecutor 侧 columns[0]==='*' 不投影。
parser 支持 '*' 后接列列表;projectRow 以原行全部列为基、其余表达式覆盖/追加
- **INSERT hooks 行键错位(P3** — SQL 省略列名时 beforeInsert/afterInsert 收到
数字键行(与 executor 写入的 schema 列名行不一致)。hooks 行映射对齐 executor
(省略列名时按 schema 列顺序)
- **KVStoreEngine insert 持久化原始行(P3)** — 非事务路径 `JSON.stringify(row)`
写入参原始对象:default 值不落盘、schema 外列被持久化。改为持久化内存中
validated 行(MemoryEngine 新增 `getRow()`O(rows) 无性能回退)
### Changed
- 测试 1198 → **1256**75 套件,+58 个 v0.7.3 回归);新增 `tests/v073-fixes.test.ts`
IS NULL ×6 / RESTRICT 索引 ×3 / INSERT 原子 ×11 / queryStream ×4 / ALTER DROP ×2 /
UNIQUE INDEX ×4 / SELECT * ×3 / hooks ×2 / KVStore validated ×3 / getRow ×1 /
WAL 失败窗口 ×2 / aria $in ×2 / $and 下推 ×4 / 常量转义 ×3 / ANALYZE ×2
+ React config 重建 ×1 + Vue 卸载 ×2 + 迁移无主键 ×2
- 行覆盖率 89.8% → **90.0%**
### Fixed(审计第二阶段:边界窗口 / 性能 / 生态收尾)
- **WAL BEGIN 写失败事务泄漏(P3** — full 模式 BEGIN 记录写失败时 currentTxnId 已设置
→ TX_ACTIVE 永久泄漏(后续无法开始新事务)。失败回滚 mvcc 登记与快照后重抛,
调用方可重试
- **WAL ROLLBACK 顺序(P3** — 内存先回滚、ROLLBACK 记录后写:full 模式写失败时
崩溃重放无 ROLLBACK 记录 → 已回滚事务的数据复活。改为先持久化 ROLLBACK 再回滚内存
(与 commitTransaction 的"WAL 领先内存"对齐):写失败 → 事务仍活跃可重试,
崩溃重放看到 ROLLBACK 记录不复活数据
- **aria $in 逐值 drainChain 性能悬崖(P2** — 索引 `$in` 逐值 indexScanToRows
每个值一次 prefetchRange + prefetchKeys(各一次 drainChain 排空后台链),
compaction 长耗时时 N 倍放大(与 v0.6.1 修的 insert 批量预加载同类)。
批级预加载全部值索引范围 + 主表行各一次,循环内同步 rangeScan/get
- **update 主键变更级联重复全表扫描(P2 性能)** — applyUpdateCascade /
applyForeignKeyUpdateRules 的阶段 1 RESTRICT 扫描与两阶段预检
checkUpdateRestrict / checkForeignKeyUpdateRestrict)完全重复,删除冗余扫描
- **多条件 AND 永不走索引(P2)** — `WHERE a AND b` 解析为顶层 $andMemory/Aria
的 tryIndexLookup 只查顶层键 → 永远全表扫描,索引形同虚设。递归展开 $and 等值
条件下推($or/$not 保守跳过,命中后全条件 matchWhere 过滤,子集语义安全);
EXPLAIN usingIndex 同步递归识别 $and 嵌套(与引擎行为对齐)
- **ANALYZE 统计不含二级索引(P3** — indexDepth/sstableCount/memtableSize 只统计
主 LSM,多索引表严重低估。汇总该表全部二级索引 LSM
- **Vue useSqlarkDatabase 卸载不 closeP3)** — 组件卸载后实例永不关闭(连接/锁/
后端句柄泄漏)。onUnmounted 时 close(失败不阻塞卸载)
- **React useDatabase config 变更不生效(P3** — initRef 只建一次,配置更新永不
重建且旧实例残留。以 config 序列化指纹为依赖:变更时 cleanup 关闭旧实例再重建
close 幂等,未完成 init 亦可安全关闭)
- **migrateFromIndexedDB 无 id 列旧库迁移中断(P3)** — 推断 schema 无主键 →
createSchema 抛 SCHEMA_ERROR 中断整个迁移。第一个非 json 列兜底为主键;
全 json 列无可用主键 → 跳过该表(skippedTables)不中断
- **SELECT 常量列 SQL 标准 '' 转义未还原(P3** — `SELECT 'O''Brien'` 输出
`O''Brien`projectRow 只处理反斜杠转义)。`''` 还原为 `'`(常量列与别名常量两处)
- **死代码清理** — 移除 AriaEngine.getWALEstimatedSizeCheckpointManager 自带
真实缓冲字节估算,此方法无调用者)
---
## [0.7.2] - 2026-08-13
### 语句级原子性 / 事务 DDL 语义统一 / 约束与绑定硬化
> 深度审计第五阶段:修复 UPDATE 语句级部分提交、事务内 DDL 回滚残留、
> SET NULL 级联绕过 required 约束、参数绑定注释误判等 6 项问题,
> Hybrid 写穿透失败补偿,四引擎语义对齐。
### Fixed
- **UPDATE 语句级部分提交(P1Memory/Aria/KVStore/Hybrid** — 单条 UPDATE 匹配
多行时,第 N 行唯一约束/校验失败抛错,前 N-1 行已写入(aria 场景其 WAL 记录
随 appendBatch 一起丢失,内存与 WAL 进一步不一致)。改为两阶段:先全量预检
validateRow + 批内唯一互查 + 索引唯一查 + 主键冲突 + 外键 RESTRICT/SET NULL
预检),任何一行失败整句不执行,后统一执行
- **批内唯一互查缺失** — 两行在同一语句中更新到同一新唯一值:索引尚未反映本语句
变更,逐行检查相互看不见 → 绕过唯一约束。两阶段预检增加批内 Set 互查
- **事务内 DDL 回滚残留(P1)** — `BEGIN; ALTER ADD COLUMN; ROLLBACK` 后新列残留
Memory/KVStore 事务快照对 schema 是浅拷贝,alterTable 直改共享 columns 对象)。
Memory/KVStore 的 ALTER TABLE / CREATE INDEX / DROP INDEX 在事务内显式拒绝
`NOT_SUPPORTED`,与 AriaEngine 对齐);createTable/dropTable 保持可回滚
- **SET NULL 级联绕过 required 约束(P1)** — 外键列 `required: true` 时 delete 级联
与 update 主键变更级联静默写入 null(不经过 validateRow)。预检阶段整体拒绝
`FOREIGN_KEY_VIOLATION`),Memory/Aria 双引擎对齐
- **bindParameters 注释误判(P2** — `-- comment ?` 中的 `?` 计入占位符(参数错位
PARAM_ERROR);注释中的单引号触发 "Unterminated string literal"。词法扫描感知
行注释与块注释(其中 `?`/引号不参与绑定与字符串状态机)
- **未闭合字符串静默接受** — `SELECT 'abc` 此前解析成功(错误结果),lexer 现抛
`PARSE_ERROR`
- **未知 where 操作符静默全匹配(P3)** — `$betwen` 等未实现/拼错操作符此前
`default: return true`(所有行匹配、过滤形同虚设)。现抛 `QUERY_ERROR`
- **UPDATE undefined 覆盖列值** — `update({ col: undefined })` 此前把 undefined 写入
行(列键丢失)。undefined 现语义化为"不更新该列"(保留旧值),null 仍显式置空
(四引擎统一;KVStore 主键 undefined 不再误触主键变更路径)
- **Hybrid write-through 非原子(P1)** — 磁盘写失败时内存已写入(重启丢数据且错误
已抛)。insert/update/delete/createTable/dropTable/alterTable/clear/createIndex/
dropIndex 磁盘失败后自动从磁盘重载内存(内存=磁盘对齐),再抛原始错误;
事务路径由双引擎快照回滚保证不受影响
### Changed
- 测试 1155 → **1198**74 套件,+43 个 v0.7.2 回归);新增 `tests/v072-fixes.test.ts`
(语句原子性 ×6 / 批内互查 ×3 / 事务 DDL ×6 / SET NULL 约束 ×5 / 注释感知 ×6 /
未闭合字符串 ×2 / 未知操作符 ×2 / undefined 语义 ×9 / Hybrid 补偿 ×4
- `CONTRIBUTING.md` 项目结构同步(移除已删除的 indexeddb/opfs 引擎与 utils.ts
---
## [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