删除: - AUDIT-aria-lsm-v0.7.4.md(50KB) - AUDIT-query-layer-v0.7.4.md(39KB) - AUDIT-storage-engines-v0.7.4.md(39KB) - PLAN-v0.7.5.md(98KB,含附录 G/H/I) 删除前先清除引用面,避免留下断链(共 18 处): - 源码注释 7 处(change-notifier / kvstore index / column-value / expression / sql-compare / where-matcher / validation):保留设计意图,引用改为"v0.8.0 审计根因 N" - 测试注释 9 处(opfs.spec / aria-opfs-backend / faulty-backend / storage-harness / v080-b6 / v080-kvstore / v080-query-layer / v080-sql-three-valued / v080-unified-validation / parser):同上 - CHANGELOG 3 处:改为不依赖已删除文档的自洽表述(B-6 交付物见各条;门禁订正三处 按内容重写),并把变异数量同步为 42 - 校验:三个 md 之间无断链;仓库内已无 PLAN-v0.7.5/AUDIT-* 的任何引用 (git 历史仍可追溯,需要时可 `git show <commit>:PLAN-v0.7.5.md` 找回) 验证:93 套件 / 1985 用例全绿;覆盖率 90.59 / 82.61 / 94.14 / 93.50(阈值 90/82/94/93); e2e 14/14;lint + 两份 tsc 干净;dist 已重建(注释只影响非压缩产物,min 产物 251,731 B / gzip 63,431 B 不变)。 说明:审查记录的核心内容仍在 CHANGELOG.md("全量回归审查"与"现场失败修复"两节), 随 PLAN 一起删除的是附录 G/H/I 的详细表格(门禁逐条验收、交付物清单、未修复项表)。
141 lines
5.4 KiB
TypeScript
141 lines
5.4 KiB
TypeScript
/**
|
||
* v0.8.0 回归套件:SQL 三值逻辑(NULL 语义)—— PB-2
|
||
* ============================================================================
|
||
* 本套件锁定 v0.8.0 审计根因 7("未解析/UNKNOWN 静默变 false")与
|
||
* 附录 §5 缺陷 A15 的修复契约,并对**四种存储引擎**逐一验证同一语义。
|
||
*
|
||
* 历史缺陷(本套件即为其反例):
|
||
* 1. `WHERE n = NULL` 命中 NULL 行(`===` 比较 null === null);
|
||
* 2. `WHERE n != NULL` 返回所有非 NULL 行;
|
||
* 3. `WHERE n NOT BETWEEN 1 AND 2` 恒空集 —— parser 生成的字段级
|
||
* `{ n: { $or: [ { $lt: 1 }, { $gt: 2 } ] } }` 被求值成"查询字段 `$lt`",
|
||
* 每行都是 UNKNOWN;根因是字段级 `$or` 递归进了 where 子句级求值器。
|
||
*
|
||
* 参考数据:
|
||
* id=1 n=1 s='x'
|
||
* id=2 n=2 s=NULL
|
||
* id=3 n=NULL s='x'
|
||
* id=4 n=3 s='y'
|
||
*/
|
||
import { MetonaSqlark } from '../src/core';
|
||
import type { DatabaseConfig } from '../src/constants';
|
||
import { rows as rowsOf } from './helpers/assertions';
|
||
|
||
interface EngineCase {
|
||
label: string;
|
||
mode: DatabaseConfig['mode'];
|
||
extra?: Partial<DatabaseConfig>;
|
||
}
|
||
|
||
const ENGINES: EngineCase[] = [
|
||
{ label: 'memory', mode: 'memory' },
|
||
{ label: 'disk', mode: 'disk' },
|
||
{ label: 'hybrid', mode: 'hybrid' },
|
||
{ label: 'aria', mode: 'aria', extra: { diskEngine: 'memory' } },
|
||
];
|
||
|
||
/** [SQL, 期望 id 集合] */
|
||
const MATRIX: Array<[sql: string, expected: string[]]> = [
|
||
// --- 与 NULL 的比较:结果 UNKNOWN,WHERE 不保留 ---
|
||
['SELECT id FROM t WHERE n = NULL', []],
|
||
['SELECT id FROM t WHERE n != NULL', []],
|
||
['SELECT id FROM t WHERE n <> NULL', []],
|
||
|
||
// --- IS NULL / IS NOT NULL 是谓词,不走比较 ---
|
||
['SELECT id FROM t WHERE n IS NULL', ['3']],
|
||
['SELECT id FROM t WHERE n IS NOT NULL', ['1', '2', '4']],
|
||
["SELECT id FROM t WHERE s IS NULL", ['2']],
|
||
|
||
// --- IN / NOT IN:列表含 NULL 且未命中 → UNKNOWN ---
|
||
['SELECT id FROM t WHERE n IN (1, NULL)', ['1']],
|
||
['SELECT id FROM t WHERE n NOT IN (1, 2)', ['4']],
|
||
["SELECT id FROM t WHERE s NOT IN ('x')", ['4']],
|
||
['SELECT id FROM t WHERE n IN (1, 2)', ['1', '2']],
|
||
|
||
// --- LIKE / NOT LIKE:NULL 操作数 → UNKNOWN ---
|
||
["SELECT id FROM t WHERE s LIKE 'x'", ['1', '3']],
|
||
["SELECT id FROM t WHERE s NOT LIKE 'x'", ['4']],
|
||
|
||
// --- NOT:UNKNOWN 取反仍是 UNKNOWN ---
|
||
['SELECT id FROM t WHERE NOT (n = 1)', ['2', '4']],
|
||
['SELECT id FROM t WHERE NOT (n = 4)', ['1', '2', '4']],
|
||
|
||
// --- BETWEEN / NOT BETWEEN(本套件的核心回归点)---
|
||
['SELECT id FROM t WHERE n BETWEEN 1 AND 2', ['1', '2']],
|
||
['SELECT id FROM t WHERE n NOT BETWEEN 1 AND 2', ['4']],
|
||
['SELECT id FROM t WHERE s BETWEEN \'a\' AND \'z\'', ['1', '3', '4']],
|
||
["SELECT id FROM t WHERE s NOT BETWEEN 'a' AND 'z'", []],
|
||
|
||
// --- 字段级 $or / $not 与 where 级逻辑组合的一致性 ---
|
||
['SELECT id FROM t WHERE n > 1 OR n < 0', ['2', '4']],
|
||
['SELECT id FROM t WHERE n > 0 AND n < 3', ['1', '2']],
|
||
["SELECT id FROM t WHERE s = 'x' OR n IS NULL", ['1', '3']],
|
||
["SELECT id FROM t WHERE s = 'x' AND n IS NULL", ['3']],
|
||
["SELECT id FROM t WHERE s != 'x'", ['4']],
|
||
|
||
// --- 裸列作为布尔条件(字段存在性)不应因 NULL 崩溃 ---
|
||
['SELECT id FROM t WHERE n >= 3', ['4']],
|
||
['SELECT id FROM t WHERE n <= 1', ['1']],
|
||
];
|
||
|
||
describe('v0.8.0 SQL 三值逻辑(PB-2 / A15)', () => {
|
||
for (const engine of ENGINES) {
|
||
describe(`${engine.label} 引擎`, () => {
|
||
let db: MetonaSqlark;
|
||
|
||
beforeAll(async () => {
|
||
db = await MetonaSqlark.create({
|
||
name: `sql-three-valued-${engine.label}`,
|
||
mode: engine.mode,
|
||
...(engine.extra ?? {}),
|
||
});
|
||
await db.defineTable('t', {
|
||
id: { type: 'string', primaryKey: true },
|
||
n: { type: 'number' },
|
||
s: { type: 'string' },
|
||
});
|
||
await db.query(
|
||
"INSERT INTO t (id, n, s) VALUES ('1', 1, 'x'), ('2', 2, NULL), ('3', NULL, 'x'), ('4', 3, 'y')",
|
||
);
|
||
});
|
||
|
||
afterAll(async () => {
|
||
await db.close();
|
||
});
|
||
|
||
it.each(MATRIX)('%s → %j', async (sql, expected) => {
|
||
const rows = rowsOf<Record<string, unknown>>(await db.query(sql));
|
||
const got = rows.map((row) => row.id as string).sort();
|
||
expect(got).toEqual([...expected].sort());
|
||
});
|
||
|
||
it('UPDATE / DELETE 使用同一套三值语义', async () => {
|
||
// 独立表,避免污染上面的只读矩阵
|
||
await db.defineTable('u', {
|
||
id: { type: 'string', primaryKey: true },
|
||
n: { type: 'number' },
|
||
});
|
||
await db.query("INSERT INTO u (id, n) VALUES ('a', 1), ('b', NULL), ('c', 3)");
|
||
|
||
// `n > 1` 只命中 c(NULL → UNKNOWN)
|
||
await db.query("UPDATE u SET n = 9 WHERE n > 1");
|
||
const afterUpdate = rowsOf<Record<string, unknown>>(await db.query('SELECT id, n FROM u ORDER BY id'));
|
||
expect(afterUpdate.map((r) => [r.id, r.n])).toEqual([
|
||
['a', 1],
|
||
['b', null],
|
||
['c', 9],
|
||
]);
|
||
|
||
// `n = NULL` 匹配 0 行(不能把 NULL 行删掉)
|
||
await db.query('DELETE FROM u WHERE n = NULL');
|
||
expect(rowsOf(await db.query('SELECT id FROM u ORDER BY id'))).toHaveLength(3);
|
||
|
||
// `n IS NULL` 命中 b
|
||
await db.query('DELETE FROM u WHERE n IS NULL');
|
||
const survivors = rowsOf<Record<string, unknown>>(await db.query('SELECT id FROM u ORDER BY id'));
|
||
expect(survivors.map((r) => r.id)).toEqual(['a', 'c']);
|
||
});
|
||
});
|
||
}
|
||
});
|