/** * Parser 边缘场景测试 * * v0.8.0 强化说明: * 1. 此前 12 处断言写着 `expect(ast.where).toBeDefined()` —— 既没有验证结构也没有验证 * 值,属于"空断言"。审计(AUDIT-query-layer / SQL 层)指出:`parser.test.ts` 无一处 * 断言混合 AND/OR 的**结构**,这正是 AND/OR 无优先级缺陷(总账第 11 项)能长期存活的原因。 * 2. 本文件此前 `parse()` 的返回类型是联合类型 `Statement`,直接访问 `.where` / `.columns` * 在类型上不成立(tests 从不做类型检查,所以没人发现)。现在用类型收窄辅助函数显式 * 区分语句种类 —— 收窄失败即测试失败,而不是静默读到一个 undefined。 */ import { parse } from '../../src/sql/parser'; import type { SelectStatement, InsertStatement, UpdateStatement, DeleteStatement, CreateTableStatement, } from '../../src/query/ast'; // --------------------------------------------------------------------------- // 类型收窄辅助:断言语句种类并返回收窄后的类型 // (替代 `as any` —— 收窄失败会立刻让测试失败,而不是让断言落空) // --------------------------------------------------------------------------- function asSelect(stmt: ReturnType): SelectStatement { expect(stmt.type).toBe('SELECT'); if (stmt.type !== 'SELECT') throw new Error(`expected SELECT, got ${stmt.type}`); return stmt; } function asInsert(stmt: ReturnType): InsertStatement & { values: unknown[][] } { expect(stmt.type).toBe('INSERT'); if (stmt.type !== 'INSERT') throw new Error(`expected INSERT, got ${stmt.type}`); if (!stmt.values) throw new Error('INSERT statement is missing values'); return stmt as InsertStatement & { values: unknown[][] }; } function asUpdate(stmt: ReturnType): UpdateStatement { expect(stmt.type).toBe('UPDATE'); if (stmt.type !== 'UPDATE') throw new Error(`expected UPDATE, got ${stmt.type}`); return stmt; } function asDelete(stmt: ReturnType): DeleteStatement { expect(stmt.type).toBe('DELETE'); if (stmt.type !== 'DELETE') throw new Error(`expected DELETE, got ${stmt.type}`); return stmt; } function asCreateTable(stmt: ReturnType): CreateTableStatement { expect(stmt.type).toBe('CREATE_TABLE'); if (stmt.type !== 'CREATE_TABLE') throw new Error(`expected CREATE_TABLE, got ${stmt.type}`); return stmt; } describe('Parser 边缘场景', () => { // ---- SELECT 扩展 ---- describe('SELECT 扩展', () => { it('WHERE 多条件 AND', () => { const ast = asSelect(parse("SELECT * FROM users WHERE age > 18 AND name LIKE 'A%'")); // v0.8.0: 断言真实结构而非 toBeDefined() expect(ast.where).toEqual({ $and: [{ age: { $gt: 18 } }, { name: { $like: 'A%' } }], }); }); it('WHERE IN 列表', () => { const ast = asSelect(parse('SELECT * FROM users WHERE id IN (1, 2, 3)')); expect(ast.where).toEqual({ id: { $in: [1, 2, 3] } }); }); it('WHERE IS NULL', () => { const ast = asSelect(parse('SELECT * FROM users WHERE bio IS NULL')); expect(ast.where).toEqual({ bio: { $eq: null } }); }); it('WHERE IS NOT NULL', () => { const ast = asSelect(parse('SELECT * FROM users WHERE bio IS NOT NULL')); expect(ast.where).toEqual({ bio: { $ne: null } }); }); it('WHERE NOT', () => { const ast = asSelect(parse('SELECT * FROM users WHERE NOT age = 18')); expect(ast.where).toEqual({ $not: { age: { $eq: 18 } } }); }); it('WHERE 括号分组', () => { const ast = asSelect(parse('SELECT * FROM users WHERE (age > 18 OR age < 10) AND active = TRUE')); expect(ast.where).toEqual({ $and: [{ $or: [{ age: { $gt: 18 } }, { age: { $lt: 10 } }] }, { active: { $eq: true } }], }); }); // v0.8.0 新增:AND 优先级必须高于 OR(SQL 标准),此前是纯左折叠 it('AND 优先级高于 OR(SQL 标准)', () => { const ast = asSelect(parse('SELECT * FROM t WHERE a = 1 OR a = 2 AND b = 3')); expect(ast.where).toEqual({ $or: [{ a: { $eq: 1 } }, { $and: [{ a: { $eq: 2 } }, { b: { $eq: 3 } }] }], }); }); it('显式括号可覆盖默认优先级', () => { const ast = asSelect(parse('SELECT * FROM t WHERE (a = 1 OR a = 2) AND b = 3')); expect(ast.where).toEqual({ $and: [{ $or: [{ a: { $eq: 1 } }, { a: { $eq: 2 } }] }, { b: { $eq: 3 } }], }); }); it('NOT 优先级高于 AND/OR', () => { const ast = asSelect(parse('SELECT * FROM t WHERE NOT a = 1 OR b = 2')); expect(ast.where).toEqual({ $or: [{ $not: { a: { $eq: 1 } } }, { b: { $eq: 2 } }], }); }); it('多层 AND/OR 交替的结合性', () => { const ast = asSelect(parse('SELECT * FROM t WHERE a = 1 AND b = 2 OR c = 3 AND d = 4')); expect(ast.where).toEqual({ $or: [ { $and: [{ a: { $eq: 1 } }, { b: { $eq: 2 } }] }, { $and: [{ c: { $eq: 3 } }, { d: { $eq: 4 } }] }, ], }); }); it('ORDER BY 多列', () => { const ast = asSelect(parse('SELECT * FROM users ORDER BY age DESC, name ASC')); expect(ast.orderBy).toHaveLength(2); expect(ast.orderBy![0]).toEqual({ column: 'age', direction: 'desc' }); expect(ast.orderBy![1]).toEqual({ column: 'name', direction: 'asc' }); }); it('只有 LIMIT 没有 OFFSET', () => { const ast = asSelect(parse('SELECT * FROM users LIMIT 5')); expect(ast.limit).toBe(5); expect(ast.offset).toBeUndefined(); }); it('只有 OFFSET 没有 LIMIT', () => { const ast = asSelect(parse('SELECT * FROM users OFFSET 10')); expect(ast.offset).toBe(10); expect(ast.limit).toBeUndefined(); }); it('不带 WHERE 的 SELECT', () => { const ast = asSelect(parse('SELECT id, name FROM users ORDER BY id LIMIT 5')); expect(ast.where).toEqual({}); }); }); // ---- INSERT 扩展 ---- describe('INSERT 扩展', () => { it('不带列名的 INSERT', () => { const ast = asInsert(parse("INSERT INTO users VALUES ('1', 'Alice', 30)")); expect(ast.columns).toBeUndefined(); expect(ast.values).toEqual([['1', 'Alice', 30]]); }); it('INSERT 布尔值和 NULL', () => { const ast = asInsert(parse('INSERT INTO users VALUES (TRUE, FALSE, NULL)')); expect(ast.values[0]).toEqual([true, false, null]); }); it('INSERT 负数和浮点数', () => { const ast = asInsert(parse('INSERT INTO scores VALUES (-1, 3.14)')); expect(ast.values[0]).toEqual([-1, 3.14]); }); }); // ---- UPDATE 扩展 ---- describe('UPDATE 扩展', () => { it('UPDATE 多列', () => { const ast = asUpdate(parse("UPDATE users SET name = 'Bob', age = 26 WHERE id = '1'")); expect(ast.sets).toEqual({ name: 'Bob', age: 26 }); expect(ast.where).toEqual({ id: { $eq: '1' } }); }); it('UPDATE 不带 WHERE', () => { const ast = asUpdate(parse('UPDATE users SET active = FALSE')); expect(ast.where).toEqual({}); }); }); // ---- DELETE 扩展 ---- describe('DELETE 扩展', () => { it('DELETE 不带 WHERE', () => { const ast = asDelete(parse('DELETE FROM users')); expect(ast.where).toEqual({}); }); it('DELETE 带复杂 WHERE', () => { const ast = asDelete(parse("DELETE FROM users WHERE age < 18 OR status = 'inactive'")); expect(ast.where).toEqual({ $or: [{ age: { $lt: 18 } }, { status: { $eq: 'inactive' } }], }); }); }); // ---- DDL 扩展 ---- describe('DDL 扩展', () => { it('CREATE TABLE 完整修饰符', () => { const ast = asCreateTable(parse("CREATE TABLE products (id STRING PRIMARY KEY, name STRING NOT NULL UNIQUE, price NUMBER DEFAULT 0, active BOOLEAN DEFAULT TRUE)")); expect(ast.columns).toHaveLength(4); expect(ast.columns[0]).toMatchObject({ name: 'id', primaryKey: true }); expect(ast.columns[1]).toMatchObject({ name: 'name', required: true, unique: true }); expect(ast.columns[2]).toMatchObject({ name: 'price', default: 0 }); expect(ast.columns[3]).toMatchObject({ name: 'active', default: true }); }); it('支持 DROP TABLE IF EXISTS 语法', () => { const ast = parse('DROP TABLE IF EXISTS users'); expect(ast).toMatchObject({ type: 'DROP_TABLE', name: 'users', ifExists: true }); }); it('DROP TABLE 不带 IF EXISTS', () => { const ast = parse('DROP TABLE users'); expect(ast).toMatchObject({ type: 'DROP_TABLE', name: 'users' }); }); }); // ---- 值类型 ---- describe('常量值解析', () => { it('布尔值 TRUE/FALSE', () => { const ast = asSelect(parse('SELECT * FROM users WHERE active = TRUE')); expect(ast.where).toEqual({ active: { $eq: true } }); }); it('NULL 值', () => { const ast = asSelect(parse('SELECT * FROM users WHERE bio IS NULL')); expect(ast.where).toEqual({ bio: { $eq: null } }); }); }); });