Files
MetonaSqlark/tests/v073-fixes.test.ts
T
thzxx 0dba1abf2a test(P0): v0.8.0 验证基座与工程门禁根治
工作流 C-1 / C-3 前半 + 测试代码类型检查。

【故障注入基座】新增 tests/helpers/storage-harness.ts + faulty-backend.ts
- TransactionalFileStore:忠实 OPFS 提交语义(close 才可见)+ 字节级故障注入
  (failNextWrite/Append/Delete、truncateAppendTo 撕裂写、crashPending 真崩溃)
- 删除旧 opfs-mock:读返回内部引用、keepExistingData:false 不截断、close 空实现
  导致"提交前可见"等真实缺陷无法被测出(31 个测试文件迁移至新 harness)
- 删除 aria-opfs-backend 内的第三份重复 mock(含从未被断言使用的 writeCalls 死代码
  与 entry.content.subarray 恒等分支)
- FaultyBackend:包装任意 IStorageBackend 注入故障;crash() 明确区别于 close()
  (后者是优雅停机,会刷完写队列 —— 这正是此前所有"崩溃恢复"测试的真相)
- 16 条基座自测证明注入真的生效(含 close 不能当崩溃的对照组)

【覆盖率口径】jest.config.cjs
- 移除 '!src/**/index.ts'(该 glob 把 AriaEngine 主实现等 15 个实现文件整体
  排除出统计,与 v0.2.6 曾承认过的问题同源),改为只排除纯类型声明文件并附理由
- 新增 coverageThreshold 门禁(此前完全不存在)
- 真实基线:语句 90.66% / 分支 82.94% / 函数 94.36% / 行 93.43%
- 修正 testMatch 使 tests/helpers 下的测试可被发现

【测试代码类型检查】tsconfig.test.json + npm run typecheck:tests
- 修复 103 个测试代码类型错误(此前 babel 剥离类型 + tsconfig 排除 tests,全部隐藏)
- 新增 tests/helpers/assertions.ts:nonNull/decode/rows/object/engineMethod/expectCode
  以断言收窄替代 as any
- 消除 21 个 lint warning(含 v043-hardening 中定义后从未调用的 mockOPFS 死代码)
- parser.test.ts 12 处 toBeDefined() 空断言升级为结构断言(并新增 AND/OR 优先级用例,
  当前红灯,对应总账第 11 项,将在工作流 A 修复)

【版本契约】新增 tests/version-contract.test.ts
- 校验 src VERSION / package.json / dist 三者一致,替代两处硬编码版本字面量

【CI 门禁】.gitea/workflows/ci.yml
- lint 去掉 continue-on-error(此前永远不让 CI 变红)
- 新增 tests 类型检查、--coverage 覆盖率门禁、dist 与源码同步校验
- 版本 0.7.4 升至 0.8.0
2026-09-14 21:03:06 +08:00

629 lines
29 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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: 索引列 $eq: nullQuery 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')");
const rows = await db.table('users').select(['id']).where({ email: { $eq: null } }).execute();
expect(rows).toHaveLength(1);
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<string, unknown>[] = [];
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<string, unknown>[] = [];
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<string, unknown>[] = [];
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<string, unknown>[] = [];
await db.queryStream('SELECT * FROM t1 WHERE t1.x = t1.y', (r) => collected.push(r));
expect(collected).toHaveLength((viaQuery as Record<string, unknown>[]).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<Record<string, unknown>>(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<Record<string, unknown>>(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<Record<string, unknown>>(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<string, unknown>[];
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<string, unknown>[];
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<Record<string, unknown>>(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<Record<string, unknown>>(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<void> } }> {
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<void> } } };
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<void> }; secondaryIndexes: Map<string, { flush(): Promise<void> }> };
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<string, unknown>).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<string, unknown>).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<Record<string, unknown>>(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: 统计含二级索引 LSMsstableCount/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<void> };
secondaryIndexes: Map<string, { flush(): Promise<void> }>;
};
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<string, unknown>).rowCount).toBe(30);
expect(typeof (stats as Record<string, unknown>).indexDepth).toBe('number');
expect((stats as Record<string, unknown>).sstableCount).toBeGreaterThanOrEqual(1);
expect(typeof (stats as Record<string, unknown>).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();
});
});