fix: v0.7.4 写语句子查询 / 约束硬化 / 真惰性流式 — UPDATE-DELETE WHERE 子查询静默 0 行(四引擎,关联引用显式 NOT_SUPPORTED + EXPLAIN 同步)/ 主键 NULL-undefined 强制拒绝 / DROP INDEX 保留建表 UNIQUE(仅索引来源可解除)/ GROUP BY-DISTINCT-UNION 键类型安全编码 / UPDATE 未知列报错 / queryStream 多语句拒绝 / KVStore 后台错误跨 reopen 清理 + Hybrid begin 补偿 / RB-Tree 删除双黑修复 + LSM 死代码清理 / findStream 迭代器化真惰性(limit 早停 O(1) 内存)/ REINDEX 单次扫描 + 48 回归

This commit is contained in:
thzxx
2026-08-15 15:08:33 +08:00
parent ccfc39656b
commit d4a3f4acd2
17 changed files with 943 additions and 72 deletions
+1 -1
View File
@@ -28,7 +28,7 @@ beforeEach(() => { installOPFSMock(new Map()); });
describe('[v0.2.5] P0-1: 版本号统一', () => {
test('VERSION 常量为当前版本(0.6.0', () => {
expect(VERSION).toBe('0.7.3');
expect(VERSION).toBe('0.7.4');
});
});
+1 -1
View File
@@ -403,7 +403,7 @@ describe('[v0.3.3] P1-9: Savepoint + MVCC 一致性', () => {
describe('[v0.3.3] 端到端', () => {
test('全部修复点可共存于 MetonaSqlark API', async () => {
expect(VERSION).toBe('0.7.3');
expect(VERSION).toBe('0.7.4');
const db = new MetonaSqlark({ name: `e2e-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`, mode: 'memory' });
await db.init();
await db.defineTable('users', {
+565
View File
@@ -0,0 +1,565 @@
/**
* 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 { MemoryEngine } from '../src/engine/memory';
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 },
});
const disk = (db.getEngine() as { getDiskEngineType?: () => string } & Record<string, unknown>);
// 让磁盘引擎 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>;
};
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();
});
});