/** * v0.8.0 回归套件:SQL 三值逻辑(NULL 语义)—— PB-2 * ============================================================================ * 本套件锁定 PLAN-v0.7.5.md 根因 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; } 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>(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>(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>(await db.query('SELECT id FROM u ORDER BY id')); expect(survivors.map((r) => r.id)).toEqual(['a', 'c']); }); }); } });