Files
MetonaSqlark/tests/engine/aria-idx-flush-race.test.ts
T
thzxx 05e6823bf1
CI / test (22.x) (push) Successful in 24m26s
CI / e2e (push) Successful in 10m0s
CI / test (18.x) (push) Successful in 27m14s
CI / test (20.x) (push) Failing after 1h19m9s
CI / test (24.x) (push) Successful in 37m49s
fix: v0.7.2 语句级原子性 + 事务 DDL 拒绝 + 约束/绑定硬化 — 6 项修复 + 43 回归 + CI 重型套件串行
- UPDATE 语句级部分提交(P1,四引擎):两阶段全量预检后执行,批内唯一互查,
  任何一行失败整句不执行(aria 场景 WAL 与内存不再错位)
- 事务内 ALTER/CREATE INDEX/DROP INDEX 残留(P1):Memory/KVStore 显式拒绝
  (对齐 Aria),createTable/dropTable 保持可回滚
- SET NULL 级联绕过 required 约束(P1):预检阶段整体拒绝 FOREIGN_KEY_VIOLATION
- bindParameters 注释误判(P2):行注释/块注释中的 ? 与引号不再参与绑定
- 未闭合字符串静默接受 → lexer 抛 PARSE_ERROR;未知 where 操作符抛 QUERY_ERROR
- UPDATE undefined 覆盖列值 → 语义化为不更新(null 仍置空)
- Hybrid 写穿透非原子(P1):磁盘失败自动重载内存对齐磁盘再抛原错误
- CI:Run tests 拆常规并行 + 重型串行(runInBand),重型测试超时余量提升,
  性能护栏 kv 120→240s / opfs 150→300s(仍拦截悬崖回归)
- 测试 1155 → 1198(74 套件),覆盖率 89.82% 保持
2026-08-13 15:31:16 +08:00

124 lines
4.7 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-fix(P0) — 二级索引大数据量完整性回归测试
*
* 覆盖两类根因:
* 1. LSM.put 的 `!this.flushing` 条件竞态(修复:每次 freeze 一律挂链)
* 2. SSTable rangeScan 尾块漏读(索引键为块尾 keylocateBlockLE 排除
* 尾 key 越界的下一块 → 范围扫描丢尾部数据;修复:endBlockIdx 多扫一块)
*/
import { AriaEngine } from '../../src/engine/aria/index';
import { createSchema } from '../../src/table/schema';
import { installOPFSMock } from '../helpers/opfs-mock';
let counter = 0;
function uniqueDB(): string {
return `idxrace-${Date.now()}-${++counter}-${Math.random().toString(36).slice(2, 6)}`;
}
beforeEach(() => { installOPFSMock(new Map()); });
describe('AriaEngine — 二级索引完整性(P0 回归)', () => {
it('5 万行写入:索引查询与主表一致(修复前丢 106~771 条)', async () => {
const engine = new AriaEngine({
storageBackend: 'opfs',
memtableSizeThreshold: 256 * 1024,
checkpointInterval: 2000,
walSyncMode: 'full',
});
await engine.open(uniqueDB(), 1);
await engine.createTable(createSchema('big', {
id: { type: 'string', primaryKey: true },
val: { type: 'number' },
tag: { type: 'string', index: true },
name: { type: 'string' },
}));
const TOTAL = 50000;
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}`, name: `User${idx}` });
}
await engine.insert('big', rows);
}
expect(await engine.count('big')).toBe(TOTAL);
// 每个 tag 的索引查询都完整(修复前丢 106-771 条)
for (let t = 0; t < 10; t++) {
const viaIdx = await engine.find('big', { table: 'big', where: { tag: `t${t}` } });
expect(viaIdx.length).toBe(5000);
}
// 崩溃恢复后索引仍完整
await (engine as any).backend.close();
(engine as any).opened = false;
const engine2 = new AriaEngine({
storageBackend: 'opfs',
memtableSizeThreshold: 256 * 1024,
checkpointInterval: 2000,
walSyncMode: 'full',
});
await engine2.open((engine as any).dbName, 1);
for (let t = 0; t < 10; t++) {
const viaIdx = await engine2.find('big', { table: 'big', where: { tag: `t${t}` } });
expect(viaIdx.length).toBe(5000);
}
await engine2.close();
}, 600000);
it('小批量高频写入(每批 50 行)触发极端 freeze 竞态', async () => {
const engine = new AriaEngine({
storageBackend: 'opfs',
memtableSizeThreshold: 32 * 1024,
checkpointInterval: 5000,
walSyncMode: 'full',
});
await engine.open(uniqueDB(), 1);
await engine.createTable(createSchema('items', {
id: { type: 'string', primaryKey: true },
tag: { type: 'string', index: true },
}));
const TOTAL = 4000;
for (let i = 0; i < TOTAL; i++) {
await engine.insert('items', [{ id: `k${i}`, tag: i % 3 === 0 ? 'a' : 'b' }]);
}
expect(await engine.count('items')).toBe(TOTAL);
expect(await engine.find('items', { table: 'items', where: { tag: 'a' } })).toHaveLength(Math.ceil(TOTAL / 3));
expect(await engine.find('items', { table: 'items', where: { tag: 'b' } })).toHaveLength(TOTAL - Math.ceil(TOTAL / 3));
await engine.close();
}, 600000);
it('多索引列同时写入:每列索引都完整', async () => {
const engine = new AriaEngine({
storageBackend: 'opfs',
memtableSizeThreshold: 64 * 1024,
checkpointInterval: 5000,
walSyncMode: 'full',
});
await engine.open(uniqueDB(), 1);
await engine.createTable(createSchema('multi', {
id: { type: 'string', primaryKey: true },
cat: { type: 'string', index: true },
grp: { type: 'number', index: true },
}));
const TOTAL = 6000;
for (let batch = 0; batch < TOTAL / 100; batch++) {
const rows = [] as Record<string, unknown>[];
for (let i = 0; i < 100; i++) {
const idx = batch * 100 + i;
rows.push({ id: `k${idx}`, cat: idx % 2 === 0 ? 'even' : 'odd', grp: idx % 5 });
}
await engine.insert('multi', rows);
}
expect(await engine.find('multi', { table: 'multi', where: { cat: 'even' } })).toHaveLength(TOTAL / 2);
expect(await engine.find('multi', { table: 'multi', where: { cat: 'odd' } })).toHaveLength(TOTAL / 2);
expect(await engine.find('multi', { table: 'multi', where: { grp: 0 } })).toHaveLength(TOTAL / 5);
expect(await engine.find('multi', { table: 'multi', where: { grp: 4 } })).toHaveLength(TOTAL / 5);
await engine.close();
}, 600000);
});