/** * AriaEngine MVCC 事务隔离 + Savepoint 测试 * 全部 MemoryBackend,零卡死 */ import { AriaEngine } from '../../src/engine/aria/index'; import { createSchema } from '../../src/table/schema'; import { resetOPFSMock } from '../helpers/storage-harness'; beforeEach(() => { resetOPFSMock(); }); describe('AriaEngine — MVCC 事务 + Savepoint', () => { let engine: AriaEngine; beforeEach(async () => { engine = new AriaEngine({ storageBackend: 'memory' }); await engine.open('mvcc-test', 1); await engine.createTable(createSchema('users', { id: { type: 'string', primaryKey: true }, name: { type: 'string', required: true }, balance: { type: 'number', default: 0 }, })); }); afterEach(async () => { await engine.close(); }); // ---- MVCC 事务 ---- it('beginTransaction 分配唯一事务ID', () => { const id1 = (engine as any).mvcc.beginTransaction(); const id2 = (engine as any).mvcc.beginTransaction(); expect(id1).not.toBe(id2); }); it('commit 后清理本事务版本(undo 记录不保留,v0.6.3)', () => { const txnId = (engine as any).mvcc.beginTransaction(); (engine as any).mvcc.writeVersion('users', '1', { name: 'Alice' }, txnId); (engine as any).mvcc.commitTransaction(txnId); const store = (engine as any).mvcc.versionStore; // v0.6.3: 已提交版本随 commit 清理(快照读取已移除,版本链仅作事务内 undo) expect(store.has('users.1')).toBe(false); }); it('rollback 移除本事务版本', () => { const txnId = (engine as any).mvcc.beginTransaction(); (engine as any).mvcc.writeVersion('users', '1', { name: 'Alice' }, txnId); (engine as any).mvcc.rollbackTransaction(txnId); const store = (engine as any).mvcc.versionStore; expect(store.has('users.1')).toBe(false); }); it('引擎层事务 commit 后数据可见', async () => { await engine.beginTransaction(); await engine.insert('users', [{ id: '1', name: 'Alice', balance: 100 }]); await engine.commitTransaction(); expect(await engine.count('users')).toBe(1); }); it('引擎层事务 rollback 后数据消失', async () => { await engine.insert('users', [{ id: '1', name: 'Alice' }]); await engine.beginTransaction(); await engine.insert('users', [{ id: '2', name: 'Bob' }]); await engine.rollbackTransaction(); expect(await engine.count('users')).toBe(1); }); it('GC 清理过旧版本(未提交版本链保留最新 N 个)', () => { for (let i = 0; i < 200; i++) { const txnId = (engine as any).mvcc.beginTransaction(); (engine as any).mvcc.writeVersion('users', '1', { ver: i }, txnId); // 不 commit:未提交版本保留在链上(活跃事务 undo) } (engine as any).mvcc.gc(50); expect((engine as any).mvcc.versionStore.get('users.1').length).toBe(50); }); it('多事务并发未提交 + 部分提交:commit 版本清理、未提交保留', () => { const mvcc = (engine as any).mvcc; const txnA = mvcc.beginTransaction(); mvcc.writeVersion('users', '1', { name: 'A1' }, txnA); const txnB = mvcc.beginTransaction(); mvcc.writeVersion('users', '1', { name: 'B1' }, txnB); mvcc.commitTransaction(txnA); // txnA 版本已清理;txnB 未提交版本保留 const versions = mvcc.versionStore.get('users.1') as { txnId: number }[]; expect(versions.map((v) => v.txnId)).toEqual([txnB]); mvcc.rollbackTransaction(txnB); expect(mvcc.versionStore.has('users.1')).toBe(false); }); // ---- Savepoint ---- it('savepoint 创建成功', async () => { await engine.beginTransaction(); await (engine as any).savepoint('sp1'); expect((engine as any).savepoints.has('sp1')).toBe(true); await engine.rollbackTransaction(); }); it('rollbackToSavepoint 恢复快照', async () => { await engine.insert('users', [{ id: '1', name: 'Alice' }]); await engine.beginTransaction(); await engine.insert('users', [{ id: '2', name: 'Bob' }]); await (engine as any).savepoint('sp1'); await engine.insert('users', [{ id: '3', name: 'Charlie' }]); await (engine as any).rollbackToSavepoint('sp1'); await engine.commitTransaction(); expect(await engine.count('users')).toBe(2); }); it('releaseSavepoint 释放后不可回滚', async () => { await engine.beginTransaction(); await (engine as any).savepoint('sp1'); await (engine as any).releaseSavepoint('sp1'); expect((engine as any).savepoints.has('sp1')).toBe(false); await engine.rollbackTransaction(); }); it('重复 savepoint 名称抛出错误', async () => { await engine.beginTransaction(); await (engine as any).savepoint('sp1'); await expect((engine as any).savepoint('sp1')).rejects.toThrow('already exists'); await engine.rollbackTransaction(); }); });