/** * v0.7.3 回归测试 — 深度审计第六阶段修复 * * 1. 索引列 IS NULL 恒空(Memory/KVStore/Hybrid) * 2. delete RESTRICT 预检前误删索引 * 3. insert 语句级部分提交(PK/unique 批内重复) * 4. Aria insert 批内 PK 重复部分提交 * 5. queryStream 子查询静默空结果 * 6. ALTER DROP 索引列残留 * 7. CREATE UNIQUE INDEX 存量重复数据 * 8. SELECT * 混别名列投影 * 9. INSERT hooks 列映射 * 10. KVStore insert 持久化 validated 行 */ import { MetonaSqlark } from '../src/core'; import { MemoryEngine } from '../src/engine/memory'; import { rows as rowsOf } from './helpers/assertions'; describe('v0.7.3: 索引列 IS NULL(三引擎对齐)', () => { test.each([ ['memory', { mode: 'memory' } as const], ['disk', { mode: 'disk', diskEngine: 'memory' } as const], ['hybrid', { mode: 'hybrid', diskEngine: 'memory' } as const], ])('%s: 索引列 IS NULL 返回 null 行', async (_label, cfg) => { const db = await MetonaSqlark.create({ name: `v073-isnull-${_label}`, ...cfg }); await db.defineTable('users', { id: { type: 'string', primaryKey: true }, email: { type: 'string', index: true }, }); await db.query("INSERT INTO users VALUES ('1', NULL), ('2', 'a@b.c')"); const rows = rowsOf<{ id: string }>(await db.query('SELECT * FROM users WHERE email IS NULL')); expect(rows).toHaveLength(1); expect(rows[0].id).toBe('1'); await db.close(); }); test.each([ ['memory', { mode: 'memory' } as const], ['disk', { mode: 'disk', diskEngine: 'memory' } as const], ])('%s: 唯一索引列 $isNull(Query Builder)不走索引短路', async (_label, cfg) => { const db = await MetonaSqlark.create({ name: `v073-isnull-qb-${_label}`, ...cfg }); await db.defineTable('users', { id: { type: 'string', primaryKey: true }, email: { type: 'string', unique: true }, }); await db.query("INSERT INTO users VALUES ('1', NULL), ('2', 'a@b.c')"); // v0.8.0(A15):此处原为 `{ $eq: null }` 且期望命中 NULL 行 —— 那是错误的 // SQL 语义。测试的真实目标是"唯一索引上的 NULL 行不被索引短路漏掉", // 用正确谓词表达后目标不变(见下一条对 `$eq: null` 的语义护栏)。 const rows = await db.table('users').select(['id']).where({ email: { $isNull: true } }).execute(); expect(rows).toHaveLength(1); await db.close(); }); test.each([ ['memory', { mode: 'memory' } as const], ['disk', { mode: 'disk', diskEngine: 'memory' } as const], ])('%s: `$eq: null` 恒 UNKNOWN —— 不再命中 NULL 行', async (_label, cfg) => { const db = await MetonaSqlark.create({ name: `v073-eqnull-qb-${_label}`, ...cfg }); await db.defineTable('users', { id: { type: 'string', primaryKey: true }, email: { type: 'string', unique: true }, }); await db.query("INSERT INTO users VALUES ('1', NULL), ('2', 'a@b.c')"); const rows = await db.table('users').select(['id']).where({ email: { $eq: null } }).execute(); expect(rows).toHaveLength(0); await db.close(); }); test('aria: IS NULL 回归护栏(v0.6.2 已修)', async () => { const db = await MetonaSqlark.create({ name: 'v073-isnull-aria', mode: 'aria', diskEngine: 'memory' }); await db.defineTable('users', { id: { type: 'string', primaryKey: true }, email: { type: 'string', index: true }, }); await db.query("INSERT INTO users VALUES ('1', NULL), ('2', 'a@b.c')"); const rows = await db.query('SELECT * FROM users WHERE email IS NULL'); expect(rows).toHaveLength(1); await db.close(); }); }); describe('v0.7.3: delete RESTRICT 预检不破坏索引', () => { test('memory: RESTRICT 抛错后唯一约束与索引查询保持有效', async () => { const db = await MetonaSqlark.create({ name: 'v073-del-restrict', mode: 'memory' }); await db.defineTable('users', { id: { type: 'string', primaryKey: true }, email: { type: 'string', unique: true, index: true }, }); await db.defineTable('orders', { id: { type: 'string', primaryKey: true }, user_id: { type: 'string', references: 'users.id', onDelete: 'RESTRICT' }, }); await db.query("INSERT INTO users VALUES ('u1', 'x@x.x')"); await db.query("INSERT INTO orders VALUES ('o1', 'u1')"); let threw = false; try { await db.query("DELETE FROM users WHERE id = 'u1'"); } catch { threw = true; } expect(threw).toBe(true); // 行仍在 expect(await db.query('SELECT * FROM users')).toHaveLength(1); // 唯一约束仍有效 let uniqueThrew = false; try { await db.query("INSERT INTO users VALUES ('u2', 'x@x.x')"); } catch { uniqueThrew = true; } expect(uniqueThrew).toBe(true); // 索引查询仍能命中 const viaIndex = await db.query("SELECT * FROM users WHERE email = 'x@x.x'"); expect(viaIndex).toHaveLength(1); await db.close(); }); test('memory: 多行匹配删除 RESTRICT 失败 → 全部行索引完好', async () => { const db = await MetonaSqlark.create({ name: 'v073-del-restrict-multi', mode: 'memory' }); await db.defineTable('users', { id: { type: 'string', primaryKey: true }, tag: { type: 'string', index: true }, }); await db.defineTable('orders', { id: { type: 'string', primaryKey: true }, user_id: { type: 'string', references: 'users.id', onDelete: 'RESTRICT' }, }); await db.query("INSERT INTO users VALUES ('u1', 't1'), ('u2', 't2')"); await db.query("INSERT INTO orders VALUES ('o1', 'u1')"); let threw = false; try { await db.query('DELETE FROM users'); } catch { threw = true; } expect(threw).toBe(true); expect(await db.query('SELECT * FROM users')).toHaveLength(2); expect(await db.query("SELECT * FROM users WHERE tag = 't1'")).toHaveLength(1); expect(await db.query("SELECT * FROM users WHERE tag = 't2'")).toHaveLength(1); await db.close(); }); test('disk: RESTRICT 抛错后索引保持(与 memory 同路径)', async () => { const db = await MetonaSqlark.create({ name: 'v073-del-restrict-disk', mode: 'disk', diskEngine: 'memory' }); await db.defineTable('users', { id: { type: 'string', primaryKey: true }, email: { type: 'string', unique: true, index: true }, }); await db.defineTable('orders', { id: { type: 'string', primaryKey: true }, user_id: { type: 'string', references: 'users.id', onDelete: 'RESTRICT' }, }); await db.query("INSERT INTO users VALUES ('u1', 'x@x.x')"); await db.query("INSERT INTO orders VALUES ('o1', 'u1')"); let threw = false; try { await db.query("DELETE FROM users WHERE id = 'u1'"); } catch { threw = true; } expect(threw).toBe(true); const viaIndex = await db.query("SELECT * FROM users WHERE email = 'x@x.x'"); expect(viaIndex).toHaveLength(1); await db.close(); }); }); describe('v0.7.3: insert 语句级原子性', () => { test.each([ ['memory', { mode: 'memory' } as const], ['disk', { mode: 'disk', diskEngine: 'memory' } as const], ['hybrid', { mode: 'hybrid', diskEngine: 'memory' } as const], ])('%s: 批内主键重复 → 整句不执行', async (_label, cfg) => { const db = await MetonaSqlark.create({ name: `v073-ins-atomic-pk-${_label}`, ...cfg }); await db.defineTable('users', { id: { type: 'string', primaryKey: true } }); let threw = false; try { await db.query("INSERT INTO users VALUES ('a'), ('a')"); } catch { threw = true; } expect(threw).toBe(true); expect(await db.query('SELECT * FROM users')).toHaveLength(0); await db.close(); }); test.each([ ['memory', { mode: 'memory' } as const], ['disk', { mode: 'disk', diskEngine: 'memory' } as const], ['hybrid', { mode: 'hybrid', diskEngine: 'memory' } as const], ])('%s: 批内唯一冲突 → 整句不执行', async (_label, cfg) => { const db = await MetonaSqlark.create({ name: `v073-ins-atomic-uq-${_label}`, ...cfg }); await db.defineTable('users', { id: { type: 'string', primaryKey: true }, email: { type: 'string', unique: true }, }); let threw = false; try { await db.query("INSERT INTO users VALUES ('1', 'a@b.c'), ('2', 'a@b.c')"); } catch { threw = true; } expect(threw).toBe(true); expect(await db.query('SELECT * FROM users')).toHaveLength(0); await db.close(); }); test.each([ ['memory', { mode: 'memory' } as const], ['disk', { mode: 'disk', diskEngine: 'memory' } as const], ])('%s: 第 N 行撞已有主键 → 整句不执行(含前 N-1 行)', async (_label, cfg) => { const db = await MetonaSqlark.create({ name: `v073-ins-atomic-exist-${_label}`, ...cfg }); await db.defineTable('users', { id: { type: 'string', primaryKey: true } }); await db.query("INSERT INTO users VALUES ('a')"); let threw = false; try { await db.query("INSERT INTO users VALUES ('b'), ('a')"); } catch { threw = true; } expect(threw).toBe(true); const rows = rowsOf<{ id: string }>(await db.query('SELECT * FROM users')); expect(rows).toHaveLength(1); expect(rows[0].id).toBe('a'); await db.close(); }); test('aria: 批内主键重复 → 整句不执行(此前部分提交 + WAL 不一致)', async () => { const db = await MetonaSqlark.create({ name: 'v073-ins-atomic-aria', mode: 'aria', diskEngine: 'memory' }); await db.defineTable('users', { id: { type: 'string', primaryKey: true } }); let threw = false; try { await db.query("INSERT INTO users VALUES ('a'), ('a')"); } catch { threw = true; } expect(threw).toBe(true); expect(await db.query('SELECT * FROM users')).toHaveLength(0); await db.close(); }); test('aria: 事务内批内主键重复 → 整句不执行且快照干净', async () => { const db = await MetonaSqlark.create({ name: 'v073-ins-atomic-aria-tx', mode: 'aria', diskEngine: 'memory' }); await db.defineTable('users', { id: { type: 'string', primaryKey: true } }); await db.query('BEGIN'); let threw = false; try { await db.query("INSERT INTO users VALUES ('a'), ('a')"); } catch { threw = true; } expect(threw).toBe(true); await db.query('COMMIT'); expect(await db.query('SELECT * FROM users')).toHaveLength(0); await db.close(); }); test('aria: 批内唯一冲突整批不落库回归护栏(v0.6.2)', async () => { const db = await MetonaSqlark.create({ name: 'v073-ins-atomic-aria-uq', mode: 'aria', diskEngine: 'memory' }); await db.defineTable('users', { id: { type: 'string', primaryKey: true }, email: { type: 'string', unique: true }, }); let threw = false; try { await db.query("INSERT INTO users VALUES ('1', 'a@b.c'), ('2', 'a@b.c')"); } catch { threw = true; } expect(threw).toBe(true); expect(await db.query('SELECT * FROM users')).toHaveLength(0); await db.close(); }); }); describe('v0.7.3: queryStream 子查询回退物化', () => { test.each([ ['memory', { mode: 'memory' } as const], ['aria', { mode: 'aria', diskEngine: 'memory' } as const], ])('%s: IN 子查询流式查询返回正确结果(回退物化)', async (_label, cfg) => { const db = await MetonaSqlark.create({ name: `v073-stream-sub-${_label}`, ...cfg }); await db.defineTable('users', { id: { type: 'string', primaryKey: true } }); await db.defineTable('orders', { id: { type: 'string', primaryKey: true }, user_id: { type: 'string' } }); await db.query("INSERT INTO users VALUES ('1'), ('2')"); await db.query("INSERT INTO orders VALUES ('o1', '1')"); const collected: Record[] = []; const n = await db.queryStream('SELECT * FROM users WHERE id IN (SELECT user_id FROM orders)', (r) => collected.push(r)); expect(n).toBe(1); expect(collected).toHaveLength(1); expect(collected[0].id).toBe('1'); await db.close(); }); test('memory: EXISTS 关联子查询流式查询回退物化', async () => { const db = await MetonaSqlark.create({ name: 'v073-stream-exists', mode: 'memory' }); await db.defineTable('users', { id: { type: 'string', primaryKey: true } }); await db.defineTable('orders', { id: { type: 'string', primaryKey: true }, user_id: { type: 'string' } }); await db.query("INSERT INTO users VALUES ('1'), ('2')"); await db.query("INSERT INTO orders VALUES ('o1', '1')"); const collected: Record[] = []; await db.queryStream( 'SELECT * FROM users u WHERE EXISTS (SELECT 1 FROM orders o WHERE o.user_id = u.id)', (r) => collected.push(r), ); expect(collected).toHaveLength(1); await db.close(); }); test('memory: 简单查询仍走引擎流式路径(未误回退)', async () => { const db = await MetonaSqlark.create({ name: 'v073-stream-simple', mode: 'memory' }); await db.defineTable('users', { id: { type: 'string', primaryKey: true } }); await db.query("INSERT INTO users VALUES ('1'), ('2')"); const collected: Record[] = []; const n = await db.queryStream("SELECT * FROM users WHERE id = '1'", (r) => collected.push(r)); expect(n).toBe(1); expect(collected).toHaveLength(1); await db.close(); }); test('memory: WHERE 列引用($col)流式查询回退物化且不抛错', async () => { const db = await MetonaSqlark.create({ name: 'v073-stream-colref', mode: 'memory' }); await db.defineTable('t1', { id: { type: 'string', primaryKey: true }, x: { type: 'number' }, y: { type: 'number' } }); await db.query("INSERT INTO t1 VALUES ('1', 5, 5), ('2', 10, 3)"); // t1.x = t1.y 解析为 $col 列引用 —— 引擎层 matchWhere 无 $col 匹配分支, // 此前流式路径会抛 QUERY_ERROR/静默过滤;v0.7.3 回退物化,结果与 query() 一致 const viaQuery = await db.query('SELECT * FROM t1 WHERE t1.x = t1.y'); const collected: Record[] = []; await db.queryStream('SELECT * FROM t1 WHERE t1.x = t1.y', (r) => collected.push(r)); expect(collected).toHaveLength((viaQuery as Record[]).length); await db.close(); }); }); describe('v0.7.3: ALTER DROP 索引列清理', () => { test.each([ ['memory', { mode: 'memory' } as const], ['disk', { mode: 'disk', diskEngine: 'memory' } as const], ])('%s: DROP 索引列后无旧索引短路(新增行可见)', async (_label, cfg) => { const db = await MetonaSqlark.create({ name: `v073-alter-drop-idx-${_label}`, ...cfg }); await db.defineTable('users', { id: { type: 'string', primaryKey: true }, email: { type: 'string', index: true }, }); await db.query("INSERT INTO users VALUES ('1', 'a@b.c')"); await db.query('ALTER TABLE users DROP COLUMN email'); await db.query("INSERT INTO users VALUES ('2')"); expect(await db.query('SELECT * FROM users')).toHaveLength(2); // 已删列不再存在于 schema;查询该列应报错而非走旧索引(executor 层不会到达) const rows = await db.query('SELECT * FROM users WHERE id = \'2\''); expect(rows).toHaveLength(1); await db.close(); }); test('memory: DROP 非索引列不影响其他列索引', async () => { const db = await MetonaSqlark.create({ name: 'v073-alter-drop-plain', mode: 'memory' }); await db.defineTable('users', { id: { type: 'string', primaryKey: true }, name: { type: 'string' }, email: { type: 'string', index: true }, }); await db.query("INSERT INTO users VALUES ('1', 'Alice', 'a@b.c')"); await db.query('ALTER TABLE users DROP COLUMN name'); const viaIndex = await db.query("SELECT * FROM users WHERE email = 'a@b.c'"); expect(viaIndex).toHaveLength(1); await db.close(); }); }); describe('v0.7.3: CREATE UNIQUE INDEX 存量唯一性', () => { test.each([ ['memory', { mode: 'memory' } as const], ['aria', { mode: 'aria', diskEngine: 'memory' } as const], ])('%s: 存量重复数据 → 抛 UNIQUE_VIOLATION 且无半初始化索引', async (_label, cfg) => { const db = await MetonaSqlark.create({ name: `v073-uqidx-dup-${_label}`, ...cfg }); await db.defineTable('users', { id: { type: 'string', primaryKey: true }, email: { type: 'string' } }); await db.query("INSERT INTO users VALUES ('1', 'a@b.c'), ('2', 'a@b.c')"); let threw = false; let code = ''; try { await db.query('CREATE UNIQUE INDEX idx_e ON users (email)'); } catch (e) { threw = true; code = (e as { code?: string }).code ?? ''; } expect(threw).toBe(true); expect(code).toBe('UNIQUE_VIOLATION'); // 失败后列标志未落:普通 CREATE INDEX 仍可建立 await db.query('CREATE INDEX idx_e ON users (email)'); const viaIndex = await db.query("SELECT * FROM users WHERE email = 'a@b.c'"); expect(viaIndex).toHaveLength(2); await db.close(); }); test.each([ ['memory', { mode: 'memory' } as const], ['aria', { mode: 'aria', diskEngine: 'memory' } as const], ])('%s: 存量数据唯一 → 建索引成功且唯一约束生效', async (_label, cfg) => { const db = await MetonaSqlark.create({ name: `v073-uqidx-ok-${_label}`, ...cfg }); await db.defineTable('users', { id: { type: 'string', primaryKey: true }, email: { type: 'string' } }); await db.query("INSERT INTO users VALUES ('1', 'a@b.c'), ('2', 'b@b.c')"); await db.query('CREATE UNIQUE INDEX idx_e ON users (email)'); let threw = false; try { await db.query("INSERT INTO users VALUES ('3', 'a@b.c')"); } catch { threw = true; } expect(threw).toBe(true); expect(await db.query('SELECT * FROM users')).toHaveLength(2); await db.close(); }); }); describe('v0.7.3: SELECT * 混别名列投影', () => { test('memory: SELECT *, name AS nick 保留全部列 + 别名列', async () => { const db = await MetonaSqlark.create({ name: 'v073-star-alias', mode: 'memory' }); await db.defineTable('users', { id: { type: 'string', primaryKey: true }, name: { type: 'string' }, age: { type: 'number' } }); await db.query("INSERT INTO users VALUES ('1', 'Alice', 30)"); const rows = rowsOf>(await db.query('SELECT *, name AS nick FROM users')); expect(rows).toHaveLength(1); const row = rows[0]; expect(row.id).toBe('1'); expect(row.name).toBe('Alice'); expect(row.age).toBe(30); expect(row.nick).toBe('Alice'); await db.close(); }); test('memory: 纯 SELECT * 行为不变(原行引用键集合)', async () => { const db = await MetonaSqlark.create({ name: 'v073-star-only', mode: 'memory' }); await db.defineTable('users', { id: { type: 'string', primaryKey: true }, name: { type: 'string' } }); await db.query("INSERT INTO users VALUES ('1', 'Alice')"); const rows = rowsOf>(await db.query('SELECT * FROM users')); expect(Object.keys(rows[0]).sort()).toEqual(['id', 'name']); await db.close(); }); test('memory: SELECT *, 常量列混合', async () => { const db = await MetonaSqlark.create({ name: 'v073-star-const', mode: 'memory' }); await db.defineTable('users', { id: { type: 'string', primaryKey: true } }); await db.query("INSERT INTO users VALUES ('1')"); const rows = rowsOf>(await db.query("SELECT *, 'lit' AS c FROM users")); const row = rows[0]; expect(row.id).toBe('1'); expect(row.c).toBe('lit'); await db.close(); }); }); describe('v0.7.3: INSERT hooks 列映射', () => { test('SQL 省略列名时 beforeInsert 收到 schema 列名映射', async () => { const db = await MetonaSqlark.create({ name: 'v073-hooks-insert', mode: 'memory' }); await db.defineTable('users', { id: { type: 'string', primaryKey: true }, name: { type: 'string' } }); let seenRows: unknown = null; db.on('beforeInsert', async (rows: unknown) => { seenRows = rows; }); await db.query("INSERT INTO users VALUES ('1', 'Alice')"); const rows = seenRows as Record[]; expect(rows).toHaveLength(1); expect(rows[0].id).toBe('1'); expect(rows[0].name).toBe('Alice'); await db.close(); }); test('SQL 显式列名时 hooks 行键按显式列名', async () => { const db = await MetonaSqlark.create({ name: 'v073-hooks-insert-cols', mode: 'memory' }); await db.defineTable('users', { id: { type: 'string', primaryKey: true }, name: { type: 'string' } }); let seenRows: unknown = null; db.on('beforeInsert', async (rows: unknown) => { seenRows = rows; }); await db.query("INSERT INTO users (name, id) VALUES ('Alice', '1')"); const rows = seenRows as Record[]; expect(rows[0].id).toBe('1'); expect(rows[0].name).toBe('Alice'); await db.close(); }); }); describe('v0.7.3: KVStore insert 持久化 validated 行', () => { test('default 值与列投影落盘(跨实例恢复一致)', async () => { const db = await MetonaSqlark.create({ name: 'v073-kv-validated', mode: 'disk', diskEngine: 'memory' }); await db.defineTable('users', { id: { type: 'string', primaryKey: true }, age: { type: 'number', default: 18 } }); await db.query("INSERT INTO users VALUES ('1')"); await db.close(); const db2 = await MetonaSqlark.create({ name: 'v073-kv-validated', mode: 'disk', diskEngine: 'memory' }); const rows = rowsOf>(await db2.query('SELECT * FROM users')); expect(rows).toHaveLength(1); expect(rows[0]).toEqual({ id: '1', age: 18 }); await db2.close(); }); test('schema 外列不持久化', async () => { const db = await MetonaSqlark.create({ name: 'v073-kv-extra-col', mode: 'disk', diskEngine: 'memory' }); await db.defineTable('users', { id: { type: 'string', primaryKey: true } }); await db.table('users').insert({ id: '1', junk: 'x' } as never); await db.close(); const db2 = await MetonaSqlark.create({ name: 'v073-kv-extra-col', mode: 'disk', diskEngine: 'memory' }); const rows = rowsOf>(await db2.query('SELECT * FROM users')); expect(Object.keys(rows[0]).sort()).toEqual(['id']); await db2.close(); }); test('MemoryEngine.getRow 暴露 validated 行', async () => { const engine = new MemoryEngine(); await engine.open('v073-getrow', 1); await engine.createTable({ name: 'users', columns: { id: { type: 'string', primaryKey: true }, age: { type: 'number', default: 18 } } }); await engine.insert('users', [{ id: '1' }]); const row = engine.getRow('users', '1'); expect(row).toEqual({ id: '1', age: 18 }); expect(engine.getRow('users', 'nope')).toBeNull(); expect(engine.getRow('missing', '1')).toBeNull(); await engine.close(); }); }); describe('v0.7.3: WAL BEGIN/ROLLBACK 写失败窗口', () => { async function createAria(name: string): Promise<{ db: MetonaSqlark; walStore: { append: (d: Uint8Array) => Promise } }> { const db = await MetonaSqlark.create({ name, mode: 'aria', diskEngine: 'memory' }); await db.defineTable('users', { id: { type: 'string', primaryKey: true } }); const engine = db.getEngine() as unknown as { wal: { store: { append: (d: Uint8Array) => Promise } } }; return { db, walStore: engine.wal.store }; } test('BEGIN 记录写失败 → 事务状态不泄漏(可重试)', async () => { const { db, walStore } = await createAria('v073-wal-begin-fail'); const orig = walStore.append.bind(walStore); walStore.append = async () => { throw new Error('wal boom'); }; await expect(db.query('BEGIN')).rejects.toThrow(); walStore.append = orig; // 事务状态未泄漏:可正常开始并提交新事务 await db.query('BEGIN'); await db.query("INSERT INTO users VALUES ('1')"); await db.query('COMMIT'); expect(await db.query('SELECT * FROM users')).toHaveLength(1); await db.close(); }); test('ROLLBACK 记录写失败 → 事务仍活跃(可重试回滚,不复活数据)', async () => { const { db, walStore } = await createAria('v073-wal-rollback-fail'); await db.query('BEGIN'); await db.query("INSERT INTO users VALUES ('1')"); const orig = walStore.append.bind(walStore); walStore.append = async () => { throw new Error('wal boom'); }; // ROLLBACK WAL 记录先写失败 → 内存未回滚、事务仍活跃 await expect(db.query('ROLLBACK')).rejects.toThrow(); walStore.append = orig; // 重试回滚成功,数据未提交 await db.query('ROLLBACK'); expect(await db.query('SELECT * FROM users')).toHaveLength(0); await db.close(); }); }); describe('v0.7.3: aria $in 批级预加载', () => { test('多值 IN(含重复值/未命中值)索引查询正确', async () => { const db = await MetonaSqlark.create({ name: 'v073-in-batch', mode: 'aria', diskEngine: 'memory' }); await db.defineTable('users', { id: { type: 'string', primaryKey: true }, tag: { type: 'string', index: true }, }); for (let i = 0; i < 50; i++) { await db.query('INSERT INTO users VALUES (?, ?)', [String(i), `t${i % 5}`]); } // flush 使索引/主数据 SSTable 化(预加载路径真实生效) const engine = db.getEngine() as unknown as { lsm: { flush(): Promise }; secondaryIndexes: Map }> }; await engine.lsm.flush(); for (const idxLsm of engine.secondaryIndexes.values()) await idxLsm.flush(); const rows = await db.query("SELECT * FROM users WHERE tag IN ('t1', 't2', 't1', 't9')"); expect(rows).toHaveLength(20); await db.close(); }); test('IN 与 $and 组合条件结果正确(索引子集 + 全条件过滤)', async () => { const db = await MetonaSqlark.create({ name: 'v073-in-and', mode: 'aria', diskEngine: 'memory' }); await db.defineTable('users', { id: { type: 'string', primaryKey: true }, tag: { type: 'string', index: true }, age: { type: 'number' }, }); for (let i = 0; i < 20; i++) { await db.query('INSERT INTO users VALUES (?, ?, ?)', [String(i), `t${i % 4}`, i]); } const rows = await db.query("SELECT * FROM users WHERE tag IN ('t1', 't2') AND age >= 10"); expect(rows).toHaveLength(5); await db.close(); }); }); describe('v0.7.3: $and 等值条件下推', () => { test.each([ ['memory', { mode: 'memory' } as const], ['aria', { mode: 'aria', diskEngine: 'memory' } as const], ])('%s: 多条件 AND 查询结果正确', async (_label, cfg) => { const db = await MetonaSqlark.create({ name: `v073-and-push-${_label}`, ...cfg }); await db.defineTable('users', { id: { type: 'string', primaryKey: true }, tag: { type: 'string', index: true }, age: { type: 'number' }, }); await db.query("INSERT INTO users VALUES ('1', 'a', 10), ('2', 'a', 20), ('3', 'b', 10)"); const rows = rowsOf<{ id: string }>(await db.query("SELECT * FROM users WHERE tag = 'a' AND age = 10")); expect(rows).toHaveLength(1); expect(rows[0].id).toBe('1'); await db.close(); }); test.each([ ['memory', { mode: 'memory' } as const], ['aria', { mode: 'aria', diskEngine: 'memory' } as const], ])('%s: EXPLAIN 识别 $and 嵌套索引条件', async (_label, cfg) => { const db = await MetonaSqlark.create({ name: `v073-and-explain-${_label}`, ...cfg }); await db.defineTable('users', { id: { type: 'string', primaryKey: true }, tag: { type: 'string', index: true }, age: { type: 'number' }, }); await db.query("INSERT INTO users VALUES ('1', 'a', 10)"); // 索引列在 $and 嵌套中 → 递归识别 index:tag const plan1 = await db.query("EXPLAIN SELECT * FROM users WHERE age > 5 AND tag = 'a'"); expect((plan1 as Record).usingIndex).toBe('index:tag'); // 主键在 $and 嵌套中 → pk const plan2 = await db.query("EXPLAIN SELECT * FROM users WHERE age > 5 AND id = '1'"); expect((plan2 as Record).usingIndex).toBe('pk'); await db.close(); }); }); describe('v0.7.3: SELECT 常量列 SQL 标准转义', () => { test("SELECT 'O''Brien' 还原为 O'Brien", async () => { const db = await MetonaSqlark.create({ name: 'v073-escape', mode: 'memory' }); await db.defineTable('users', { id: { type: 'string', primaryKey: true } }); await db.query("INSERT INTO users VALUES ('1')"); const rows = rowsOf<{ name: string }>(await db.query("SELECT 'O''Brien' AS name FROM users")); expect(rows[0].name).toBe("O'Brien"); await db.close(); }); test("无表查询 SELECT 'a''b' AS x 转义还原", async () => { const db = await MetonaSqlark.create({ name: 'v073-escape-notable', mode: 'memory' }); const rows = rowsOf<{ x: string }>(await db.query("SELECT 'a''b' AS x")); expect(rows[0].x).toBe("a'b"); await db.close(); }); test("SELECT *, 'x''y' AS c 混合投影转义", async () => { const db = await MetonaSqlark.create({ name: 'v073-escape-star', mode: 'memory' }); await db.defineTable('users', { id: { type: 'string', primaryKey: true } }); await db.query("INSERT INTO users VALUES ('1')"); const rows = rowsOf>(await db.query("SELECT *, 'x''y' AS c FROM users")); const row = rows[0]; expect(row.id).toBe('1'); expect(row.c).toBe("x'y"); await db.close(); }); }); describe('v0.7.3: ANALYZE 统计二级索引', () => { test('aria: 统计含二级索引 LSM(sstableCount/memtableSize 汇总)', async () => { const db = await MetonaSqlark.create({ name: 'v073-analyze', mode: 'aria', diskEngine: 'memory' }); await db.defineTable('users', { id: { type: 'string', primaryKey: true }, email: { type: 'string', index: true }, tag: { type: 'string', index: true }, }); for (let i = 0; i < 30; i++) { await db.query('INSERT INTO users VALUES (?, ?, ?)', [String(i), `e${i}@x.x`, `t${i % 3}`]); } const engine = db.getEngine() as unknown as { lsm: { flush(): Promise }; secondaryIndexes: Map }>; }; await engine.lsm.flush(); for (const idxLsm of engine.secondaryIndexes.values()) await idxLsm.flush(); const stats = await db.query('ANALYZE users'); expect((stats as Record).rowCount).toBe(30); expect(typeof (stats as Record).indexDepth).toBe('number'); expect((stats as Record).sstableCount).toBeGreaterThanOrEqual(1); expect(typeof (stats as Record).memtableSize).toBe('number'); await db.close(); }); test('memory: ANALYZE 不支持抛 NOT_SUPPORTED(护栏)', async () => { const db = await MetonaSqlark.create({ name: 'v073-analyze-memory', mode: 'memory' }); await db.defineTable('users', { id: { type: 'string', primaryKey: true } }); await expect(db.query('ANALYZE users')).rejects.toMatchObject({ code: 'NOT_SUPPORTED' }); await db.close(); }); });