Files
MetonaSqlark/dist
thzxx 85f0f170a4 fix(A13): 自引用外键级联(删除/置空/更新/预检四条路径全部生效)
缺陷(PLAN §5 #33,实测确认):
MemoryEngine 与 AriaEngine 的级联实现里都有 `if (refTableName === tableName) continue;`
—— 自引用外键被整体跳过,四条路径全部失效:
  - ON DELETE CASCADE:`DELETE root` 只删 root,子树 a/b/c 全部残留,
    且 parent_id 指向已删除的行。父行已不在 → 这些行**之后再也无法通过级联清理**
    (永久悬挂,静默数据不一致);
  - ON DELETE SET NULL:子行的 parent_id 保持旧值(等于什么都没做);
  - ON UPDATE CASCADE:`UPDATE node SET id='root2'` 后子行仍指向 'root'(悬挂);
  - ON DELETE/UPDATE RESTRICT 预检:不检查自引用,约束形同虚设。
两个引擎的跳过条件逐字相同,因此缺陷是同步的(跨引擎一致地错)。

修法:
1. 删除全部 6 处 `refTableName === tableName)continue`(memory 3 + aria 3)。
2. 自引用带来的两个实现约束,已在注释中写明:
   - **先收集引用者再处理**:自引用时遍历的正是同一个 Map,边遍历边删会让
     Map 迭代器跳过条目(memory 侧改为先收集 pk 数组);
   - **先递归子树再删父行**:否则删掉父行后子行的 parent_id 再也匹配不上。
   Aria 侧本就通过 `getAllRows`(cloneRow 副本)收集,天然满足第一条。
3. 级联写入复用 B-1 的 `validatePartial`(上一提交已做),保持一致。

关于 A14(`ON UPDATE CASCADE` 传递链)——**审计结论有误,实测正常**:
审计记录为"A→B→C 链改 A 主键后 C 悬空",但 C 引用的是 **b.id**(未变),
因此 C 不需要更新,"悬空"的推断不成立。本提交的用例把这一结论固化,
并按真正的悬空场景(改 b.id → C 必须跟着更新)补了断言,
避免将来有人按那份错误结论去"修"一个不存在的问题。

验证:新增 tests/v080-foreign-key.test.ts(4 引擎 × 8 项,共 32 断言),
含"删兄弟分支时只清自己子树""RESTRICT 阻断时三张表都不得变化"等边界;
并做**变异验证**:重新插入一处 skip 后 3 项立即失败,恢复后全绿。
全量 87 套件 / 1729 测试通过;typecheck、lint、build 零错误;dist 已重建。
2026-09-15 01:36:29 +08:00
..