fix: dts TS类型修复
This commit is contained in:
Vendored
+219
-30
@@ -3076,6 +3076,114 @@ class OPFSBackend {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AriaEngine Page Header — 页面头部编解码
|
||||||
|
* @module engine/aria/page/header
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* 初始化新页面的 Header。
|
||||||
|
*/
|
||||||
|
function initPageHeader(buf, pageId, type) {
|
||||||
|
const view = new DataView(buf);
|
||||||
|
view.setUint32(0, pageId, false);
|
||||||
|
view.setUint8(4, type);
|
||||||
|
view.setUint16(5, PAGE_HEADER_SIZE, false); // freeStart = header 之后
|
||||||
|
view.setUint16(7, buf.byteLength, false); // freeEnd = 页面末尾
|
||||||
|
view.setUint16(9, 0, false); // slotCount = 0
|
||||||
|
view.setUint32(11, 0, false); // checksum = 0
|
||||||
|
view.setUint8(15, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AriaEngine File Manager — 页面文件管理 + PageIO 实现
|
||||||
|
* @module engine/aria/store/file_manager
|
||||||
|
*
|
||||||
|
* 负责管理页面文件的生命周期:分配/释放页面 ID,读写页面。
|
||||||
|
*/
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// FileManager (implements PageIO)
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
class FileManager {
|
||||||
|
constructor(backend) {
|
||||||
|
this.nextPageId = 0;
|
||||||
|
this.metaLoaded = false;
|
||||||
|
this.dbName = '';
|
||||||
|
this.backend = backend;
|
||||||
|
}
|
||||||
|
/** 初始化:从存储中读取元数据 */
|
||||||
|
async init(dbName) {
|
||||||
|
this.dbName = dbName;
|
||||||
|
const meta = await this.backend.read('__aria_meta');
|
||||||
|
if (meta && meta instanceof ArrayBuffer && meta.byteLength >= 4) {
|
||||||
|
const view = new DataView(meta);
|
||||||
|
this.nextPageId = view.getUint32(0, false);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.nextPageId = 1;
|
||||||
|
await this.saveMeta();
|
||||||
|
}
|
||||||
|
this.metaLoaded = true;
|
||||||
|
}
|
||||||
|
// ---- PageIO ----
|
||||||
|
async readPage(pageId) {
|
||||||
|
const key = `pg_${pageId}`;
|
||||||
|
const data = await this.backend.read(key);
|
||||||
|
if (!data) {
|
||||||
|
// 第一次访问:创建新页面
|
||||||
|
return this.createEmptyPage(pageId, PageType.DATA);
|
||||||
|
}
|
||||||
|
// 确保大小正确
|
||||||
|
if (data.byteLength < PAGE_SIZE) {
|
||||||
|
const padded = new ArrayBuffer(PAGE_SIZE);
|
||||||
|
new Uint8Array(padded).set(new Uint8Array(data));
|
||||||
|
return padded;
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
async writePage(pageId, data) {
|
||||||
|
const key = `pg_${pageId}`;
|
||||||
|
await this.backend.write(key, data);
|
||||||
|
}
|
||||||
|
async allocatePageId() {
|
||||||
|
const id = this.nextPageId++;
|
||||||
|
await this.saveMeta();
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
async freePageId(_pageId) {
|
||||||
|
// 简化实现:不回收 pageId
|
||||||
|
const key = `pg_${_pageId}`;
|
||||||
|
await this.backend.delete(key);
|
||||||
|
}
|
||||||
|
// ---- 表页面分配 ----
|
||||||
|
/**
|
||||||
|
* 分配一个新的表元数据页面。
|
||||||
|
*/
|
||||||
|
async allocateTableRootPage() {
|
||||||
|
const pageId = await this.allocatePageId();
|
||||||
|
const data = new ArrayBuffer(PAGE_SIZE);
|
||||||
|
initPageHeader(data, pageId, PageType.META);
|
||||||
|
await this.writePage(pageId, data);
|
||||||
|
return pageId;
|
||||||
|
}
|
||||||
|
// ---- 辅助 ----
|
||||||
|
async saveMeta() {
|
||||||
|
const buf = new ArrayBuffer(8);
|
||||||
|
new DataView(buf).setUint32(0, this.nextPageId, false);
|
||||||
|
await this.backend.write('__aria_meta', buf);
|
||||||
|
}
|
||||||
|
createEmptyPage(pageId, type) {
|
||||||
|
const buf = new ArrayBuffer(PAGE_SIZE);
|
||||||
|
initPageHeader(buf, pageId, type);
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
/** 清空所有数据 */
|
||||||
|
async clearAll() {
|
||||||
|
await this.backend.clear();
|
||||||
|
this.nextPageId = 1;
|
||||||
|
await this.saveMeta();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AriaEngine MVCC — 多版本并发控制
|
* AriaEngine MVCC — 多版本并发控制
|
||||||
* @module engine/aria/transaction/mvcc
|
* @module engine/aria/transaction/mvcc
|
||||||
@@ -3276,24 +3384,6 @@ class MVCCManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* AriaEngine Page Header — 页面头部编解码
|
|
||||||
* @module engine/aria/page/header
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* 初始化新页面的 Header。
|
|
||||||
*/
|
|
||||||
function initPageHeader(buf, pageId, type) {
|
|
||||||
const view = new DataView(buf);
|
|
||||||
view.setUint32(0, pageId, false);
|
|
||||||
view.setUint8(4, type);
|
|
||||||
view.setUint16(5, PAGE_HEADER_SIZE, false); // freeStart = header 之后
|
|
||||||
view.setUint16(7, buf.byteLength, false); // freeEnd = 页面末尾
|
|
||||||
view.setUint16(9, 0, false); // slotCount = 0
|
|
||||||
view.setUint32(11, 0, false); // checksum = 0
|
|
||||||
view.setUint8(15, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AriaEngine Page Format — 页面格式整合层
|
* AriaEngine Page Format — 页面格式整合层
|
||||||
* @module engine/aria/page/format
|
* @module engine/aria/page/format
|
||||||
@@ -3637,6 +3727,94 @@ class BufferPool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AriaEngine LZ4 Compression — 简化 LZ4 压缩/解压
|
||||||
|
* @module engine/aria/compression/lz4
|
||||||
|
*
|
||||||
|
* Token 格式(1 字节):
|
||||||
|
* hi 4bit = litLen (0-15)
|
||||||
|
* lo 4bit = matchField (0-15, 实际匹配 = field+4)
|
||||||
|
*
|
||||||
|
* 字面量-匹配序列: [token] [litLen bytes] [2B LE offset]
|
||||||
|
* 末尾纯字面量: [token with lo=0] [litLen bytes] ← 仅在流末尾出现
|
||||||
|
*/
|
||||||
|
const MIN_MATCH = 4;
|
||||||
|
function compressLZ4(input) {
|
||||||
|
if (input.byteLength < MIN_MATCH)
|
||||||
|
return input;
|
||||||
|
const maxOut = input.byteLength + (input.byteLength >> 8) + 32;
|
||||||
|
const out = new Uint8Array(maxOut);
|
||||||
|
let si = 0, di = 0;
|
||||||
|
let litStart = 0;
|
||||||
|
while (si < input.byteLength) {
|
||||||
|
// 搜索最长 backward match
|
||||||
|
let bestLen = 0, bestOff = 0;
|
||||||
|
const searchStart = Math.max(0, si - 65535);
|
||||||
|
for (let p = searchStart; p < si; p++) {
|
||||||
|
let ml = 0;
|
||||||
|
while (si + ml < input.byteLength && p + ml < si &&
|
||||||
|
input[p + ml] === input[si + ml] && ml < 255)
|
||||||
|
ml++;
|
||||||
|
if (ml >= MIN_MATCH && ml > bestLen) {
|
||||||
|
bestLen = ml;
|
||||||
|
bestOff = si - p;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (bestLen >= MIN_MATCH && (si - litStart) <= 15) {
|
||||||
|
// 有匹配 → 输出组合 token(字面量+匹配)
|
||||||
|
const litLen = si - litStart;
|
||||||
|
const matchField = Math.min(bestLen - MIN_MATCH, 15);
|
||||||
|
out[di++] = ((litLen & 0x0F) << 4) | (matchField & 0x0F);
|
||||||
|
for (let j = 0; j < litLen; j++)
|
||||||
|
out[di++] = input[litStart + j];
|
||||||
|
out[di++] = bestOff & 0xFF;
|
||||||
|
out[di++] = (bestOff >> 8) & 0xFF;
|
||||||
|
si += bestLen;
|
||||||
|
litStart = si;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// 无匹配或字面量已满 15 → 继续累积(不单独输出,等下个匹配合并)
|
||||||
|
si++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 输出末尾纯字面量(matchField=0,无 offset)
|
||||||
|
let remaining = si - litStart;
|
||||||
|
while (remaining > 0) {
|
||||||
|
const chunk = Math.min(remaining, 15);
|
||||||
|
out[di++] = (chunk & 0x0F) << 4; // lo=0 表示无匹配/无 offset
|
||||||
|
for (let j = 0; j < chunk; j++)
|
||||||
|
out[di++] = input[litStart + j];
|
||||||
|
remaining -= chunk;
|
||||||
|
litStart += chunk;
|
||||||
|
}
|
||||||
|
return di >= input.byteLength ? input : out.slice(0, di);
|
||||||
|
}
|
||||||
|
function decompressLZ4(input, originalSize) {
|
||||||
|
const out = new Uint8Array(originalSize);
|
||||||
|
let si = 0, di = 0;
|
||||||
|
while (si < input.byteLength && di < originalSize) {
|
||||||
|
const token = input[si++];
|
||||||
|
const litLen = (token >> 4) & 0x0F;
|
||||||
|
const matchField = token & 0x0F;
|
||||||
|
// 复制字面量
|
||||||
|
for (let i = 0; i < litLen && si < input.byteLength && di < originalSize; i++) {
|
||||||
|
out[di++] = input[si++];
|
||||||
|
}
|
||||||
|
if (di >= originalSize || si >= input.byteLength)
|
||||||
|
break;
|
||||||
|
// 非末尾 → 必有 offset + 匹配(即使 matchField==0 也复制 MIN_MATCH 字节)
|
||||||
|
if (si + 1 < input.byteLength) {
|
||||||
|
const offset = input[si++] | (input[si++] << 8);
|
||||||
|
const matchLen = matchField + MIN_MATCH;
|
||||||
|
for (let i = 0; i < matchLen && di < originalSize; i++) {
|
||||||
|
out[di] = out[di - offset];
|
||||||
|
di++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AriaEngine — 自研页面式存储引擎主类
|
* AriaEngine — 自研页面式存储引擎主类
|
||||||
* @module engine/aria/index
|
* @module engine/aria/index
|
||||||
@@ -3684,14 +3862,10 @@ class AriaEngine {
|
|||||||
this.backend = new MemoryBackend();
|
this.backend = new MemoryBackend();
|
||||||
}
|
}
|
||||||
await this.backend.open(dbName);
|
await this.backend.open(dbName);
|
||||||
// 2a. 初始化 Buffer Pool(页面缓存)
|
// 2a. FileManager (PageIO 实现) + Buffer Pool
|
||||||
const pageIO = {
|
const fileManager = new FileManager(this.backend);
|
||||||
readPage: async (pageId) => this.backend.read(`pg_${pageId}`),
|
await fileManager.init(dbName);
|
||||||
writePage: async (pageId, data) => this.backend.write(`pg_${pageId}`, data),
|
this.bufferPool = new BufferPool(fileManager, this.config.bufferPoolPages);
|
||||||
allocatePageId: async () => Date.now(),
|
|
||||||
freePageId: async () => { },
|
|
||||||
};
|
|
||||||
this.bufferPool = new BufferPool(pageIO, this.config.bufferPoolPages);
|
|
||||||
// 2. 构建 SSTableStore
|
// 2. 构建 SSTableStore
|
||||||
const sstableStore = this.createSSTableStore();
|
const sstableStore = this.createSSTableStore();
|
||||||
// 3. 初始化主 LSM(PK 索引)
|
// 3. 初始化主 LSM(PK 索引)
|
||||||
@@ -4030,7 +4204,7 @@ class AriaEngine {
|
|||||||
async beginTransaction() {
|
async beginTransaction() {
|
||||||
if (this.currentTxnId)
|
if (this.currentTxnId)
|
||||||
throw new DatabaseError('Transaction already in progress', 'TX_ACTIVE');
|
throw new DatabaseError('Transaction already in progress', 'TX_ACTIVE');
|
||||||
this.currentTxnId = Date.now();
|
this.currentTxnId = this.mvcc.beginTransaction();
|
||||||
this.txnSnapshot = new Map();
|
this.txnSnapshot = new Map();
|
||||||
this.wal.append({
|
this.wal.append({
|
||||||
type: WALRecordType.BEGIN,
|
type: WALRecordType.BEGIN,
|
||||||
@@ -4052,6 +4226,7 @@ class AriaEngine {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
this.mvcc.commitTransaction(this.currentTxnId);
|
||||||
this.wal.append({
|
this.wal.append({
|
||||||
type: WALRecordType.COMMIT,
|
type: WALRecordType.COMMIT,
|
||||||
txnId: this.currentTxnId,
|
txnId: this.currentTxnId,
|
||||||
@@ -4065,6 +4240,7 @@ class AriaEngine {
|
|||||||
async rollbackTransaction() {
|
async rollbackTransaction() {
|
||||||
if (!this.currentTxnId)
|
if (!this.currentTxnId)
|
||||||
throw new DatabaseError('No active transaction', 'TX_NONE');
|
throw new DatabaseError('No active transaction', 'TX_NONE');
|
||||||
|
this.mvcc.rollbackTransaction(this.currentTxnId);
|
||||||
this.txnSnapshot = null;
|
this.txnSnapshot = null;
|
||||||
this.wal.append({
|
this.wal.append({
|
||||||
type: WALRecordType.ROLLBACK,
|
type: WALRecordType.ROLLBACK,
|
||||||
@@ -4214,12 +4390,25 @@ class AriaEngine {
|
|||||||
const META_KEY = '__aria_lsm_meta';
|
const META_KEY = '__aria_lsm_meta';
|
||||||
return {
|
return {
|
||||||
save: async (id, data) => {
|
save: async (id, data) => {
|
||||||
const buf = data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);
|
let buf = data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);
|
||||||
|
// 压缩(若启用)
|
||||||
|
if (this.config.compression) {
|
||||||
|
const compressed = compressLZ4(new Uint8Array(buf));
|
||||||
|
buf = compressed.buffer.slice(compressed.byteOffset, compressed.byteOffset + compressed.byteLength);
|
||||||
|
}
|
||||||
await this.backend.write(`sst_${id}`, buf);
|
await this.backend.write(`sst_${id}`, buf);
|
||||||
},
|
},
|
||||||
load: async (id) => {
|
load: async (id) => {
|
||||||
const buf = await this.backend.read(`sst_${id}`);
|
const raw = await this.backend.read(`sst_${id}`);
|
||||||
return buf ? new Uint8Array(buf) : null;
|
if (!raw)
|
||||||
|
return null;
|
||||||
|
let buf = new Uint8Array(raw);
|
||||||
|
// 解压(若启用)
|
||||||
|
if (this.config.compression) {
|
||||||
|
const decompressed = decompressLZ4(buf, buf.length * 2); // 估计原始大小
|
||||||
|
buf = decompressed;
|
||||||
|
}
|
||||||
|
return buf;
|
||||||
},
|
},
|
||||||
delete: async (id) => {
|
delete: async (id) => {
|
||||||
await this.backend.delete(`sst_${id}`);
|
await this.backend.delete(`sst_${id}`);
|
||||||
|
|||||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+219
-30
@@ -3072,6 +3072,114 @@ class OPFSBackend {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AriaEngine Page Header — 页面头部编解码
|
||||||
|
* @module engine/aria/page/header
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* 初始化新页面的 Header。
|
||||||
|
*/
|
||||||
|
function initPageHeader(buf, pageId, type) {
|
||||||
|
const view = new DataView(buf);
|
||||||
|
view.setUint32(0, pageId, false);
|
||||||
|
view.setUint8(4, type);
|
||||||
|
view.setUint16(5, PAGE_HEADER_SIZE, false); // freeStart = header 之后
|
||||||
|
view.setUint16(7, buf.byteLength, false); // freeEnd = 页面末尾
|
||||||
|
view.setUint16(9, 0, false); // slotCount = 0
|
||||||
|
view.setUint32(11, 0, false); // checksum = 0
|
||||||
|
view.setUint8(15, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AriaEngine File Manager — 页面文件管理 + PageIO 实现
|
||||||
|
* @module engine/aria/store/file_manager
|
||||||
|
*
|
||||||
|
* 负责管理页面文件的生命周期:分配/释放页面 ID,读写页面。
|
||||||
|
*/
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// FileManager (implements PageIO)
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
class FileManager {
|
||||||
|
constructor(backend) {
|
||||||
|
this.nextPageId = 0;
|
||||||
|
this.metaLoaded = false;
|
||||||
|
this.dbName = '';
|
||||||
|
this.backend = backend;
|
||||||
|
}
|
||||||
|
/** 初始化:从存储中读取元数据 */
|
||||||
|
async init(dbName) {
|
||||||
|
this.dbName = dbName;
|
||||||
|
const meta = await this.backend.read('__aria_meta');
|
||||||
|
if (meta && meta instanceof ArrayBuffer && meta.byteLength >= 4) {
|
||||||
|
const view = new DataView(meta);
|
||||||
|
this.nextPageId = view.getUint32(0, false);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.nextPageId = 1;
|
||||||
|
await this.saveMeta();
|
||||||
|
}
|
||||||
|
this.metaLoaded = true;
|
||||||
|
}
|
||||||
|
// ---- PageIO ----
|
||||||
|
async readPage(pageId) {
|
||||||
|
const key = `pg_${pageId}`;
|
||||||
|
const data = await this.backend.read(key);
|
||||||
|
if (!data) {
|
||||||
|
// 第一次访问:创建新页面
|
||||||
|
return this.createEmptyPage(pageId, PageType.DATA);
|
||||||
|
}
|
||||||
|
// 确保大小正确
|
||||||
|
if (data.byteLength < PAGE_SIZE) {
|
||||||
|
const padded = new ArrayBuffer(PAGE_SIZE);
|
||||||
|
new Uint8Array(padded).set(new Uint8Array(data));
|
||||||
|
return padded;
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
async writePage(pageId, data) {
|
||||||
|
const key = `pg_${pageId}`;
|
||||||
|
await this.backend.write(key, data);
|
||||||
|
}
|
||||||
|
async allocatePageId() {
|
||||||
|
const id = this.nextPageId++;
|
||||||
|
await this.saveMeta();
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
async freePageId(_pageId) {
|
||||||
|
// 简化实现:不回收 pageId
|
||||||
|
const key = `pg_${_pageId}`;
|
||||||
|
await this.backend.delete(key);
|
||||||
|
}
|
||||||
|
// ---- 表页面分配 ----
|
||||||
|
/**
|
||||||
|
* 分配一个新的表元数据页面。
|
||||||
|
*/
|
||||||
|
async allocateTableRootPage() {
|
||||||
|
const pageId = await this.allocatePageId();
|
||||||
|
const data = new ArrayBuffer(PAGE_SIZE);
|
||||||
|
initPageHeader(data, pageId, PageType.META);
|
||||||
|
await this.writePage(pageId, data);
|
||||||
|
return pageId;
|
||||||
|
}
|
||||||
|
// ---- 辅助 ----
|
||||||
|
async saveMeta() {
|
||||||
|
const buf = new ArrayBuffer(8);
|
||||||
|
new DataView(buf).setUint32(0, this.nextPageId, false);
|
||||||
|
await this.backend.write('__aria_meta', buf);
|
||||||
|
}
|
||||||
|
createEmptyPage(pageId, type) {
|
||||||
|
const buf = new ArrayBuffer(PAGE_SIZE);
|
||||||
|
initPageHeader(buf, pageId, type);
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
/** 清空所有数据 */
|
||||||
|
async clearAll() {
|
||||||
|
await this.backend.clear();
|
||||||
|
this.nextPageId = 1;
|
||||||
|
await this.saveMeta();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AriaEngine MVCC — 多版本并发控制
|
* AriaEngine MVCC — 多版本并发控制
|
||||||
* @module engine/aria/transaction/mvcc
|
* @module engine/aria/transaction/mvcc
|
||||||
@@ -3272,24 +3380,6 @@ class MVCCManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* AriaEngine Page Header — 页面头部编解码
|
|
||||||
* @module engine/aria/page/header
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* 初始化新页面的 Header。
|
|
||||||
*/
|
|
||||||
function initPageHeader(buf, pageId, type) {
|
|
||||||
const view = new DataView(buf);
|
|
||||||
view.setUint32(0, pageId, false);
|
|
||||||
view.setUint8(4, type);
|
|
||||||
view.setUint16(5, PAGE_HEADER_SIZE, false); // freeStart = header 之后
|
|
||||||
view.setUint16(7, buf.byteLength, false); // freeEnd = 页面末尾
|
|
||||||
view.setUint16(9, 0, false); // slotCount = 0
|
|
||||||
view.setUint32(11, 0, false); // checksum = 0
|
|
||||||
view.setUint8(15, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AriaEngine Page Format — 页面格式整合层
|
* AriaEngine Page Format — 页面格式整合层
|
||||||
* @module engine/aria/page/format
|
* @module engine/aria/page/format
|
||||||
@@ -3633,6 +3723,94 @@ class BufferPool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AriaEngine LZ4 Compression — 简化 LZ4 压缩/解压
|
||||||
|
* @module engine/aria/compression/lz4
|
||||||
|
*
|
||||||
|
* Token 格式(1 字节):
|
||||||
|
* hi 4bit = litLen (0-15)
|
||||||
|
* lo 4bit = matchField (0-15, 实际匹配 = field+4)
|
||||||
|
*
|
||||||
|
* 字面量-匹配序列: [token] [litLen bytes] [2B LE offset]
|
||||||
|
* 末尾纯字面量: [token with lo=0] [litLen bytes] ← 仅在流末尾出现
|
||||||
|
*/
|
||||||
|
const MIN_MATCH = 4;
|
||||||
|
function compressLZ4(input) {
|
||||||
|
if (input.byteLength < MIN_MATCH)
|
||||||
|
return input;
|
||||||
|
const maxOut = input.byteLength + (input.byteLength >> 8) + 32;
|
||||||
|
const out = new Uint8Array(maxOut);
|
||||||
|
let si = 0, di = 0;
|
||||||
|
let litStart = 0;
|
||||||
|
while (si < input.byteLength) {
|
||||||
|
// 搜索最长 backward match
|
||||||
|
let bestLen = 0, bestOff = 0;
|
||||||
|
const searchStart = Math.max(0, si - 65535);
|
||||||
|
for (let p = searchStart; p < si; p++) {
|
||||||
|
let ml = 0;
|
||||||
|
while (si + ml < input.byteLength && p + ml < si &&
|
||||||
|
input[p + ml] === input[si + ml] && ml < 255)
|
||||||
|
ml++;
|
||||||
|
if (ml >= MIN_MATCH && ml > bestLen) {
|
||||||
|
bestLen = ml;
|
||||||
|
bestOff = si - p;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (bestLen >= MIN_MATCH && (si - litStart) <= 15) {
|
||||||
|
// 有匹配 → 输出组合 token(字面量+匹配)
|
||||||
|
const litLen = si - litStart;
|
||||||
|
const matchField = Math.min(bestLen - MIN_MATCH, 15);
|
||||||
|
out[di++] = ((litLen & 0x0F) << 4) | (matchField & 0x0F);
|
||||||
|
for (let j = 0; j < litLen; j++)
|
||||||
|
out[di++] = input[litStart + j];
|
||||||
|
out[di++] = bestOff & 0xFF;
|
||||||
|
out[di++] = (bestOff >> 8) & 0xFF;
|
||||||
|
si += bestLen;
|
||||||
|
litStart = si;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// 无匹配或字面量已满 15 → 继续累积(不单独输出,等下个匹配合并)
|
||||||
|
si++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 输出末尾纯字面量(matchField=0,无 offset)
|
||||||
|
let remaining = si - litStart;
|
||||||
|
while (remaining > 0) {
|
||||||
|
const chunk = Math.min(remaining, 15);
|
||||||
|
out[di++] = (chunk & 0x0F) << 4; // lo=0 表示无匹配/无 offset
|
||||||
|
for (let j = 0; j < chunk; j++)
|
||||||
|
out[di++] = input[litStart + j];
|
||||||
|
remaining -= chunk;
|
||||||
|
litStart += chunk;
|
||||||
|
}
|
||||||
|
return di >= input.byteLength ? input : out.slice(0, di);
|
||||||
|
}
|
||||||
|
function decompressLZ4(input, originalSize) {
|
||||||
|
const out = new Uint8Array(originalSize);
|
||||||
|
let si = 0, di = 0;
|
||||||
|
while (si < input.byteLength && di < originalSize) {
|
||||||
|
const token = input[si++];
|
||||||
|
const litLen = (token >> 4) & 0x0F;
|
||||||
|
const matchField = token & 0x0F;
|
||||||
|
// 复制字面量
|
||||||
|
for (let i = 0; i < litLen && si < input.byteLength && di < originalSize; i++) {
|
||||||
|
out[di++] = input[si++];
|
||||||
|
}
|
||||||
|
if (di >= originalSize || si >= input.byteLength)
|
||||||
|
break;
|
||||||
|
// 非末尾 → 必有 offset + 匹配(即使 matchField==0 也复制 MIN_MATCH 字节)
|
||||||
|
if (si + 1 < input.byteLength) {
|
||||||
|
const offset = input[si++] | (input[si++] << 8);
|
||||||
|
const matchLen = matchField + MIN_MATCH;
|
||||||
|
for (let i = 0; i < matchLen && di < originalSize; i++) {
|
||||||
|
out[di] = out[di - offset];
|
||||||
|
di++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AriaEngine — 自研页面式存储引擎主类
|
* AriaEngine — 自研页面式存储引擎主类
|
||||||
* @module engine/aria/index
|
* @module engine/aria/index
|
||||||
@@ -3680,14 +3858,10 @@ class AriaEngine {
|
|||||||
this.backend = new MemoryBackend();
|
this.backend = new MemoryBackend();
|
||||||
}
|
}
|
||||||
await this.backend.open(dbName);
|
await this.backend.open(dbName);
|
||||||
// 2a. 初始化 Buffer Pool(页面缓存)
|
// 2a. FileManager (PageIO 实现) + Buffer Pool
|
||||||
const pageIO = {
|
const fileManager = new FileManager(this.backend);
|
||||||
readPage: async (pageId) => this.backend.read(`pg_${pageId}`),
|
await fileManager.init(dbName);
|
||||||
writePage: async (pageId, data) => this.backend.write(`pg_${pageId}`, data),
|
this.bufferPool = new BufferPool(fileManager, this.config.bufferPoolPages);
|
||||||
allocatePageId: async () => Date.now(),
|
|
||||||
freePageId: async () => { },
|
|
||||||
};
|
|
||||||
this.bufferPool = new BufferPool(pageIO, this.config.bufferPoolPages);
|
|
||||||
// 2. 构建 SSTableStore
|
// 2. 构建 SSTableStore
|
||||||
const sstableStore = this.createSSTableStore();
|
const sstableStore = this.createSSTableStore();
|
||||||
// 3. 初始化主 LSM(PK 索引)
|
// 3. 初始化主 LSM(PK 索引)
|
||||||
@@ -4026,7 +4200,7 @@ class AriaEngine {
|
|||||||
async beginTransaction() {
|
async beginTransaction() {
|
||||||
if (this.currentTxnId)
|
if (this.currentTxnId)
|
||||||
throw new DatabaseError('Transaction already in progress', 'TX_ACTIVE');
|
throw new DatabaseError('Transaction already in progress', 'TX_ACTIVE');
|
||||||
this.currentTxnId = Date.now();
|
this.currentTxnId = this.mvcc.beginTransaction();
|
||||||
this.txnSnapshot = new Map();
|
this.txnSnapshot = new Map();
|
||||||
this.wal.append({
|
this.wal.append({
|
||||||
type: WALRecordType.BEGIN,
|
type: WALRecordType.BEGIN,
|
||||||
@@ -4048,6 +4222,7 @@ class AriaEngine {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
this.mvcc.commitTransaction(this.currentTxnId);
|
||||||
this.wal.append({
|
this.wal.append({
|
||||||
type: WALRecordType.COMMIT,
|
type: WALRecordType.COMMIT,
|
||||||
txnId: this.currentTxnId,
|
txnId: this.currentTxnId,
|
||||||
@@ -4061,6 +4236,7 @@ class AriaEngine {
|
|||||||
async rollbackTransaction() {
|
async rollbackTransaction() {
|
||||||
if (!this.currentTxnId)
|
if (!this.currentTxnId)
|
||||||
throw new DatabaseError('No active transaction', 'TX_NONE');
|
throw new DatabaseError('No active transaction', 'TX_NONE');
|
||||||
|
this.mvcc.rollbackTransaction(this.currentTxnId);
|
||||||
this.txnSnapshot = null;
|
this.txnSnapshot = null;
|
||||||
this.wal.append({
|
this.wal.append({
|
||||||
type: WALRecordType.ROLLBACK,
|
type: WALRecordType.ROLLBACK,
|
||||||
@@ -4210,12 +4386,25 @@ class AriaEngine {
|
|||||||
const META_KEY = '__aria_lsm_meta';
|
const META_KEY = '__aria_lsm_meta';
|
||||||
return {
|
return {
|
||||||
save: async (id, data) => {
|
save: async (id, data) => {
|
||||||
const buf = data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);
|
let buf = data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);
|
||||||
|
// 压缩(若启用)
|
||||||
|
if (this.config.compression) {
|
||||||
|
const compressed = compressLZ4(new Uint8Array(buf));
|
||||||
|
buf = compressed.buffer.slice(compressed.byteOffset, compressed.byteOffset + compressed.byteLength);
|
||||||
|
}
|
||||||
await this.backend.write(`sst_${id}`, buf);
|
await this.backend.write(`sst_${id}`, buf);
|
||||||
},
|
},
|
||||||
load: async (id) => {
|
load: async (id) => {
|
||||||
const buf = await this.backend.read(`sst_${id}`);
|
const raw = await this.backend.read(`sst_${id}`);
|
||||||
return buf ? new Uint8Array(buf) : null;
|
if (!raw)
|
||||||
|
return null;
|
||||||
|
let buf = new Uint8Array(raw);
|
||||||
|
// 解压(若启用)
|
||||||
|
if (this.config.compression) {
|
||||||
|
const decompressed = decompressLZ4(buf, buf.length * 2); // 估计原始大小
|
||||||
|
buf = decompressed;
|
||||||
|
}
|
||||||
|
return buf;
|
||||||
},
|
},
|
||||||
delete: async (id) => {
|
delete: async (id) => {
|
||||||
await this.backend.delete(`sst_${id}`);
|
await this.backend.delete(`sst_${id}`);
|
||||||
|
|||||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+219
-30
@@ -3078,6 +3078,114 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AriaEngine Page Header — 页面头部编解码
|
||||||
|
* @module engine/aria/page/header
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* 初始化新页面的 Header。
|
||||||
|
*/
|
||||||
|
function initPageHeader(buf, pageId, type) {
|
||||||
|
const view = new DataView(buf);
|
||||||
|
view.setUint32(0, pageId, false);
|
||||||
|
view.setUint8(4, type);
|
||||||
|
view.setUint16(5, PAGE_HEADER_SIZE, false); // freeStart = header 之后
|
||||||
|
view.setUint16(7, buf.byteLength, false); // freeEnd = 页面末尾
|
||||||
|
view.setUint16(9, 0, false); // slotCount = 0
|
||||||
|
view.setUint32(11, 0, false); // checksum = 0
|
||||||
|
view.setUint8(15, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AriaEngine File Manager — 页面文件管理 + PageIO 实现
|
||||||
|
* @module engine/aria/store/file_manager
|
||||||
|
*
|
||||||
|
* 负责管理页面文件的生命周期:分配/释放页面 ID,读写页面。
|
||||||
|
*/
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// FileManager (implements PageIO)
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
class FileManager {
|
||||||
|
constructor(backend) {
|
||||||
|
this.nextPageId = 0;
|
||||||
|
this.metaLoaded = false;
|
||||||
|
this.dbName = '';
|
||||||
|
this.backend = backend;
|
||||||
|
}
|
||||||
|
/** 初始化:从存储中读取元数据 */
|
||||||
|
async init(dbName) {
|
||||||
|
this.dbName = dbName;
|
||||||
|
const meta = await this.backend.read('__aria_meta');
|
||||||
|
if (meta && meta instanceof ArrayBuffer && meta.byteLength >= 4) {
|
||||||
|
const view = new DataView(meta);
|
||||||
|
this.nextPageId = view.getUint32(0, false);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.nextPageId = 1;
|
||||||
|
await this.saveMeta();
|
||||||
|
}
|
||||||
|
this.metaLoaded = true;
|
||||||
|
}
|
||||||
|
// ---- PageIO ----
|
||||||
|
async readPage(pageId) {
|
||||||
|
const key = `pg_${pageId}`;
|
||||||
|
const data = await this.backend.read(key);
|
||||||
|
if (!data) {
|
||||||
|
// 第一次访问:创建新页面
|
||||||
|
return this.createEmptyPage(pageId, PageType.DATA);
|
||||||
|
}
|
||||||
|
// 确保大小正确
|
||||||
|
if (data.byteLength < PAGE_SIZE) {
|
||||||
|
const padded = new ArrayBuffer(PAGE_SIZE);
|
||||||
|
new Uint8Array(padded).set(new Uint8Array(data));
|
||||||
|
return padded;
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
async writePage(pageId, data) {
|
||||||
|
const key = `pg_${pageId}`;
|
||||||
|
await this.backend.write(key, data);
|
||||||
|
}
|
||||||
|
async allocatePageId() {
|
||||||
|
const id = this.nextPageId++;
|
||||||
|
await this.saveMeta();
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
async freePageId(_pageId) {
|
||||||
|
// 简化实现:不回收 pageId
|
||||||
|
const key = `pg_${_pageId}`;
|
||||||
|
await this.backend.delete(key);
|
||||||
|
}
|
||||||
|
// ---- 表页面分配 ----
|
||||||
|
/**
|
||||||
|
* 分配一个新的表元数据页面。
|
||||||
|
*/
|
||||||
|
async allocateTableRootPage() {
|
||||||
|
const pageId = await this.allocatePageId();
|
||||||
|
const data = new ArrayBuffer(PAGE_SIZE);
|
||||||
|
initPageHeader(data, pageId, PageType.META);
|
||||||
|
await this.writePage(pageId, data);
|
||||||
|
return pageId;
|
||||||
|
}
|
||||||
|
// ---- 辅助 ----
|
||||||
|
async saveMeta() {
|
||||||
|
const buf = new ArrayBuffer(8);
|
||||||
|
new DataView(buf).setUint32(0, this.nextPageId, false);
|
||||||
|
await this.backend.write('__aria_meta', buf);
|
||||||
|
}
|
||||||
|
createEmptyPage(pageId, type) {
|
||||||
|
const buf = new ArrayBuffer(PAGE_SIZE);
|
||||||
|
initPageHeader(buf, pageId, type);
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
/** 清空所有数据 */
|
||||||
|
async clearAll() {
|
||||||
|
await this.backend.clear();
|
||||||
|
this.nextPageId = 1;
|
||||||
|
await this.saveMeta();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AriaEngine MVCC — 多版本并发控制
|
* AriaEngine MVCC — 多版本并发控制
|
||||||
* @module engine/aria/transaction/mvcc
|
* @module engine/aria/transaction/mvcc
|
||||||
@@ -3278,24 +3386,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* AriaEngine Page Header — 页面头部编解码
|
|
||||||
* @module engine/aria/page/header
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* 初始化新页面的 Header。
|
|
||||||
*/
|
|
||||||
function initPageHeader(buf, pageId, type) {
|
|
||||||
const view = new DataView(buf);
|
|
||||||
view.setUint32(0, pageId, false);
|
|
||||||
view.setUint8(4, type);
|
|
||||||
view.setUint16(5, PAGE_HEADER_SIZE, false); // freeStart = header 之后
|
|
||||||
view.setUint16(7, buf.byteLength, false); // freeEnd = 页面末尾
|
|
||||||
view.setUint16(9, 0, false); // slotCount = 0
|
|
||||||
view.setUint32(11, 0, false); // checksum = 0
|
|
||||||
view.setUint8(15, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AriaEngine Page Format — 页面格式整合层
|
* AriaEngine Page Format — 页面格式整合层
|
||||||
* @module engine/aria/page/format
|
* @module engine/aria/page/format
|
||||||
@@ -3639,6 +3729,94 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AriaEngine LZ4 Compression — 简化 LZ4 压缩/解压
|
||||||
|
* @module engine/aria/compression/lz4
|
||||||
|
*
|
||||||
|
* Token 格式(1 字节):
|
||||||
|
* hi 4bit = litLen (0-15)
|
||||||
|
* lo 4bit = matchField (0-15, 实际匹配 = field+4)
|
||||||
|
*
|
||||||
|
* 字面量-匹配序列: [token] [litLen bytes] [2B LE offset]
|
||||||
|
* 末尾纯字面量: [token with lo=0] [litLen bytes] ← 仅在流末尾出现
|
||||||
|
*/
|
||||||
|
const MIN_MATCH = 4;
|
||||||
|
function compressLZ4(input) {
|
||||||
|
if (input.byteLength < MIN_MATCH)
|
||||||
|
return input;
|
||||||
|
const maxOut = input.byteLength + (input.byteLength >> 8) + 32;
|
||||||
|
const out = new Uint8Array(maxOut);
|
||||||
|
let si = 0, di = 0;
|
||||||
|
let litStart = 0;
|
||||||
|
while (si < input.byteLength) {
|
||||||
|
// 搜索最长 backward match
|
||||||
|
let bestLen = 0, bestOff = 0;
|
||||||
|
const searchStart = Math.max(0, si - 65535);
|
||||||
|
for (let p = searchStart; p < si; p++) {
|
||||||
|
let ml = 0;
|
||||||
|
while (si + ml < input.byteLength && p + ml < si &&
|
||||||
|
input[p + ml] === input[si + ml] && ml < 255)
|
||||||
|
ml++;
|
||||||
|
if (ml >= MIN_MATCH && ml > bestLen) {
|
||||||
|
bestLen = ml;
|
||||||
|
bestOff = si - p;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (bestLen >= MIN_MATCH && (si - litStart) <= 15) {
|
||||||
|
// 有匹配 → 输出组合 token(字面量+匹配)
|
||||||
|
const litLen = si - litStart;
|
||||||
|
const matchField = Math.min(bestLen - MIN_MATCH, 15);
|
||||||
|
out[di++] = ((litLen & 0x0F) << 4) | (matchField & 0x0F);
|
||||||
|
for (let j = 0; j < litLen; j++)
|
||||||
|
out[di++] = input[litStart + j];
|
||||||
|
out[di++] = bestOff & 0xFF;
|
||||||
|
out[di++] = (bestOff >> 8) & 0xFF;
|
||||||
|
si += bestLen;
|
||||||
|
litStart = si;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// 无匹配或字面量已满 15 → 继续累积(不单独输出,等下个匹配合并)
|
||||||
|
si++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 输出末尾纯字面量(matchField=0,无 offset)
|
||||||
|
let remaining = si - litStart;
|
||||||
|
while (remaining > 0) {
|
||||||
|
const chunk = Math.min(remaining, 15);
|
||||||
|
out[di++] = (chunk & 0x0F) << 4; // lo=0 表示无匹配/无 offset
|
||||||
|
for (let j = 0; j < chunk; j++)
|
||||||
|
out[di++] = input[litStart + j];
|
||||||
|
remaining -= chunk;
|
||||||
|
litStart += chunk;
|
||||||
|
}
|
||||||
|
return di >= input.byteLength ? input : out.slice(0, di);
|
||||||
|
}
|
||||||
|
function decompressLZ4(input, originalSize) {
|
||||||
|
const out = new Uint8Array(originalSize);
|
||||||
|
let si = 0, di = 0;
|
||||||
|
while (si < input.byteLength && di < originalSize) {
|
||||||
|
const token = input[si++];
|
||||||
|
const litLen = (token >> 4) & 0x0F;
|
||||||
|
const matchField = token & 0x0F;
|
||||||
|
// 复制字面量
|
||||||
|
for (let i = 0; i < litLen && si < input.byteLength && di < originalSize; i++) {
|
||||||
|
out[di++] = input[si++];
|
||||||
|
}
|
||||||
|
if (di >= originalSize || si >= input.byteLength)
|
||||||
|
break;
|
||||||
|
// 非末尾 → 必有 offset + 匹配(即使 matchField==0 也复制 MIN_MATCH 字节)
|
||||||
|
if (si + 1 < input.byteLength) {
|
||||||
|
const offset = input[si++] | (input[si++] << 8);
|
||||||
|
const matchLen = matchField + MIN_MATCH;
|
||||||
|
for (let i = 0; i < matchLen && di < originalSize; i++) {
|
||||||
|
out[di] = out[di - offset];
|
||||||
|
di++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AriaEngine — 自研页面式存储引擎主类
|
* AriaEngine — 自研页面式存储引擎主类
|
||||||
* @module engine/aria/index
|
* @module engine/aria/index
|
||||||
@@ -3686,14 +3864,10 @@
|
|||||||
this.backend = new MemoryBackend();
|
this.backend = new MemoryBackend();
|
||||||
}
|
}
|
||||||
await this.backend.open(dbName);
|
await this.backend.open(dbName);
|
||||||
// 2a. 初始化 Buffer Pool(页面缓存)
|
// 2a. FileManager (PageIO 实现) + Buffer Pool
|
||||||
const pageIO = {
|
const fileManager = new FileManager(this.backend);
|
||||||
readPage: async (pageId) => this.backend.read(`pg_${pageId}`),
|
await fileManager.init(dbName);
|
||||||
writePage: async (pageId, data) => this.backend.write(`pg_${pageId}`, data),
|
this.bufferPool = new BufferPool(fileManager, this.config.bufferPoolPages);
|
||||||
allocatePageId: async () => Date.now(),
|
|
||||||
freePageId: async () => { },
|
|
||||||
};
|
|
||||||
this.bufferPool = new BufferPool(pageIO, this.config.bufferPoolPages);
|
|
||||||
// 2. 构建 SSTableStore
|
// 2. 构建 SSTableStore
|
||||||
const sstableStore = this.createSSTableStore();
|
const sstableStore = this.createSSTableStore();
|
||||||
// 3. 初始化主 LSM(PK 索引)
|
// 3. 初始化主 LSM(PK 索引)
|
||||||
@@ -4032,7 +4206,7 @@
|
|||||||
async beginTransaction() {
|
async beginTransaction() {
|
||||||
if (this.currentTxnId)
|
if (this.currentTxnId)
|
||||||
throw new DatabaseError('Transaction already in progress', 'TX_ACTIVE');
|
throw new DatabaseError('Transaction already in progress', 'TX_ACTIVE');
|
||||||
this.currentTxnId = Date.now();
|
this.currentTxnId = this.mvcc.beginTransaction();
|
||||||
this.txnSnapshot = new Map();
|
this.txnSnapshot = new Map();
|
||||||
this.wal.append({
|
this.wal.append({
|
||||||
type: WALRecordType.BEGIN,
|
type: WALRecordType.BEGIN,
|
||||||
@@ -4054,6 +4228,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
this.mvcc.commitTransaction(this.currentTxnId);
|
||||||
this.wal.append({
|
this.wal.append({
|
||||||
type: WALRecordType.COMMIT,
|
type: WALRecordType.COMMIT,
|
||||||
txnId: this.currentTxnId,
|
txnId: this.currentTxnId,
|
||||||
@@ -4067,6 +4242,7 @@
|
|||||||
async rollbackTransaction() {
|
async rollbackTransaction() {
|
||||||
if (!this.currentTxnId)
|
if (!this.currentTxnId)
|
||||||
throw new DatabaseError('No active transaction', 'TX_NONE');
|
throw new DatabaseError('No active transaction', 'TX_NONE');
|
||||||
|
this.mvcc.rollbackTransaction(this.currentTxnId);
|
||||||
this.txnSnapshot = null;
|
this.txnSnapshot = null;
|
||||||
this.wal.append({
|
this.wal.append({
|
||||||
type: WALRecordType.ROLLBACK,
|
type: WALRecordType.ROLLBACK,
|
||||||
@@ -4216,12 +4392,25 @@
|
|||||||
const META_KEY = '__aria_lsm_meta';
|
const META_KEY = '__aria_lsm_meta';
|
||||||
return {
|
return {
|
||||||
save: async (id, data) => {
|
save: async (id, data) => {
|
||||||
const buf = data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);
|
let buf = data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);
|
||||||
|
// 压缩(若启用)
|
||||||
|
if (this.config.compression) {
|
||||||
|
const compressed = compressLZ4(new Uint8Array(buf));
|
||||||
|
buf = compressed.buffer.slice(compressed.byteOffset, compressed.byteOffset + compressed.byteLength);
|
||||||
|
}
|
||||||
await this.backend.write(`sst_${id}`, buf);
|
await this.backend.write(`sst_${id}`, buf);
|
||||||
},
|
},
|
||||||
load: async (id) => {
|
load: async (id) => {
|
||||||
const buf = await this.backend.read(`sst_${id}`);
|
const raw = await this.backend.read(`sst_${id}`);
|
||||||
return buf ? new Uint8Array(buf) : null;
|
if (!raw)
|
||||||
|
return null;
|
||||||
|
let buf = new Uint8Array(raw);
|
||||||
|
// 解压(若启用)
|
||||||
|
if (this.config.compression) {
|
||||||
|
const decompressed = decompressLZ4(buf, buf.length * 2); // 估计原始大小
|
||||||
|
buf = decompressed;
|
||||||
|
}
|
||||||
|
return buf;
|
||||||
},
|
},
|
||||||
delete: async (id) => {
|
delete: async (id) => {
|
||||||
await this.backend.delete(`sst_${id}`);
|
await this.backend.delete(`sst_${id}`);
|
||||||
|
|||||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
+46
-13
@@ -20,9 +20,12 @@ import { WALRecordType, type WALRecord } from './types';
|
|||||||
import { CheckpointManager } from './wal/checkpoint';
|
import { CheckpointManager } from './wal/checkpoint';
|
||||||
import { IndexedDBBackend, MemoryBackend, type IStorageBackend } from './store/backend';
|
import { IndexedDBBackend, MemoryBackend, type IStorageBackend } from './store/backend';
|
||||||
import { OPFSBackend } from './store/opfs_backend';
|
import { OPFSBackend } from './store/opfs_backend';
|
||||||
|
import { FileManager } from './store/file_manager';
|
||||||
import { MVCCManager } from './transaction/mvcc';
|
import { MVCCManager } from './transaction/mvcc';
|
||||||
import { BloomFilter } from './index/bloom';
|
import { BloomFilter } from './index/bloom';
|
||||||
import { BufferPool, type PageIO } from './buffer/pool';
|
import { BufferPool } from './buffer/pool';
|
||||||
|
import { compressLZ4, decompressLZ4 } from './compression/lz4';
|
||||||
|
import { isCryptoEnabled, encryptPage, decryptPage } from './crypto';
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// AriaEngine
|
// AriaEngine
|
||||||
@@ -78,14 +81,10 @@ export class AriaEngine implements IStorageEngine {
|
|||||||
}
|
}
|
||||||
await this.backend.open(dbName);
|
await this.backend.open(dbName);
|
||||||
|
|
||||||
// 2a. 初始化 Buffer Pool(页面缓存)
|
// 2a. FileManager (PageIO 实现) + Buffer Pool
|
||||||
const pageIO: PageIO = {
|
const fileManager = new FileManager(this.backend);
|
||||||
readPage: async (pageId) => this.backend.read(`pg_${pageId}`),
|
await fileManager.init(dbName);
|
||||||
writePage: async (pageId, data) => this.backend.write(`pg_${pageId}`, data),
|
this.bufferPool = new BufferPool(fileManager, this.config.bufferPoolPages);
|
||||||
allocatePageId: async () => Date.now(),
|
|
||||||
freePageId: async () => {},
|
|
||||||
};
|
|
||||||
this.bufferPool = new BufferPool(pageIO, this.config.bufferPoolPages);
|
|
||||||
|
|
||||||
// 2. 构建 SSTableStore
|
// 2. 构建 SSTableStore
|
||||||
const sstableStore = this.createSSTableStore();
|
const sstableStore = this.createSSTableStore();
|
||||||
@@ -482,7 +481,7 @@ export class AriaEngine implements IStorageEngine {
|
|||||||
|
|
||||||
async beginTransaction(): Promise<void> {
|
async beginTransaction(): Promise<void> {
|
||||||
if (this.currentTxnId) throw new DatabaseError('Transaction already in progress', 'TX_ACTIVE');
|
if (this.currentTxnId) throw new DatabaseError('Transaction already in progress', 'TX_ACTIVE');
|
||||||
this.currentTxnId = Date.now();
|
this.currentTxnId = this.mvcc.beginTransaction();
|
||||||
this.txnSnapshot = new Map();
|
this.txnSnapshot = new Map();
|
||||||
|
|
||||||
this.wal.append({
|
this.wal.append({
|
||||||
@@ -506,6 +505,8 @@ export class AriaEngine implements IStorageEngine {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.mvcc.commitTransaction(this.currentTxnId);
|
||||||
|
|
||||||
this.wal.append({
|
this.wal.append({
|
||||||
type: WALRecordType.COMMIT,
|
type: WALRecordType.COMMIT,
|
||||||
txnId: this.currentTxnId,
|
txnId: this.currentTxnId,
|
||||||
@@ -521,6 +522,7 @@ export class AriaEngine implements IStorageEngine {
|
|||||||
async rollbackTransaction(): Promise<void> {
|
async rollbackTransaction(): Promise<void> {
|
||||||
if (!this.currentTxnId) throw new DatabaseError('No active transaction', 'TX_NONE');
|
if (!this.currentTxnId) throw new DatabaseError('No active transaction', 'TX_NONE');
|
||||||
|
|
||||||
|
this.mvcc.rollbackTransaction(this.currentTxnId);
|
||||||
this.txnSnapshot = null;
|
this.txnSnapshot = null;
|
||||||
|
|
||||||
this.wal.append({
|
this.wal.append({
|
||||||
@@ -666,12 +668,43 @@ export class AriaEngine implements IStorageEngine {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
save: async (id, data) => {
|
save: async (id, data) => {
|
||||||
const buf = data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength) as ArrayBuffer;
|
let buf: ArrayBuffer = data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength) as ArrayBuffer;
|
||||||
|
// 压缩(若启用)
|
||||||
|
if (this.config.compression) {
|
||||||
|
const compressed = compressLZ4(new Uint8Array(buf));
|
||||||
|
buf = compressed.buffer.slice(compressed.byteOffset, compressed.byteOffset + compressed.byteLength) as ArrayBuffer;
|
||||||
|
}
|
||||||
|
// 加密(若启用)
|
||||||
|
if (isCryptoEnabled()) {
|
||||||
|
const enc = await encryptPage(buf);
|
||||||
|
const header = new Uint8Array(12 + 4); // IV(12) + originalLen(4)
|
||||||
|
header.set(enc.iv, 0);
|
||||||
|
new DataView(header.buffer).setUint32(12, data.byteLength, false);
|
||||||
|
const combined = new Uint8Array(header.length + enc.data.byteLength);
|
||||||
|
combined.set(header, 0);
|
||||||
|
combined.set(new Uint8Array(enc.data), header.length);
|
||||||
|
buf = combined.buffer;
|
||||||
|
}
|
||||||
await this.backend.write(`sst_${id}`, buf);
|
await this.backend.write(`sst_${id}`, buf);
|
||||||
},
|
},
|
||||||
load: async (id) => {
|
load: async (id) => {
|
||||||
const buf = await this.backend.read(`sst_${id}`);
|
const raw = await this.backend.read(`sst_${id}`);
|
||||||
return buf ? new Uint8Array(buf) : null;
|
if (!raw) return null;
|
||||||
|
let buf = new Uint8Array(raw);
|
||||||
|
// 解密(若数据带加密头)
|
||||||
|
if (isCryptoEnabled() && buf.length > 16) {
|
||||||
|
const iv = buf.slice(0, 12);
|
||||||
|
const origLen = new DataView(buf.buffer, buf.byteOffset + 12, 4).getUint32(0, false);
|
||||||
|
const ciphertext = buf.slice(16).buffer;
|
||||||
|
const decrypted = await decryptPage(iv, ciphertext);
|
||||||
|
buf = new Uint8Array(decrypted, 0, origLen);
|
||||||
|
}
|
||||||
|
// 解压(若启用)
|
||||||
|
if (this.config.compression) {
|
||||||
|
const decompressed = decompressLZ4(buf, buf.length * 2); // 估计原始大小
|
||||||
|
buf = decompressed as any;
|
||||||
|
}
|
||||||
|
return buf;
|
||||||
},
|
},
|
||||||
delete: async (id) => {
|
delete: async (id) => {
|
||||||
await this.backend.delete(`sst_${id}`);
|
await this.backend.delete(`sst_${id}`);
|
||||||
|
|||||||
@@ -27,9 +27,8 @@ export class FileManager implements PageIO {
|
|||||||
/** 初始化:从存储中读取元数据 */
|
/** 初始化:从存储中读取元数据 */
|
||||||
async init(dbName: string): Promise<void> {
|
async init(dbName: string): Promise<void> {
|
||||||
this.dbName = dbName;
|
this.dbName = dbName;
|
||||||
// 读取 nextPageId
|
|
||||||
const meta = await this.backend.read('__aria_meta');
|
const meta = await this.backend.read('__aria_meta');
|
||||||
if (meta) {
|
if (meta && meta instanceof ArrayBuffer && meta.byteLength >= 4) {
|
||||||
const view = new DataView(meta);
|
const view = new DataView(meta);
|
||||||
this.nextPageId = view.getUint32(0, false);
|
this.nextPageId = view.getUint32(0, false);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
Reference in New Issue
Block a user