Files
MetonaSqlark/tests/v074-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

565 lines
24 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.4 回归测试 — 深度审计第七阶段修复
*
* 1. UPDATE/DELETE WHERE 子查询静默 0 行(四引擎)
* 2. 写语句关联引用($col / EXISTS)显式 NOT_SUPPORTED
* 3. EXPLAIN UPDATE/DELETE 子查询 estimatedRows
* 4. 主键 NULL/undefined 静默入库(四引擎)
* 5. DROP INDEX 建表 UNIQUE 约束保护 / CREATE UNIQUE INDEX 可解除
* 6. GROUP BY null 与 'null' 字符串分离(DISTINCT/UNION 同类)
* 7. UPDATE 未知列显式报错
* 8. queryStream 多语句显式报错
* 9. KVStore 后台错误跨 reopen 清理
* 10. Hybrid beginTransaction 部分成功补偿回滚
* 11. findStream 真惰性(limit 提前终止)
* 12. reindex 单次扫描多索引列重建
*/
import { MetonaSqlark } from '../src/core';
import { KVStore } from '../src/engine/kvstore/index';
import { SharedMemoryBackend } from '../src/engine/kvstore/shared_memory_medium';
const uniqueName = (tag: string): string => `v074-${tag}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
async function makeDb(mode: 'memory' | 'disk' | 'aria' | 'hybrid') {
const db = await MetonaSqlark.create({
name: uniqueName(mode),
mode,
diskEngine: 'memory',
});
await db.defineTable('t', {
id: { type: 'string', primaryKey: true },
v: { type: 'number' },
});
return db;
}
// ===================================================================
// 1. UPDATE / DELETE 子查询
// ===================================================================
describe('v0.7.4: UPDATE/DELETE WHERE 子查询(此前静默 0 行)', () => {
test.each([
['memory', 'memory'],
['disk', 'disk'],
['aria', 'aria'],
['hybrid', 'hybrid'],
] as const)('%s: UPDATE IN (SELECT) 正确影响行', async (_label, mode) => {
const db = await makeDb(mode);
await db.query("INSERT INTO t VALUES ('1', 10), ('2', 20)");
const n = await db.query('UPDATE t SET v = 99 WHERE id IN (SELECT id FROM t WHERE v > 15)');
expect(n).toBe(1);
const rows = await db.query('SELECT * FROM t ORDER BY id') as Record<string, unknown>[];
expect(rows.map((r) => r.v)).toEqual([10, 99]);
await db.close();
});
test.each([
['memory', 'memory'],
['disk', 'disk'],
['aria', 'aria'],
['hybrid', 'hybrid'],
] as const)('%s: DELETE IN (SELECT) 正确影响行', async (_label, mode) => {
const db = await makeDb(mode);
await db.query("INSERT INTO t VALUES ('1', 10), ('2', 20)");
const n = await db.query('DELETE FROM t WHERE id IN (SELECT id FROM t WHERE v > 15)');
expect(n).toBe(1);
const rows = await db.query('SELECT * FROM t') as Record<string, unknown>[];
expect(rows.map((r) => r.id)).toEqual(['1']);
await db.close();
});
test('memory: UPDATE 标量子查询(op (SELECT)', async () => {
const db = await makeDb('memory');
await db.query("INSERT INTO t VALUES ('1', 10), ('2', 20)");
const n = await db.query('UPDATE t SET v = 1 WHERE v = (SELECT MAX(v) FROM t)');
expect(n).toBe(1);
await db.close();
});
test('aria: UPDATE 子查询后持久化重开一致(kv 后端跨实例共享介质)', async () => {
const name = uniqueName('aria-persist');
const db1 = await MetonaSqlark.create({ name, mode: 'aria', diskEngine: 'kv' });
await db1.defineTable('t', {
id: { type: 'string', primaryKey: true },
v: { type: 'number' },
});
await db1.query("INSERT INTO t VALUES ('1', 10), ('2', 20)");
await db1.query('UPDATE t SET v = 99 WHERE id IN (SELECT id FROM t WHERE v > 15)');
await db1.close();
const db2 = await MetonaSqlark.create({ name, mode: 'aria', diskEngine: 'kv' });
const rows = await db2.query('SELECT * FROM t ORDER BY id') as Record<string, unknown>[];
expect(rows.map((r) => r.v)).toEqual([10, 99]);
await db2.close();
});
});
// ===================================================================
// 2. 写语句关联引用显式报错
// ===================================================================
describe('v0.7.4: 写语句关联引用显式 NOT_SUPPORTED', () => {
test('memory: SQL 列比较(a = b)解析层明确报错(此前静默或误解析)', async () => {
const db = await makeDb('memory');
await db.query("INSERT INTO t VALUES ('1', 10)");
// parser 不支持列引用作为比较值 → 明确 PARSE_ERROR,绝不静默影响行
const err = await db.query('UPDATE t SET v = 1 WHERE id = id').catch((e: unknown) => e as { code?: string });
expect((err as { code?: string }).code).toBeDefined();
// 行未被修改(无静默副作用)
const rows = await db.query('SELECT * FROM t') as Record<string, unknown>[];
expect(rows[0].v).toBe(10);
await db.close();
});
test('memory: QueryBuilder 列引用更新 WHERE 显式 NOT_SUPPORTED', async () => {
const db = await makeDb('memory');
await db.query("INSERT INTO t VALUES ('1', 10)");
await expect(
db.table('t').update({ v: 1 }).where({ id: { $eq: { $col: 'id' } } }).execute(),
).rejects.toMatchObject({ code: 'NOT_SUPPORTED' });
await db.close();
});
test('memory: DELETE WHERE EXISTS(关联)报错而非静默 0 行', async () => {
const db = await makeDb('memory');
await db.query("INSERT INTO t VALUES ('1', 10)");
await expect(
db.query('DELETE FROM t WHERE EXISTS (SELECT 1 FROM t t2 WHERE t2.id = t.id)'),
).rejects.toMatchObject({ code: 'NOT_SUPPORTED' });
await db.close();
});
test('aria: UPDATE 关联 EXISTS 报错', async () => {
const db = await makeDb('aria');
await db.query("INSERT INTO t VALUES ('1', 10)");
await expect(
db.query('UPDATE t SET v = 1 WHERE EXISTS (SELECT 1 FROM t t2 WHERE t2.id = t.id)'),
).rejects.toMatchObject({ code: 'NOT_SUPPORTED' });
await db.close();
});
});
// ===================================================================
// 3. EXPLAIN 子查询 estimatedRows
// ===================================================================
describe('v0.7.4: EXPLAIN UPDATE/DELETE 子查询估算', () => {
test('EXPLAIN UPDATE IN (SELECT) estimatedRows 正确', async () => {
const db = await makeDb('memory');
await db.query("INSERT INTO t VALUES ('1', 10), ('2', 20)");
const plan = await db.query('EXPLAIN UPDATE t SET v = 1 WHERE id IN (SELECT id FROM t WHERE v > 15)') as Record<string, unknown>;
expect(plan.estimatedRows).toBe(1);
// EXPLAIN 不产生副作用
const rows = await db.query('SELECT * FROM t') as Record<string, unknown>[];
expect(rows.map((r) => r.v)).toEqual([10, 20]);
await db.close();
});
});
// ===================================================================
// 4. 主键 NULL/undefined 拒绝
// ===================================================================
describe('v0.7.4: 主键 NULL/undefined 拒绝', () => {
test.each([
['memory', 'memory'],
['disk', 'disk'],
['aria', 'aria'],
['hybrid', 'hybrid'],
] as const)('%s: INSERT 主键 NULL 拒绝', async (_label, mode) => {
const db = await makeDb(mode);
await expect(db.query('INSERT INTO t VALUES (NULL, 1)')).rejects.toMatchObject({ code: 'VALIDATION_ERROR' });
const rows = await db.query('SELECT * FROM t') as Record<string, unknown>[];
expect(rows).toHaveLength(0);
await db.close();
});
test.each([
['memory', 'memory'],
['disk', 'disk'],
['aria', 'aria'],
] as const)('%s: INSERT 省略主键(无 default)拒绝', async (_label, mode) => {
const db = await makeDb(mode);
await expect(db.query('INSERT INTO t (v) VALUES (5)')).rejects.toMatchObject({ code: 'VALIDATION_ERROR' });
await db.close();
});
test('memory: UPDATE 主键置 null 拒绝', async () => {
const db = await makeDb('memory');
await db.query("INSERT INTO t VALUES ('1', 10)");
await expect(db.query("UPDATE t SET id = NULL WHERE id = '1'")).rejects.toMatchObject({ code: 'VALIDATION_ERROR' });
const rows = await db.query('SELECT * FROM t') as Record<string, unknown>[];
expect(rows[0].id).toBe('1');
await db.close();
});
test('memory: 主键 default 生效时允许省略', async () => {
const db = await MetonaSqlark.create({ name: uniqueName('pkdefault'), mode: 'memory' });
await db.defineTable('t', {
id: { type: 'string', primaryKey: true, default: 'auto' },
v: { type: 'number' },
});
const pks = await db.query('INSERT INTO t (v) VALUES (5)') as string[];
expect(pks).toEqual(['auto']);
await db.close();
});
test('aria: 主键 undefinedTable API)拒绝', async () => {
const db = await makeDb('aria');
await expect(
db.table('t').insert({ v: 3 } as Record<string, unknown>),
).rejects.toMatchObject({ code: 'VALIDATION_ERROR' });
await db.close();
});
});
// ===================================================================
// 5. DROP INDEX 与 UNIQUE 约束
// ===================================================================
describe('v0.7.4: DROP INDEX UNIQUE 约束保护', () => {
test.each([
['memory', 'memory'],
['aria', 'aria'],
] as const)('%s: 建表 UNIQUE 列 DROP INDEX 拒绝且约束保留', async (_label, mode) => {
const db = await MetonaSqlark.create({ name: uniqueName('uniq'), mode, diskEngine: 'memory' });
await db.defineTable('u', {
id: { type: 'string', primaryKey: true },
email: { type: 'string', unique: true },
});
await db.query("INSERT INTO u VALUES ('1', 'a@x.com')");
await expect(db.query('DROP INDEX idx ON u (email)')).rejects.toMatchObject({ code: 'NOT_SUPPORTED' });
// 约束仍生效
await expect(db.query("INSERT INTO u VALUES ('2', 'a@x.com')")).rejects.toMatchObject({ code: 'UNIQUE_VIOLATION' });
const schema = await db.getEngine().getTableSchema('u');
expect(schema!.columns.email.unique).toBe(true);
await db.close();
});
test.each([
['memory', 'memory'],
['aria', 'aria'],
] as const)('%s: CREATE UNIQUE INDEX 后 DROP 可解除(本会话)', async (_label, mode) => {
const db = await MetonaSqlark.create({ name: uniqueName('uniq2'), mode, diskEngine: 'memory' });
await db.defineTable('u', {
id: { type: 'string', primaryKey: true },
email: { type: 'string' },
});
await db.query("INSERT INTO u VALUES ('1', 'a@x.com')");
await db.query('CREATE UNIQUE INDEX idx_u ON u (email)');
await expect(db.query("INSERT INTO u VALUES ('2', 'a@x.com')")).rejects.toMatchObject({ code: 'UNIQUE_VIOLATION' });
await db.query('DROP INDEX idx_u ON u (email)');
// 约束已随索引解除
await db.query("INSERT INTO u VALUES ('2', 'a@x.com')");
const rows = await db.query('SELECT * FROM u') as Record<string, unknown>[];
expect(rows).toHaveLength(2);
await db.close();
});
test('aria: 建表 UNIQUE DROP INDEX 拒绝后重启约束仍在(kv 后端)', async () => {
const name = uniqueName('uniq3');
const db1 = await MetonaSqlark.create({ name, mode: 'aria', diskEngine: 'kv' });
await db1.defineTable('u', {
id: { type: 'string', primaryKey: true },
email: { type: 'string', unique: true },
});
await db1.query("INSERT INTO u VALUES ('1', 'a@x.com')");
await expect(db1.query('DROP INDEX idx ON u (email)')).rejects.toMatchObject({ code: 'NOT_SUPPORTED' });
await db1.close();
const db2 = await MetonaSqlark.create({ name, mode: 'aria', diskEngine: 'kv' });
await expect(db2.query("INSERT INTO u VALUES ('2', 'a@x.com')")).rejects.toMatchObject({ code: 'UNIQUE_VIOLATION' });
await db2.close();
});
});
// ===================================================================
// 6. GROUP BY / DISTINCT / UNION 键编码
// ===================================================================
describe('v0.7.4: 分组/去重键类型安全编码', () => {
test('memory: GROUP BY null 与 "null" 字符串分离', async () => {
const db = await MetonaSqlark.create({ name: uniqueName('grp'), mode: 'memory' });
await db.defineTable('g', {
id: { type: 'string', primaryKey: true },
grp: { type: 'string' },
});
await db.query("INSERT INTO g VALUES ('a', NULL), ('b', 'null'), ('c', NULL)");
const rows = await db.query('SELECT grp, COUNT(*) AS c FROM g GROUP BY grp') as Record<string, unknown>[];
expect(rows).toHaveLength(2);
const byKey = new Map(rows.map((r) => [r.grp, r.c]));
expect(byKey.get(null)).toBe(2);
expect(byKey.get('null')).toBe(1);
await db.close();
});
test('aria: GROUP BY null 分离', async () => {
const db = await MetonaSqlark.create({ name: uniqueName('grp-aria'), mode: 'aria', diskEngine: 'memory' });
await db.defineTable('g', {
id: { type: 'string', primaryKey: true },
grp: { type: 'string' },
});
await db.query("INSERT INTO g VALUES ('a', NULL), ('b', 'null')");
const rows = await db.query('SELECT grp, COUNT(*) AS c FROM g GROUP BY grp') as Record<string, unknown>[];
expect(rows).toHaveLength(2);
await db.close();
});
test('memory: DISTINCT null 与 "\\0" 字符串分离', async () => {
const db = await MetonaSqlark.create({ name: uniqueName('dist'), mode: 'memory' });
await db.defineTable('g', {
id: { type: 'string', primaryKey: true },
grp: { type: 'string' },
});
await db.query(`INSERT INTO g VALUES ('a', NULL), ('b', '\\0'), ('c', '\\0')`);
const rows = await db.query('SELECT DISTINCT grp FROM g') as Record<string, unknown>[];
expect(rows).toHaveLength(2);
await db.close();
});
test('memory: UNION null 与字符串不吞并', async () => {
const db = await MetonaSqlark.create({ name: uniqueName('union'), mode: 'memory' });
await db.defineTable('g', {
id: { type: 'string', primaryKey: true },
grp: { type: 'string' },
});
await db.query("INSERT INTO g VALUES ('a', NULL), ('b', 'null')");
const rows = await db.query(
"SELECT grp FROM g WHERE id = 'a' UNION SELECT grp FROM g WHERE id = 'b'",
) as Record<string, unknown>[];
expect(rows).toHaveLength(2);
await db.close();
});
});
// ===================================================================
// 7. UPDATE 未知列报错
// ===================================================================
describe('v0.7.4: UPDATE 未知列显式报错', () => {
test.each([
['memory', 'memory'],
['disk', 'disk'],
['aria', 'aria'],
['hybrid', 'hybrid'],
] as const)('%s: SQL UPDATE 未知列 COLUMN_NOT_FOUND', async (_label, mode) => {
const db = await makeDb(mode);
await db.query("INSERT INTO t VALUES ('1', 10)");
await expect(db.query("UPDATE t SET nonexistent = 9 WHERE id = '1'")).rejects.toMatchObject({ code: 'COLUMN_NOT_FOUND' });
const rows = await db.query('SELECT * FROM t') as Record<string, unknown>[];
expect(JSON.stringify(rows[0])).not.toContain('nonexistent');
await db.close();
});
test('memory: Table API update 未知列报错', async () => {
const db = await makeDb('memory');
await db.query("INSERT INTO t VALUES ('1', 10)");
await expect(
db.table('t').update({ nonexistent: 1 }).where({ id: '1' }).execute(),
).rejects.toMatchObject({ code: 'COLUMN_NOT_FOUND' });
await db.close();
});
test('memory: 更新既有列不受影响(回归护栏)', async () => {
const db = await makeDb('memory');
await db.query("INSERT INTO t VALUES ('1', 10)");
const n = await db.query("UPDATE t SET v = 20 WHERE id = '1'");
expect(n).toBe(1);
await db.close();
});
});
// ===================================================================
// 8. queryStream 多语句
// ===================================================================
describe('v0.7.4: queryStream 多语句显式报错', () => {
test('queryStream 多语句抛 PARSE_ERROR(不静默忽略后续语句)', async () => {
const db = await makeDb('memory');
await db.query("INSERT INTO t VALUES ('1', 10)");
await expect(
db.queryStream('SELECT * FROM t; DELETE FROM t', () => {}),
).rejects.toMatchObject({ code: 'PARSE_ERROR' });
// 后续语句未执行
const rows = await db.query('SELECT * FROM t') as Record<string, unknown>[];
expect(rows).toHaveLength(1);
await db.close();
});
test('queryStream 单语句正常流式(回归护栏)', async () => {
const db = await makeDb('memory');
await db.query("INSERT INTO t VALUES ('1', 10), ('2', 20)");
let n = 0;
const count = await db.queryStream('SELECT * FROM t', () => { n++; });
expect(count).toBe(2);
expect(n).toBe(2);
await db.close();
});
});
// ===================================================================
// 9. KVStore 后台错误跨 reopen
// ===================================================================
describe('v0.7.4: KVStore 后台错误状态生命周期', () => {
test('close/reopen 后 checkpoint 不抛旧 KV_BACKGROUND_ERROR', async () => {
SharedMemoryBackend.clearRegistry();
const medium = new SharedMemoryBackend();
const kv1 = new KVStore(medium);
await kv1.open('v074-kv-life');
// 制造一次失败写入:直接在介质层破坏 append(用只读 medium 模拟失败)
await kv1.close();
// 用带失败注入的介质:append 抛错 → lastBackgroundError 记录
const failingMedium = {
open: async (_n: string) => {},
close: async () => {},
isOpen: () => true,
read: async () => null,
write: async () => {},
append: async () => { throw new Error('disk full'); },
writeMany: async () => {},
delete: async () => {},
deleteMany: async () => {},
listKeys: async () => [],
exists: async () => false,
clear: async () => {},
};
const kv2 = new KVStore(failingMedium as never);
await kv2.open('v074-kv-fail');
await expect(kv2.put('k', new TextEncoder().encode('v').buffer)).rejects.toThrow();
await kv2.close();
// 重开同一实例(同 medium 不再失败)
(failingMedium.append as unknown) = async () => {};
await kv2.open('v074-kv-fail');
// 无残留后台错误:checkpoint 不抛
await expect(kv2.checkpoint()).resolves.not.toThrow();
await kv2.close();
});
});
// ===================================================================
// 10. Hybrid beginTransaction 补偿
// ===================================================================
describe('v0.7.4: Hybrid beginTransaction 部分成功补偿', () => {
test('磁盘 begin 失败时内存快照回滚(后续事务可正常开始)', async () => {
const db = await MetonaSqlark.create({ name: uniqueName('hybrid-begin'), mode: 'hybrid', diskEngine: 'memory' });
await db.defineTable('t', {
id: { type: 'string', primaryKey: true },
});
// 让磁盘引擎 begin 抛错:先把磁盘引擎置于活跃事务
const diskEngine = (db.getEngine() as unknown as { getDiskEngine?: () => { beginTransaction(): Promise<void> } }).getDiskEngine
? undefined
: undefined;
// HybridEngine 无 getDiskEngine 公共接口 → 直接通过 disk 事务路径制造失败:
// 内存引擎先 begin 成功、磁盘引擎第二个 begin 报 TX_ACTIVE
const hybridEngine = db.getEngine() as unknown as {
beginTransaction: () => Promise<void>;
rollbackTransaction: () => Promise<void>;
};
await hybridEngine.beginTransaction();
// 磁盘引擎现在活跃;再次 begin → 内存 begin 成功、磁盘抛 TX_ACTIVE → 补偿回滚
await expect(hybridEngine.beginTransaction()).rejects.toMatchObject({ code: 'TX_ACTIVE' });
await hybridEngine.rollbackTransaction();
// 补偿后事务可正常开始并提交
await db.transaction(async (trx) => {
await trx.table('t').insert({ id: '1' });
});
const rows = await db.query('SELECT * FROM t') as Record<string, unknown>[];
expect(rows).toHaveLength(1);
expect(diskEngine).toBeUndefined();
await db.close();
});
});
// ===================================================================
// 11. findStream 真惰性
// ===================================================================
describe('v0.7.4: aria findStream 真惰性(limit 提前终止)', () => {
test('大表 limit 1 流式只消费必要条目', async () => {
const db = await MetonaSqlark.create({ name: uniqueName('lazy'), mode: 'aria', diskEngine: 'memory' });
await db.defineTable('logs', {
id: { type: 'string', primaryKey: true },
level: { type: 'string' },
});
const rows: Record<string, unknown>[] = [];
for (let i = 0; i < 2000; i++) rows.push({ id: `l${i}`, level: i % 2 ? 'error' : 'info' });
await db.table('logs').insertMany(rows);
let called = 0;
const count = await db.table('logs').stream(
() => { called++; },
{ limit: 5 },
);
expect(count).toBe(5);
expect(called).toBe(5);
// 语义护栏:无 limit 时全量
let all = 0;
await db.table('logs').stream(() => { all++; });
expect(all).toBe(2000);
await db.close();
});
test('queryStream limit 流式正确 + 与物化结果一致', async () => {
const db = await MetonaSqlark.create({ name: uniqueName('lazy2'), mode: 'aria', diskEngine: 'memory' });
await db.defineTable('logs', {
id: { type: 'string', primaryKey: true },
level: { type: 'string' },
});
const rows: Record<string, unknown>[] = [];
for (let i = 0; i < 500; i++) rows.push({ id: `l${i}`, level: i % 2 ? 'error' : 'info' });
await db.table('logs').insertMany(rows);
const streamed: string[] = [];
const count = await db.queryStream(
"SELECT id FROM logs WHERE level = 'error' LIMIT 7",
(row) => { streamed.push((row as Record<string, unknown>).id as string); },
);
expect(count).toBe(7);
const materialized = await db.query(
"SELECT id FROM logs WHERE level = 'error' LIMIT 7",
) as Record<string, unknown>[];
expect(streamed).toEqual(materialized.map((r) => r.id));
await db.close();
});
});
// ===================================================================
// 12. reindex 多索引列重建
// ===================================================================
describe('v0.7.4: reindex 单次扫描重建多索引列', () => {
test('aria: 多索引列 REINDEX 后查询完整', async () => {
const db = await MetonaSqlark.create({ name: uniqueName('reidx'), mode: 'aria', diskEngine: 'memory' });
await db.defineTable('u', {
id: { type: 'string', primaryKey: true },
email: { type: 'string', unique: true },
city: { type: 'string', index: true },
});
const rows: Record<string, unknown>[] = [];
for (let i = 0; i < 300; i++) {
rows.push({ id: `u${i}`, email: `e${i}@x.com`, city: i % 3 ? 'Beijing' : 'Shanghai' });
}
await db.table('u').insertMany(rows);
await db.query('REINDEX TABLE u');
const bj = await db.query("SELECT * FROM u WHERE city = 'Beijing'") as Record<string, unknown>[];
expect(bj).toHaveLength(200);
const email = await db.query("SELECT * FROM u WHERE email = 'e1@x.com'") as Record<string, unknown>[];
expect(email).toHaveLength(1);
// 唯一约束在重建后仍生效
await expect(db.table('u').insert({ id: 'uX', email: 'e1@x.com', city: 'Beijing' })).rejects.toMatchObject({ code: 'UNIQUE_VIOLATION' });
await db.close();
});
test('memory: reindex 路径不受影响(无实现护栏)', async () => {
const db = await MetonaSqlark.create({ name: uniqueName('reidx-mem'), mode: 'memory' });
await db.defineTable('u', {
id: { type: 'string', primaryKey: true },
email: { type: 'string', unique: true },
});
await db.query("INSERT INTO u VALUES ('1', 'a@x.com')");
await expect(db.query('REINDEX TABLE u')).rejects.toMatchObject({ code: 'NOT_SUPPORTED' });
await db.close();
});
});