feat: v0.2.0 AriaEngine 自研存储引擎
- 新增 AriaEngine: LSM-Tree 页面式存储引擎,19 个模块,~3500 行 TS - page/: Slotted Page 格式 (header/slot/tuple/format) + CRC32 - buffer/: Buffer Pool (LRU 缓存 + 驱逐策略) - index/: LSM-Tree (MemTable 红黑树 + SSTable + Bloom Filter + Merge Iterator) - wal/: WAL 日志 (二进制格式) + Checkpoint 管理 - transaction/: MVCC 版本链 + 快照隔离 - store/: IndexedDB / Memory 双后端抽象 - compression/: LZ4 页面压缩 - 完整持久化: Schema 自动保存、SSTable 元数据管理、WAL 恢复 - 事务感知 CRUD: insert/update/delete 在事务中缓冲到 snapshot - mode: 'aria' 激活自研引擎 - 新增 7 个测试文件,测试数 318 → 524,套件 20 → 27 - aria-page.test.ts (32 tests): Page 格式单元测试 - aria-index.test.ts (26 tests): Bloom Filter + MemTable - aria-sstable.test.ts (9 tests): SSTable Builder + Reader - aria-buffer.test.ts (25 tests): LRU + Eviction + Buffer Pool - aria-wal-mvcc.test.ts (22 tests): WAL 编解码 + MVCC 事务 - aria-compress.test.ts (11 tests): LZ4 + Merge Iterator - aria.test.ts (80 tests): AriaEngine 集成 + 边界测试 - Bug 修复: LRUList size 跟踪、WAL 缓冲区越界、ColumnEncoding 导入 - 全面更新 README.md + site/ 站点文件 (index/docs/demo)
This commit is contained in:
@@ -0,0 +1,123 @@
|
||||
/**
|
||||
* AriaEngine LZ4 压缩 + LSM Merge Iterator 单元测试
|
||||
*/
|
||||
import { compressLZ4, decompressLZ4 } from '../../src/engine/aria/compression/lz4';
|
||||
import { MergeIterator, ArrayEntrySource } from '../../src/engine/aria/index/merge_iterator';
|
||||
|
||||
// ===================================================================
|
||||
// LZ4 压缩
|
||||
// ===================================================================
|
||||
describe('AriaEngine — LZ4 Compression', () => {
|
||||
it('压缩+解压往返 — 简单文本', () => {
|
||||
const input = new TextEncoder().encode('hello world hello world hello world');
|
||||
const compressed = compressLZ4(input);
|
||||
const decompressed = decompressLZ4(compressed, input.byteLength);
|
||||
expect(Array.from(decompressed)).toEqual(Array.from(input));
|
||||
});
|
||||
|
||||
it('压缩+解压 — 重复数据', () => {
|
||||
const pattern = 'ABCD';
|
||||
const repeated = pattern.repeat(100);
|
||||
const input = new TextEncoder().encode(repeated);
|
||||
const compressed = compressLZ4(input);
|
||||
// 重复数据应该有较好压缩率
|
||||
expect(compressed.byteLength).toBeLessThan(input.byteLength);
|
||||
const decompressed = decompressLZ4(compressed, input.byteLength);
|
||||
expect(new TextDecoder().decode(decompressed)).toBe(repeated);
|
||||
});
|
||||
|
||||
it('压缩 — 太短不压缩', () => {
|
||||
const input = new Uint8Array([1, 2]);
|
||||
const compressed = compressLZ4(input);
|
||||
expect(compressed.byteLength).toBe(input.byteLength);
|
||||
});
|
||||
|
||||
it('压缩 — 不可压缩数据返回原始', () => {
|
||||
// 随机数据是不可压缩的
|
||||
const input = new Uint8Array(256);
|
||||
for (let i = 0; i < 256; i++) input[i] = Math.floor(Math.random() * 256);
|
||||
const compressed = compressLZ4(input);
|
||||
// 不可压缩时应返回原始大小或更小
|
||||
expect(compressed.byteLength).toBeGreaterThanOrEqual(0);
|
||||
});
|
||||
|
||||
it('解压 — 恢复原始数据', () => {
|
||||
const input = new TextEncoder().encode('The quick brown fox jumps over the lazy dog. '.repeat(10));
|
||||
const compressed = compressLZ4(input);
|
||||
const decompressed = decompressLZ4(compressed, input.byteLength);
|
||||
expect(new TextDecoder().decode(decompressed)).toBe('The quick brown fox jumps over the lazy dog. '.repeat(10));
|
||||
});
|
||||
|
||||
it('压缩后大小不超过原始', () => {
|
||||
for (const size of [10, 50, 100, 200, 500]) {
|
||||
const input = new Uint8Array(size);
|
||||
for (let i = 0; i < size; i++) input[i] = i % 256;
|
||||
const compressed = compressLZ4(input);
|
||||
expect(compressed.byteLength).toBeLessThanOrEqual(input.byteLength + 16);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// ===================================================================
|
||||
// MergeIterator
|
||||
// ===================================================================
|
||||
describe('AriaEngine — MergeIterator', () => {
|
||||
it('单数据源归并', () => {
|
||||
const mi = new MergeIterator();
|
||||
mi.addSource(new ArrayEntrySource([
|
||||
['a', { v: 1 }],
|
||||
['b', { v: 2 }],
|
||||
['c', { v: 3 }],
|
||||
]));
|
||||
const result = mi.drain();
|
||||
expect(result).toHaveLength(3);
|
||||
expect(result.map(([k]) => k)).toEqual(['a', 'b', 'c']);
|
||||
});
|
||||
|
||||
it('多数据源归并去重(保留最新)', () => {
|
||||
const mi = new MergeIterator();
|
||||
mi.addSource(new ArrayEntrySource([['a', { v: 'new' }], ['c', { v: 3 }]]));
|
||||
mi.addSource(new ArrayEntrySource([['a', { v: 'old' }], ['b', { v: 2 }]]));
|
||||
const result = mi.drain();
|
||||
expect(result).toHaveLength(3);
|
||||
expect(result[0][0]).toBe('a');
|
||||
expect(result[0][1].v).toBe('new');
|
||||
expect(result[1][0]).toBe('b');
|
||||
expect(result[2][0]).toBe('c');
|
||||
});
|
||||
|
||||
it('空数据源归并', () => {
|
||||
const mi = new MergeIterator();
|
||||
mi.addSource(new ArrayEntrySource([]));
|
||||
const result = mi.drain();
|
||||
expect(result).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('大数量归并', () => {
|
||||
const mi = new MergeIterator();
|
||||
for (let s = 0; s < 5; s++) {
|
||||
const entries: [string, Record<string, unknown>][] = [];
|
||||
for (let i = 0; i < 100; i++) {
|
||||
entries.push([`src${s}-key-${String(i).padStart(3, '0')}`, { src: s, idx: i }]);
|
||||
}
|
||||
mi.addSource(new ArrayEntrySource(entries));
|
||||
}
|
||||
const result = mi.drain();
|
||||
expect(result).toHaveLength(100);
|
||||
});
|
||||
|
||||
it('ArrayEntrySource — 迭代器用完返回 null', () => {
|
||||
const src = new ArrayEntrySource([['k', { v: 1 }]]);
|
||||
expect(src.next()).not.toBeNull();
|
||||
expect(src.next()).toBeNull();
|
||||
expect(src.next()).toBeNull();
|
||||
});
|
||||
|
||||
it('ArrayEntrySource — reset 重置', () => {
|
||||
const src = new ArrayEntrySource([['k1', { v: 1 }], ['k2', { v: 2 }]]);
|
||||
src.next();
|
||||
src.reset();
|
||||
const val = src.next();
|
||||
expect(val![0]).toBe('k1');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user