Files
MetonaSqlark/tests/engine/aria-matrix-audit.test.ts
T

318 lines
13 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.6.1 生产矩阵审计 — 模式 × 功能全组合验收
*
* 后端(opfs/kv/memory)× 特性(页面化/加密/压缩/WAL开关/同步模式)×
* 功能(CRUD/二级索引/事务/崩溃恢复/主键变更一致性)
*
* 目标:全部组合生产级可用。任一组合同等断言 —— 数据完整、索引一致、崩溃恢复不丢。
*/
import { AriaEngine } from '../../src/engine/aria/index';
import { createSchema } from '../../src/table/schema';
import { SharedMemoryBackend } from '../../src/engine/kvstore/shared_memory_medium';
import { installOPFSMock } from '../helpers/opfs-mock';
let counter = 0;
function uniqueDB(): string {
return `mx-${Date.now()}-${++counter}-${Math.random().toString(36).slice(2, 6)}`;
}
const SCHEMA = () => createSchema('big', {
id: { type: 'string', primaryKey: true },
val: { type: 'number' },
tag: { type: 'string', index: true },
grp: { type: 'number', index: true },
name: { type: 'string' },
});
/** 写入 total 行(tag/grp 双索引) */
async function seed(engine: AriaEngine, table: string, total: number): Promise<void> {
for (let batch = 0; batch < total / 1000; batch++) {
const rows = [] as Record<string, unknown>[];
for (let i = 0; i < 1000; i++) {
const idx = batch * 1000 + i;
rows.push({ id: `k${idx}`, val: idx, tag: `t${idx % 10}`, grp: idx % 5, name: `U${idx}` });
}
await engine.insert(table, rows);
}
}
/** 双索引完整性断言 */
async function assertIndexes(engine: AriaEngine, table: string, total: number): Promise<void> {
expect(await engine.count(table)).toBe(total);
for (let t = 0; t < 10; t++) {
expect(await engine.find(table, { table, where: { tag: `t${t}` } })).toHaveLength(total / 10);
}
for (let g = 0; g < 5; g++) {
expect(await engine.find(table, { table, where: { grp: g } })).toHaveLength(total / 5);
}
}
/** 崩溃恢复:关闭后端 → 新引擎重开同库 */
async function reopen(dbName: string, config: Record<string, unknown>): Promise<AriaEngine> {
const engine2 = new AriaEngine(config as never);
await engine2.open(dbName, 1);
return engine2;
}
beforeEach(() => {
SharedMemoryBackend.clearRegistry();
installOPFSMock(new Map());
});
describe('生产矩阵审计 — 后端 × 核心功能', () => {
const coreConfig = (backend: string): Record<string, unknown> => ({
storageBackend: backend,
memtableSizeThreshold: 256 * 1024,
checkpointInterval: 5000,
walSyncMode: 'full',
});
it('kv × 3 万行 + 双索引 + 事务 + 崩溃恢复', async () => {
const dbName = uniqueDB();
const engine = new AriaEngine(coreConfig('kv') as never);
await engine.open(dbName, 1);
await engine.createTable(SCHEMA());
await seed(engine, 'big', 30000);
// 事务:提交一批更新
await engine.beginTransaction();
for (let i = 0; i < 100; i++) {
await engine.update('big', { table: 'big', where: { id: `k${i}` } }, { name: `Tx${i}` });
}
await engine.commitTransaction();
await assertIndexes(engine, 'big', 30000);
const row = await engine.find('big', { table: 'big', where: { id: 'k50' } });
expect(row[0].name).toBe('Tx50');
// 事务回滚不产生残留
await engine.beginTransaction();
for (let i = 1000; i < 1100; i++) {
await engine.update('big', { table: 'big', where: { id: `k${i}` } }, { name: `Rx${i}` });
}
await engine.rollbackTransaction();
expect((await engine.find('big', { table: 'big', where: { id: 'k1050' } }))[0].name).toBe('U1050');
// 崩溃恢复
await (engine as any).backend.close();
(engine as any).opened = false;
const engine2 = await reopen(dbName, coreConfig('kv'));
await assertIndexes(engine2, 'big', 30000);
expect((await engine2.find('big', { table: 'big', where: { id: 'k50' } }))[0].name).toBe('Tx50');
await engine2.close();
}, 180000);
it('opfs × 3 万行 + 双索引 + 崩溃恢复', async () => {
const dbName = uniqueDB();
const engine = new AriaEngine(coreConfig('opfs') as never);
await engine.open(dbName, 1);
await engine.createTable(SCHEMA());
await seed(engine, 'big', 30000);
await assertIndexes(engine, 'big', 30000);
await (engine as any).backend.close();
(engine as any).opened = false;
const engine2 = await reopen(dbName, coreConfig('opfs'));
await assertIndexes(engine2, 'big', 30000);
await engine2.close();
}, 180000);
it('memory × 2 万行 + 双索引 + 事务回滚', async () => {
const engine = new AriaEngine(coreConfig('memory') as never);
await engine.open(uniqueDB(), 1);
await engine.createTable(SCHEMA());
await seed(engine, 'big', 20000);
await engine.beginTransaction();
await engine.update('big', { table: 'big', where: { id: 'k7' } }, { tag: 't9' });
await engine.rollbackTransaction();
await assertIndexes(engine, 'big', 20000);
// 更新后索引一致(无脏数据)——k7 从 t7 移到 t9
await engine.update('big', { table: 'big', where: { id: 'k7' } }, { tag: 't9' });
expect(await engine.find('big', { table: 'big', where: { tag: 't9' } })).toHaveLength(2001);
expect(await engine.find('big', { table: 'big', where: { tag: 't7' } })).toHaveLength(1999);
await engine.close();
}, 120000);
});
describe('生产矩阵审计 — 特性组合', () => {
it('kv × 加密 × 压缩 × 页面化(默认开)→ 2 万行 + 崩溃恢复', async () => {
const dbName = uniqueDB();
const cfg = {
storageBackend: 'kv', memtableSizeThreshold: 256 * 1024, checkpointInterval: 5000,
walSyncMode: 'full', compression: true, encryption: { password: 'matrix-secret' },
};
const engine = new AriaEngine(cfg as never);
await engine.open(dbName, 1);
await engine.createTable(SCHEMA());
await seed(engine, 'big', 20000);
await assertIndexes(engine, 'big', 20000);
await (engine as any).backend.close();
(engine as any).opened = false;
const engine2 = await reopen(dbName, cfg);
await assertIndexes(engine2, 'big', 20000);
await engine2.close();
// 错误密码必须拒绝打开
const bad = new AriaEngine({ ...cfg, encryption: { password: 'wrong' } } as never);
await expect(bad.open(dbName, 1)).rejects.toThrow();
}, 180000);
it('kv × pageStorage:false(整 value)→ 2 万行 + 崩溃恢复', async () => {
const dbName = uniqueDB();
const cfg = {
storageBackend: 'kv', memtableSizeThreshold: 256 * 1024, checkpointInterval: 5000,
walSyncMode: 'full', pageStorage: false,
};
const engine = new AriaEngine(cfg as never);
await engine.open(dbName, 1);
await engine.createTable(SCHEMA());
await seed(engine, 'big', 20000);
await assertIndexes(engine, 'big', 20000);
await (engine as any).backend.close();
(engine as any).opened = false;
const engine2 = await reopen(dbName, cfg);
await assertIndexes(engine2, 'big', 20000);
await engine2.close();
}, 180000);
it('opfs × 加密 × 压缩 × 页面化全开 → 2 万行 + 崩溃恢复', async () => {
const dbName = uniqueDB();
const cfg = {
storageBackend: 'opfs', memtableSizeThreshold: 256 * 1024, checkpointInterval: 5000,
walSyncMode: 'full', compression: true, encryption: { password: 'matrix-secret' },
pageStorage: true,
};
const engine = new AriaEngine(cfg as never);
await engine.open(dbName, 1);
await engine.createTable(SCHEMA());
await seed(engine, 'big', 20000);
await assertIndexes(engine, 'big', 20000);
await (engine as any).backend.close();
(engine as any).opened = false;
const engine2 = await reopen(dbName, cfg);
await assertIndexes(engine2, 'big', 20000);
await engine2.close();
}, 180000);
it('opfs × walEnabled:false → 写入 + 优雅关闭后重开完整', async () => {
const dbName = uniqueDB();
const cfg = {
storageBackend: 'opfs', memtableSizeThreshold: 128 * 1024, checkpointInterval: 2000,
walEnabled: false, pageStorage: false,
};
const engine = new AriaEngine(cfg as never);
await engine.open(dbName, 1);
await engine.createTable(SCHEMA());
await seed(engine, 'big', 20000);
await assertIndexes(engine, 'big', 20000);
// 优雅关闭:close 等待后台链 + checkpoint 落盘 → 重开完整
await engine.close();
const engine2 = await reopen(dbName, cfg);
await assertIndexes(engine2, 'big', 20000);
await engine2.close();
}, 180000);
it('kv × walEnabled:false → 写入 + 优雅关闭后重开完整', async () => {
const dbName = uniqueDB();
const cfg = {
storageBackend: 'kv', memtableSizeThreshold: 128 * 1024, checkpointInterval: 2000,
walEnabled: false, pageStorage: false,
};
const engine = new AriaEngine(cfg as never);
await engine.open(dbName, 1);
await engine.createTable(SCHEMA());
await seed(engine, 'big', 20000);
await assertIndexes(engine, 'big', 20000);
await engine.close();
const engine2 = await reopen(dbName, cfg);
await assertIndexes(engine2, 'big', 20000);
await engine2.close();
}, 180000);
it('kv × walSyncMode:batch → 优雅关闭后重开完整(崩溃保底已 checkpoint 数据)', async () => {
const dbName = uniqueDB();
const cfg = {
storageBackend: 'kv', memtableSizeThreshold: 256 * 1024, checkpointInterval: 3000,
walSyncMode: 'batch',
};
const engine = new AriaEngine(cfg as never);
await engine.open(dbName, 1);
await engine.createTable(SCHEMA());
await seed(engine, 'big', 20000);
await assertIndexes(engine, 'big', 20000);
await engine.close();
const engine2 = await reopen(dbName, cfg);
await assertIndexes(engine2, 'big', 20000);
await engine2.close();
}, 180000);
it('opfs × walSyncMode:none → 优雅关闭后重开完整(checkpoint 兜底)', async () => {
const dbName = uniqueDB();
const cfg = {
storageBackend: 'opfs', memtableSizeThreshold: 128 * 1024, checkpointInterval: 3000,
walSyncMode: 'none', pageStorage: false,
};
const engine = new AriaEngine(cfg as never);
await engine.open(dbName, 1);
await engine.createTable(SCHEMA());
await seed(engine, 'big', 20000);
await assertIndexes(engine, 'big', 20000);
await engine.close();
const engine2 = await reopen(dbName, cfg);
await assertIndexes(engine2, 'big', 20000);
await engine2.close();
}, 180000);
});
describe('生产矩阵审计 — 主键变更索引一致性(三后端)', () => {
it('kv × 主键变更:新旧索引无脏数据', async () => {
const engine = new AriaEngine({ storageBackend: 'kv', memtableSizeThreshold: 256 * 1024, checkpointInterval: 100000 } as never);
await engine.open(uniqueDB(), 1);
await engine.createTable(SCHEMA());
await engine.insert('big', [{ id: 'a1', val: 1, tag: 'x', grp: 1, name: 'A' }]);
// 主键 a1 → b1tag/grp 不变)
await engine.update('big', { table: 'big', where: { id: 'a1' } }, { id: 'b1' });
expect(await engine.count('big')).toBe(1);
expect(await engine.find('big', { table: 'big', where: { tag: 'x' } })).toHaveLength(1);
expect(await engine.find('big', { table: 'big', where: { id: 'a1' } })).toHaveLength(0);
await engine.close();
}, 60000);
it('opfs × 主键变更 + 崩溃恢复:索引一致', async () => {
const dbName = uniqueDB();
const engine = new AriaEngine({ storageBackend: 'opfs', memtableSizeThreshold: 256 * 1024, checkpointInterval: 100000, pageStorage: false } as never);
await engine.open(dbName, 1);
await engine.createTable(SCHEMA());
for (let i = 0; i < 5000; i++) {
await engine.insert('big', [{ id: `k${i}`, val: i, tag: `t${i % 10}`, grp: i % 5, name: `U${i}` }]);
}
for (let i = 0; i < 5000; i += 10) {
await engine.update('big', { table: 'big', where: { id: `k${i}` } }, { id: `p${i}` });
}
await (engine as any).backend.close();
(engine as any).opened = false;
const engine2 = new AriaEngine({ storageBackend: 'opfs', memtableSizeThreshold: 256 * 1024, checkpointInterval: 100000, pageStorage: false } as never);
await engine2.open(dbName, 1);
expect(await engine2.count('big')).toBe(5000);
for (let t = 0; t < 10; t++) {
expect(await engine2.find('big', { table: 'big', where: { tag: `t${t}` } })).toHaveLength(500);
}
await engine2.close();
}, 120000);
it('memory × 级联删除 + 索引清理', async () => {
const engine = new AriaEngine({ storageBackend: 'memory', memtableSizeThreshold: 256 * 1024 } as never);
await engine.open(uniqueDB(), 1);
await engine.createTable(createSchema('parent', {
id: { type: 'string', primaryKey: true },
tag: { type: 'string', index: true },
}));
await engine.createTable(createSchema('child', {
id: { type: 'string', primaryKey: true },
pid: { type: 'string', references: 'parent.id', onDelete: 'CASCADE', index: true },
}));
for (let i = 0; i < 2000; i++) {
await engine.insert('parent', [{ id: `p${i}`, tag: `t${i % 10}` }]);
await engine.insert('child', [{ id: `c${i}`, pid: `p${i}` }]);
}
await engine.delete('parent', { table: 'parent', where: { id: 'p5' } });
expect(await engine.count('child')).toBe(1999);
expect(await engine.find('child', { table: 'child', where: { pid: 'p5' } })).toHaveLength(0);
await engine.close();
}, 60000);
});