fix: v0.6.1 生产可用性深度审查 — MemoryEngine 级联环无限递归(P0)/ KVStore 多表事务与级联原子化 / 未open防护统一 / 27 个异常场景测试 + aria级联回归
CI / test (18.x) (push) Successful in 10m59s
CI / test (20.x) (push) Successful in 10m59s
CI / test (22.x) (push) Successful in 10m49s
CI / test (24.x) (push) Successful in 10m47s
CI / e2e (push) Successful in 10m4s

This commit is contained in:
thzxx
2026-08-10 14:29:40 +08:00
parent a9e1a281d3
commit 8a78f27f7d
15 changed files with 954 additions and 227 deletions
+106 -55
View File
@@ -729,9 +729,14 @@ class MemoryEngine {
// ---- 外键级联 ----
/**
* 级联删除查找引用 tableName.pkValue 的所有表的行并删除
* v0.6.1-fix: 环路保护ABA 级联环不再无限递归栈溢出AriaEngine 同语义
* @returns 级联删除的行数
*/
async cascadeDelete(tableName, pkValue, _row) {
async cascadeDelete(tableName, pkValue, _row, visited = new Set()) {
const visitKey = `${tableName}:${pkValue}`;
if (visited.has(visitKey))
return 0;
visited.add(visitKey);
let totalCascade = 0;
for (const [refTableName, refSchema] of this.schemas) {
if (refTableName === tableName)
@@ -763,7 +768,7 @@ class MemoryEngine {
if (refRow) {
// v0.3.3: 级联删除前清理索引条目
this.removeIndexEntries(refTableName, refRow, refPk);
totalCascade += await this.cascadeDelete(refTableName, refPk, refRow);
totalCascade += await this.cascadeDelete(refTableName, refPk, refRow, visited);
}
refTableData.delete(refPk);
totalCascade++;
@@ -1726,6 +1731,8 @@ class KVStoreEngine {
this.txActive = false;
/** 事务中写过的表(commit 时只 flush 这些表) */
this.txDirtyTables = new Set();
/** 事务中发生 schema 变更(DDL)—— commit 时持久化 schema */
this.txSchemaChanged = false;
this.kv = new KVStore(medium, checkpointThreshold);
}
// ---- 行 key 编解码 ----
@@ -1850,6 +1857,7 @@ class KVStoreEngine {
await this.memory.createTable(schema);
if (this.txActive) {
this.txDirtyTables.add(schema.name);
this.txSchemaChanged = true;
return;
}
await this.persistSchema();
@@ -1859,19 +1867,27 @@ class KVStoreEngine {
await this.memory.dropTable(tableName);
if (this.txActive) {
this.txDirtyTables.add(tableName);
this.txSchemaChanged = true;
return;
}
await this.persistSchema();
// 删除该表全部行(KV 中残留清理)
await this.flushTable(tableName);
const diff = await this.collectTableDiff(tableName);
if (Object.keys(diff.puts).length > 0)
await this.kv.putMany(diff.puts);
if (diff.deletes.length > 0)
await this.kv.deleteMany(diff.deletes);
}
async hasTable(tableName) {
this.ensureOpen();
return this.memory.hasTable(tableName);
}
async getTableNames() {
this.ensureOpen();
return this.memory.getTableNames();
}
async getTableSchema(tableName) {
this.ensureOpen();
return this.memory.getTableSchema(tableName);
}
async alterTable(tableName, action, column) {
@@ -1879,12 +1895,17 @@ class KVStoreEngine {
await this.memory.alterTable(tableName, action, column);
if (this.txActive) {
this.txDirtyTables.add(tableName);
this.txSchemaChanged = true;
return;
}
await this.persistSchema();
if (action === 'DROP') {
// 重写存储行(移除该列)
await this.flushTable(tableName);
const diff = await this.collectTableDiff(tableName);
if (Object.keys(diff.puts).length > 0)
await this.kv.putMany(diff.puts);
if (diff.deletes.length > 0)
await this.kv.deleteMany(diff.deletes);
}
}
// ---- CRUD ----
@@ -1929,16 +1950,18 @@ class KVStoreEngine {
this.txDirtyTables.add(tableName);
return count;
}
const puts = {};
const deletes = [];
if (pkChanged) {
// 主键变更:相关表整表 diff(罕见操作,可靠性优先)
for (const t of await this.affectedTables(tableName)) {
await this.flushTable(t);
const diff = await this.collectTableDiff(t);
Object.assign(puts, diff.puts);
deletes.push(...diff.deletes);
}
}
else {
// 增量重写受影响行
const puts = {};
const deletes = [];
for (const pk of affected) {
const row = await this.memory.find(tableName, { table: tableName, where: { [pkCol]: pk } });
if (row.length > 0) {
@@ -1948,16 +1971,20 @@ class KVStoreEngine {
deletes.push(this.rowKey(tableName, pk));
}
}
if (Object.keys(puts).length > 0)
await this.kv.putMany(puts);
if (deletes.length > 0)
await this.kv.deleteMany(deletes);
// 级联影响表(SET NULL/CASCADE 外键)整表 diff
for (const t of await this.affectedTables(tableName)) {
if (t !== tableName)
await this.flushTable(t);
if (t === tableName)
continue;
const diff = await this.collectTableDiff(t);
Object.assign(puts, diff.puts);
deletes.push(...diff.deletes);
}
}
// 单次原子写(一条日志记录 = 真原子,v0.6.1)
if (Object.keys(puts).length > 0)
await this.kv.putMany(puts);
if (deletes.length > 0)
await this.kv.deleteMany(deletes);
return count;
}
async delete(tableName, query) {
@@ -1969,14 +1996,20 @@ class KVStoreEngine {
this.txDirtyTables.add(tableName);
return count;
}
const puts = {};
const deletes = pks.map((pk) => this.rowKey(tableName, pk));
// 级联影响表整表 diff(合并到单次原子写,v0.6.1)
for (const t of await this.affectedTables(tableName)) {
if (t === tableName)
continue;
const diff = await this.collectTableDiff(t);
Object.assign(puts, diff.puts);
deletes.push(...diff.deletes);
}
if (Object.keys(puts).length > 0)
await this.kv.putMany(puts);
if (deletes.length > 0)
await this.kv.deleteMany(deletes);
// 级联影响表整表 diff
for (const t of await this.affectedTables(tableName)) {
if (t !== tableName)
await this.flushTable(t);
}
return count;
}
async count(tableName, query) {
@@ -1990,7 +2023,11 @@ class KVStoreEngine {
this.txDirtyTables.add(tableName);
return;
}
await this.flushTable(tableName);
const diff = await this.collectTableDiff(tableName);
if (Object.keys(diff.puts).length > 0)
await this.kv.putMany(diff.puts);
if (diff.deletes.length > 0)
await this.kv.deleteMany(diff.deletes);
}
// ---- 动态索引 ----
async createIndex(tableName, column, unique) {
@@ -1998,6 +2035,7 @@ class KVStoreEngine {
await this.memory.createIndex(tableName, column, unique);
if (this.txActive) {
this.txDirtyTables.add(tableName);
this.txSchemaChanged = true;
return;
}
await this.persistSchema();
@@ -2007,6 +2045,7 @@ class KVStoreEngine {
await this.memory.dropIndex(tableName, column, indexName);
if (this.txActive) {
this.txDirtyTables.add(tableName);
this.txSchemaChanged = true;
return;
}
await this.persistSchema();
@@ -2017,27 +2056,40 @@ class KVStoreEngine {
await this.memory.beginTransaction();
this.txActive = true;
this.txDirtyTables = new Set();
this.txSchemaChanged = false;
}
async commitTransaction() {
this.ensureOpen();
if (!this.txActive)
throw new DatabaseError('No active transaction', 'TX_NONE');
// 先持久化(原子),再提交内存快照(失败可回滚)
// v0.6.1: 全部 dirty 表合并为单次原子 flush(一条日志记录 = 真原子,
// 多表事务中途崩溃/失败不会出现"部分表已提交")
const puts = {};
const deletes = [];
for (const table of this.txDirtyTables) {
if (await this.memory.hasTable(table)) {
await this.flushTable(table);
const diff = await this.collectTableDiff(table);
Object.assign(puts, diff.puts);
deletes.push(...diff.deletes);
}
else {
// 事务内 drop 的表:清理 KV 残留行
const all = await this.kv.getAll();
const prefix = this.rowPrefix(table);
const deletes = all.filter(([key]) => key.startsWith(prefix)).map(([key]) => key);
if (deletes.length > 0)
await this.kv.deleteMany(deletes);
for (const [key] of all) {
if (key.startsWith(prefix))
deletes.push(key);
}
}
}
// v0.6.0-fix: 事务内 DDLcreate/drop/alter)的 schema 一并持久化
await this.persistSchema();
if (Object.keys(puts).length > 0)
await this.kv.putMany(puts);
if (deletes.length > 0)
await this.kv.deleteMany(deletes);
// 事务内 DDL 的 schema 一并持久化
if (this.txSchemaChanged) {
await this.persistSchema();
}
await this.kv.checkpoint();
await this.memory.commitTransaction();
this.txActive = false;
@@ -2050,6 +2102,7 @@ class KVStoreEngine {
await this.memory.rollbackTransaction();
this.txActive = false;
this.txDirtyTables = new Set();
this.txSchemaChanged = false;
}
// ---- 内部 ----
ensureOpen() {
@@ -2112,41 +2165,39 @@ class KVStoreEngine {
await this.kv.put(SCHEMA_KEY, enc(JSON.stringify(schemas)));
}
/**
* 整表 diff 持久化内存行全部 put + KV 残留行删除原子 putMany + deleteMany
* 用于主键变更 / 级联 / dropTable / clear / alterTable DROP / 事务 commit
* v0.6.1: 整表 diff 收集不落盘内存行全部 put + KV 残留行删除
* 调用方合并到单次原子 putMany/deleteMany多表操作真原子
*/
async flushTable(tableName) {
async collectTableDiff(tableName) {
const prefix = this.rowPrefix(tableName);
// 表已删除:仅清理 KV 残留行
const schema = await this.memory.getTableSchema(tableName);
if (!schema) {
const all = await this.kv.getAll();
const deletes = all.filter(([key]) => key.startsWith(prefix)).map(([key]) => key);
if (deletes.length > 0)
await this.kv.deleteMany(deletes);
return;
}
const pkCol = this.getPK(schema);
const rows = await this.memory.find(tableName, { table: tableName });
const puts = {};
const current = new Set();
for (const row of rows) {
const key = this.rowKey(tableName, String(row[pkCol]));
current.add(key);
puts[key] = enc(JSON.stringify(row));
}
// KV 残留行(内存中已不存在)删除
const all = await this.kv.getAll();
const deletes = [];
for (const [key] of all) {
if (key.startsWith(prefix) && !current.has(key)) {
deletes.push(key);
// 表已删除:仅收集 KV 残留行删除
const schema = await this.memory.getTableSchema(tableName);
if (schema) {
const pkCol = this.getPK(schema);
const rows = await this.memory.find(tableName, { table: tableName });
const current = new Set();
for (const row of rows) {
const key = this.rowKey(tableName, String(row[pkCol]));
current.add(key);
puts[key] = enc(JSON.stringify(row));
}
// KV 残留行(内存中已不存在)
const all = await this.kv.getAll();
for (const [key] of all) {
if (key.startsWith(prefix) && !current.has(key))
deletes.push(key);
}
}
if (Object.keys(puts).length > 0)
await this.kv.putMany(puts);
if (deletes.length > 0)
await this.kv.deleteMany(deletes);
else {
const all = await this.kv.getAll();
for (const [key] of all) {
if (key.startsWith(prefix))
deletes.push(key);
}
}
return { puts, deletes };
}
}