Files
MetonaSqlark/tests/engine/aria-mvcc-savepoint.test.ts
T
thzxx f97c5a6001
CI / test (18.x) (push) Successful in 19m13s
CI / test (20.x) (push) Successful in 18m8s
CI / test (22.x) (push) Successful in 17m18s
CI / test (24.x) (push) Successful in 22m57s
CI / e2e (push) Successful in 9m53s
fix(P2): v0.6.3 原子性/一致性/资源治理 — 8 项修复 + 12 回归
- KVStore 混合写单记录原子:新增 writeBatch(put+delete 同一条日志记录),
  KVStoreEngine 全部混合写路径统一(兑现真原子宣称,崩溃无新旧行并存)
- WAL full 模式写入失败抛错(此前 console.warn 吞错 → 崩溃即丢且无感知)
- MemoryEngine SET NULL 级联索引残留:复用 removeIndexEntries(消除虚假 UNIQUE_VIOLATION)
- delete 级联两阶段:先全量 RESTRICT 预检(沿 CASCADE 链递归)再执行,无部分级联
  (Memory/Aria 对齐)
- BufferPool 驱逐同步清理 pages Map(EvictionManager onRemove 回调,内存预算真实生效)
- MVCC commit 清理已提交版本(版本链仅作事务内 undo,消除行数据双份常驻)
- LSM.flush 重复入链修复(入链即置空 immutable)+ frozenMemtables 可见性时序
- rollbackToSavepoint 重建受影响表二级索引(消除过期索引条目)

测试 1114 → 1126(71 套件);行覆盖率 89.7%;版本 0.6.3
2026-08-13 10:30:39 +08:00

124 lines
4.8 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.
/**
* AriaEngine MVCC 事务隔离 + Savepoint 测试
* 全部 MemoryBackend,零卡死
*/
import { AriaEngine } from '../../src/engine/aria/index';
import { createSchema } from '../../src/table/schema';
import { installOPFSMock } from '../helpers/opfs-mock';
beforeEach(() => { installOPFSMock(new Map()); });
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();
});
});