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,138 @@
|
||||
/**
|
||||
* AriaEngine Slot Directory — 页面内行槽位管理
|
||||
* @module engine/aria/page/slot
|
||||
*
|
||||
* Slot 目录从页面 Header 之后向下增长,每条记录 4 字节。
|
||||
*/
|
||||
|
||||
import {
|
||||
PAGE_HEADER_SIZE,
|
||||
SLOT_ENTRY_SIZE,
|
||||
PAGE_SIZE,
|
||||
type SlotEntry,
|
||||
} from '../types';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Slot 读写
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* 读取 slot 号对应的 SlotEntry。
|
||||
*/
|
||||
export function getSlotEntry(buf: ArrayBuffer, slotIndex: number): SlotEntry {
|
||||
const offset = PAGE_HEADER_SIZE + slotIndex * SLOT_ENTRY_SIZE;
|
||||
const view = new DataView(buf);
|
||||
return {
|
||||
offset: view.getUint16(offset, false),
|
||||
length: view.getUint16(offset + 2, false),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入 slot 号对应的 SlotEntry。
|
||||
*/
|
||||
export function setSlotEntry(
|
||||
buf: ArrayBuffer,
|
||||
slotIndex: number,
|
||||
entry: SlotEntry,
|
||||
): void {
|
||||
const offset = PAGE_HEADER_SIZE + slotIndex * SLOT_ENTRY_SIZE;
|
||||
const view = new DataView(buf);
|
||||
view.setUint16(offset, entry.offset, false);
|
||||
view.setUint16(offset + 2, entry.length, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取页面中所有 slot 条目。
|
||||
*/
|
||||
export function getAllSlots(
|
||||
buf: ArrayBuffer,
|
||||
slotCount: number,
|
||||
): SlotEntry[] {
|
||||
const entries: SlotEntry[] = [];
|
||||
for (let i = 0; i < slotCount; i++) {
|
||||
entries.push(getSlotEntry(buf, i));
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Slot 空间计算
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/** 获取 slot 目录占用的总字节数 */
|
||||
export function getSlotDirectorySize(slotCount: number): number {
|
||||
return slotCount * SLOT_ENTRY_SIZE;
|
||||
}
|
||||
|
||||
/** 获取可用空闲空间(字节) */
|
||||
export function getFreeSpace(buf: ArrayBuffer): number {
|
||||
const view = new DataView(buf);
|
||||
const freeStart = view.getUint16(5, false); // slot 区之后
|
||||
const freeEnd = view.getUint16(7, false); // 数据区之前
|
||||
return freeEnd - freeStart;
|
||||
}
|
||||
|
||||
/** 检查是否有足够空间存放长度为 len 的行 */
|
||||
export function hasEnoughSpace(buf: ArrayBuffer, len: number): boolean {
|
||||
const slotCount = new DataView(buf).getUint16(9, false);
|
||||
const neededSlotSize = (slotCount + 1) * SLOT_ENTRY_SIZE;
|
||||
const freeStart = PAGE_HEADER_SIZE + neededSlotSize;
|
||||
const freeEnd = new DataView(buf).getUint16(7, false);
|
||||
return freeEnd - freeStart >= len;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 插入 / 删除 slot
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* 在页面中分配一个 slot 并写入行数据。
|
||||
* 返回分配的 slot 索引,失败返回 -1。
|
||||
*/
|
||||
export function allocateSlot(
|
||||
buf: ArrayBuffer,
|
||||
rowData: Uint8Array,
|
||||
): number {
|
||||
const view = new DataView(buf);
|
||||
const slotCount = view.getUint16(9, false);
|
||||
const neededSlotSpace = (slotCount + 1) * SLOT_ENTRY_SIZE;
|
||||
const freeStart = PAGE_HEADER_SIZE + neededSlotSpace;
|
||||
const freeEnd = view.getUint16(7, false);
|
||||
|
||||
if (freeEnd - freeStart < rowData.byteLength) {
|
||||
return -1; // 空间不足
|
||||
}
|
||||
|
||||
// 将数据放入页面底部
|
||||
const dataOffset = freeEnd - rowData.byteLength;
|
||||
const dest = new Uint8Array(buf, dataOffset, rowData.byteLength);
|
||||
dest.set(rowData);
|
||||
|
||||
// 写入 slot 条目
|
||||
setSlotEntry(buf, slotCount, { offset: dataOffset, length: rowData.byteLength });
|
||||
|
||||
// 更新 header
|
||||
view.setUint16(5, freeStart, false); // freeStart
|
||||
view.setUint16(7, dataOffset, false); // freeEnd
|
||||
view.setUint16(9, slotCount + 1, false); // slotCount
|
||||
|
||||
return slotCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从页面中删除指定 slot 的数据(标记为无效,offset 置 0)。
|
||||
* 注:简化实现,不做 slot 压缩。
|
||||
*/
|
||||
export function freeSlot(buf: ArrayBuffer, slotIndex: number): void {
|
||||
setSlotEntry(buf, slotIndex, { offset: 0, length: 0 });
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取指定 slot 的行数据。
|
||||
*/
|
||||
export function readSlotData(buf: ArrayBuffer, slotIndex: number): Uint8Array | null {
|
||||
const entry = getSlotEntry(buf, slotIndex);
|
||||
if (entry.offset === 0 || entry.length === 0) return null;
|
||||
return new Uint8Array(buf, entry.offset, entry.length);
|
||||
}
|
||||
Reference in New Issue
Block a user