- 新增 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)
168 lines
4.5 KiB
TypeScript
168 lines
4.5 KiB
TypeScript
/**
|
|
* AriaEngine Page Format — 页面格式整合层
|
|
* @module engine/aria/page/format
|
|
*
|
|
* 组合 Header / Slot / Tuple 操作,提供统一的页面管理接口。
|
|
*/
|
|
|
|
import { PAGE_SIZE, PageType, type PageHandle } from '../types';
|
|
import {
|
|
initPageHeader,
|
|
decodePageHeader,
|
|
encodePageHeader,
|
|
getSlotCount,
|
|
getPageId,
|
|
} from './header';
|
|
import { allocateSlot, freeSlot, readSlotData, getAllSlots } from './slot';
|
|
import { encodeTuple, decodeTuple } from './tuple';
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 页面创建
|
|
// ---------------------------------------------------------------------------
|
|
|
|
/** 创建一个新的空页面 */
|
|
export function createPage(pageId: number, type: PageType): PageHandle {
|
|
const data = new ArrayBuffer(PAGE_SIZE);
|
|
initPageHeader(data, pageId, type);
|
|
return {
|
|
pageId,
|
|
type,
|
|
data,
|
|
dirty: true,
|
|
pins: 0,
|
|
prev: null,
|
|
next: null,
|
|
lastAccess: Date.now(),
|
|
};
|
|
}
|
|
|
|
/** 从 ArrayBuffer 恢复页面句柄 */
|
|
export function pageFromBuffer(
|
|
pageId: number,
|
|
buffer: ArrayBuffer,
|
|
): PageHandle {
|
|
return {
|
|
pageId,
|
|
type: new DataView(buffer).getUint8(4) as PageType,
|
|
data: buffer,
|
|
dirty: false,
|
|
pins: 0,
|
|
prev: null,
|
|
next: null,
|
|
lastAccess: Date.now(),
|
|
};
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 行操作(页面级)
|
|
// ---------------------------------------------------------------------------
|
|
|
|
/**
|
|
* 插入一行到页面,返回 slot 索引,空间不足返回 -1。
|
|
*/
|
|
export function pageInsertRow(
|
|
page: PageHandle,
|
|
row: Record<string, unknown>,
|
|
columnOrder: string[],
|
|
columnTypes: Record<string, string>,
|
|
): number {
|
|
const encoded = encodeTuple(row, columnOrder, columnTypes);
|
|
const slotIdx = allocateSlot(page.data, encoded);
|
|
if (slotIdx >= 0) {
|
|
page.dirty = true;
|
|
page.lastAccess = Date.now();
|
|
}
|
|
return slotIdx;
|
|
}
|
|
|
|
/**
|
|
* 读取指定 slot 的行数据并解码。
|
|
*/
|
|
export function pageReadRow(
|
|
page: PageHandle,
|
|
slotIndex: number,
|
|
columnOrder: string[],
|
|
columnTypes: Record<string, string>,
|
|
): Record<string, unknown> | null {
|
|
const slotData = readSlotData(page.data, slotIndex);
|
|
if (!slotData) return null;
|
|
return decodeTuple(slotData, columnOrder, columnTypes);
|
|
}
|
|
|
|
/**
|
|
* 读取页面中的所有行。
|
|
*/
|
|
export function pageReadAllRows(
|
|
page: PageHandle,
|
|
columnOrder: string[],
|
|
columnTypes: Record<string, string>,
|
|
): Record<string, unknown>[] {
|
|
const rows: Record<string, unknown>[] = [];
|
|
const slotCount = getSlotCount(page.data);
|
|
for (let i = 0; i < slotCount; i++) {
|
|
const row = pageReadRow(page, i, columnOrder, columnTypes);
|
|
if (row) rows.push(row);
|
|
}
|
|
return rows;
|
|
}
|
|
|
|
/**
|
|
* 标记 slot 为已删除。
|
|
*/
|
|
export function pageDeleteRow(page: PageHandle, slotIndex: number): void {
|
|
freeSlot(page.data, slotIndex);
|
|
page.dirty = true;
|
|
page.lastAccess = Date.now();
|
|
}
|
|
|
|
/**
|
|
* 重写指定 slot 的行数据。
|
|
*/
|
|
export function pageUpdateRow(
|
|
page: PageHandle,
|
|
slotIndex: number,
|
|
row: Record<string, unknown>,
|
|
columnOrder: string[],
|
|
columnTypes: Record<string, string>,
|
|
): void {
|
|
// 先标记旧 slot 为删除
|
|
pageDeleteRow(page, slotIndex);
|
|
// 分配新 slot,可能会在不同位置
|
|
const newSlot = pageInsertRow(page, row, columnOrder, columnTypes);
|
|
// 注意:调用者需要自行维护 slot index → pk 的映射
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 校验和
|
|
// ---------------------------------------------------------------------------
|
|
|
|
/** 简单 CRC32(使用预先计算的查找表简化) */
|
|
export function computeChecksum(data: ArrayBuffer): number {
|
|
const view = new Uint8Array(data);
|
|
let hash = 0;
|
|
for (let i = 0; i < view.byteLength; i++) {
|
|
hash = ((hash << 5) - hash + view[i]) | 0;
|
|
}
|
|
return hash >>> 0;
|
|
}
|
|
|
|
/** 更新页面的校验和字段 */
|
|
export function updateChecksum(page: PageHandle): void {
|
|
// 先清零校验和字段
|
|
const view = new DataView(page.data);
|
|
view.setUint32(11, 0, false);
|
|
// 计算校验和
|
|
const cksum = computeChecksum(page.data);
|
|
view.setUint32(11, cksum, false);
|
|
}
|
|
|
|
/** 验证页面校验和 */
|
|
export function verifyChecksum(page: PageHandle): boolean {
|
|
const stored = new DataView(page.data).getUint32(11, false);
|
|
// 临时清零
|
|
new DataView(page.data).setUint32(11, 0, false);
|
|
const computed = computeChecksum(page.data);
|
|
new DataView(page.data).setUint32(11, stored, false);
|
|
return stored === computed;
|
|
}
|