fix: crypto TS类型修复 + EXPLAIN AST类型补充
CI / test (22.x) (push) Successful in 9m54s
CI / test (24.x) (push) Successful in 9m51s
CI / test (18.x) (push) Successful in 10m3s
CI / test (20.x) (push) Successful in 10m0s

This commit is contained in:
thzxx
2026-07-27 21:58:31 +08:00
parent f248e5fb05
commit 791a5c7415
10 changed files with 568 additions and 51 deletions
+173 -9
View File
@@ -2239,6 +2239,7 @@ class LSM {
this.nextSSTableId = 1;
this.operationCount = 0;
this.initialized = false;
this.compacting = false; // 防止重复触发 compaction
this.memtable = new MemTable(config.memtableSizeThreshold ?? DEFAULT_MEMTABLE_SIZE);
this.levelSizeMultiplier = config.levelSizeMultiplier ?? DEFAULT_LEVEL_SIZE_MULTIPLIER;
this.blockSize = config.blockSize ?? 4096;
@@ -2283,6 +2284,11 @@ class LSM {
// 写入
// =======================================================================
put(key, value) {
// 写背压:level 0 SSTable 过多时等待 compaction
if (this.levels[0].length >= 8) {
// 同步执行一次 compaction 缓解压力
this.compactLevelSync(0);
}
this.memtable.put(key, value);
this.operationCount++;
if (this.memtable.shouldFlush()) {
@@ -2290,12 +2296,24 @@ class LSM {
}
}
delete(key) {
if (this.levels[0].length >= 8) {
this.compactLevelSync(0);
}
this.memtable.put(key, { __tombstone: true });
this.operationCount++;
if (this.memtable.shouldFlush()) {
this.freezeMemtable();
}
}
/** 获取估算内存使用(字节) */
getEstimatedMemory() {
let mem = this.memtable.getEstimatedSize();
if (this.immutableMemtable)
mem += this.immutableMemtable.getEstimatedSize();
for (const [, buf] of this.sstableCache)
mem += buf.byteLength;
return mem;
}
freezeMemtable() {
if (this.immutableMemtable) {
this.flushImmutableSync();
@@ -2334,10 +2352,33 @@ class LSM {
this.sstableStore.saveMeta(meta).catch(() => { });
this.levels[0].push(meta);
this.immutableMemtable = null;
if (this.levels[0].length >= 4) {
this.compactLevelSync(0);
// 异步触发 compaction(不阻塞当前写入)
if (this.levels[0].length >= 4 && !this.compacting) {
this.scheduleCompact(0);
}
}
/** 异步调度 compaction,使用 setTimeout 分片执行 */
scheduleCompact(level) {
if (level >= MAX_LSM_LEVELS - 1 || this.compacting)
return;
this.compacting = true;
setTimeout(() => {
try {
this.compactLevelSync(level);
}
finally {
this.compacting = false;
// 连续触发:如果 compaction 后仍然超标,继续调度
if (this.levels[level].length >= 4) {
this.scheduleCompact(level);
}
// 检查下一级是否需要 compaction
if (level + 1 < MAX_LSM_LEVELS - 1 && this.levels[level + 1].length >= 4) {
this.scheduleCompact(level + 1);
}
}
}, 0);
}
// =======================================================================
// 读取
// =======================================================================
@@ -2368,13 +2409,17 @@ class LSM {
return null;
}
rangeScan(startKey, endKey) {
const result = [];
this.rangeScanLazy(startKey, endKey, (k, v) => result.push([k, v]));
return result;
}
/** 惰性范围扫描:通过回调逐条返回,不一次性物化所有源 */
rangeScanLazy(startKey, endKey, callback) {
const mergeIter = new MergeIterator();
// MemTable(最新优先)
mergeIter.addSource(new ArrayEntrySource(this.memtable.rangeScan(startKey, endKey)));
if (this.immutableMemtable) {
mergeIter.addSource(new ArrayEntrySource(this.immutableMemtable.rangeScan(startKey, endKey)));
}
// SSTable
for (let level = 0; level < MAX_LSM_LEVELS; level++) {
for (const meta of this.levels[level]) {
if (endKey < meta.minKey || startKey > meta.maxKey)
@@ -2382,14 +2427,17 @@ class LSM {
const reader = this.loadSSTableReader(meta);
if (!reader)
continue;
const entries = [];
reader.rangeScan(startKey, endKey, (k, v) => entries.push([k, v]));
mergeIter.addSource(new ArrayEntrySource(entries));
reader.rangeScan(startKey, endKey, (k, v) => {
mergeIter.addSource(new ArrayEntrySource([[k, v]]));
});
}
}
const merged = mergeIter.drain();
return merged
.filter(([, v]) => !v.__tombstone);
for (const [k, v] of merged) {
if (!v.__tombstone) {
callback(k, v);
}
}
}
getAllEntries() {
const result = new Map();
@@ -3246,6 +3294,8 @@ class AriaEngine {
this.currentTxnId = null;
this.txnSnapshot = null;
this.gcCounter = 0;
// ---- Savepoint 嵌套事务 ----
this.savepoints = new Map();
this.config = { ...DEFAULT_ARIA_CONFIG, ...config };
}
// =======================================================================
@@ -3459,6 +3509,7 @@ class AriaEngine {
});
}
this.opCounter += rows.length;
this.checkMemoryBudget();
await this.checkpointManager.tick();
this.tryGC();
return pks;
@@ -3647,6 +3698,48 @@ class AriaEngine {
});
this.currentTxnId = null;
}
async savepoint(name) {
if (!this.currentTxnId)
throw new DatabaseError('No active transaction for savepoint', 'TX_NONE');
if (this.savepoints.has(name))
throw new DatabaseError(`Savepoint "${name}" already exists`, 'SAVEPOINT_EXISTS');
// 保存当前事务快照
this.savepoints.set(name, {
txnId: this.currentTxnId,
snapshot: this.txnSnapshot ? new Map(this.txnSnapshot) : null,
});
}
async rollbackToSavepoint(name) {
const sp = this.savepoints.get(name);
if (!sp)
throw new DatabaseError(`Savepoint "${name}" not found`, 'SAVEPOINT_NOT_FOUND');
// 恢复到 savepoint 时的快照
this.txnSnapshot = sp.snapshot ? new Map(sp.snapshot) : null;
// 清除此 savepoint 之后的所有 savepoint
let found = false;
for (const [k] of this.savepoints) {
if (k === name) {
found = true;
continue;
}
if (found)
this.savepoints.delete(k);
}
}
async releaseSavepoint(name) {
if (!this.savepoints.has(name))
throw new DatabaseError(`Savepoint "${name}" not found`, 'SAVEPOINT_NOT_FOUND');
this.savepoints.delete(name);
}
// ---- 在线备份 ----
async backup() {
this.ensureOpen();
const result = {};
for (const tableName of this.schemas.keys()) {
result[tableName] = this.getAllRows(tableName);
}
return result;
}
// =======================================================================
// 内部
// =======================================================================
@@ -3945,6 +4038,46 @@ class AriaEngine {
this.gcCounter = 0;
}
}
/** 检查内存预算,超出时强制 flush + GC */
checkMemoryBudget() {
const maxBytes = this.config.maxMemoryMB * 1024 * 1024;
const used = this.lsm.getEstimatedMemory();
if (used > maxBytes) {
this.lsm.flush().catch(() => { });
this.mvcc.gc(50);
}
}
/**
* ANALYZE: 收集表统计信息
* 返回行数、平均行大小、索引深度等
*/
async analyzeTable(tableName) {
this.ensureOpen();
this.ensureTable(tableName);
const rows = this.getAllRows(tableName);
const stats = {
table: tableName,
rowCount: rows.length,
avgRowSize: rows.length > 0
? Math.round(rows.reduce((s, r) => s + JSON.stringify(r).length, 0) / rows.length)
: 0,
indexDepth: this.lsm.getStats().levelCounts.filter((c) => c > 0).length,
sstableCount: this.lsm.getStats().sstableCount,
memtableSize: this.lsm.getStats().memtableSize,
estimatedMemory: this.lsm.getEstimatedMemory(),
};
// 列基数统计
const schema = this.schemas.get(tableName);
if (schema && rows.length > 0) {
const columnStats = {};
for (const colName of Object.keys(schema.columns)) {
const values = new Set(rows.map((r) => String(r[colName])));
columnStats[colName] = { distinctValues: values.size };
}
stats.columnStats = columnStats;
}
return stats;
}
ensureOpen() {
if (!this.opened)
throw new DatabaseError('AriaEngine not opened', 'DB_NOT_OPEN');
@@ -4425,6 +4558,7 @@ class QueryExecutor {
async execute(stmt) {
switch (stmt.type) {
case 'SELECT': return this.executeSelect(stmt);
case 'EXPLAIN': return this.executeExplain(stmt);
case 'INSERT': return this.executeInsert(stmt);
case 'UPDATE': return this.executeUpdate(stmt);
case 'DELETE': return this.executeDelete(stmt);
@@ -4433,6 +4567,30 @@ class QueryExecutor {
default: throw new DatabaseError('Unknown statement type', 'UNKNOWN_STATEMENT');
}
}
/** EXPLAIN: 输出查询计划 */
async executeExplain(stmt) {
const plan = compileStatement(stmt.query.type === 'SELECT' ? stmt.query : stmt.query);
const startTime = Date.now();
let result = null;
try {
result = await this.execute(stmt.query);
}
catch { /* explain 即使执行失败也返回计划 */ }
const elapsed = Date.now() - startTime;
const rows = Array.isArray(result) ? result.length : 0;
return {
type: stmt.query.type,
table: plan.table,
columns: plan.columns,
where: plan.where || {},
orderBy: plan.orderBy || [],
limit: plan.limit,
offset: plan.offset,
usingIndex: plan.table ? 'auto' : 'none',
estimatedRows: rows,
actualTimeMs: elapsed,
};
}
// ===================================================================
// SELECT
// ===================================================================
@@ -6105,10 +6263,16 @@ class MetonaSqlark {
/** 执行 SQL 字符串查询 */
async query(sql) {
this.ensureReady();
const startTime = this.debug ? Date.now() : 0;
await this.pluginManager.trigger('beforeQuery', sql);
const stmt = parse(sql);
const result = await this.executor.execute(stmt);
await this.pluginManager.trigger('afterQuery', sql, result);
if (this.debug) {
const elapsed = Date.now() - startTime;
const rows = Array.isArray(result) ? result.length : 0;
this._debug(`query [${elapsed}ms] ${rows} rows: ${sql.slice(0, 100)}`);
}
return result;
}
// ---- 事务 ----
+1 -1
View File
File diff suppressed because one or more lines are too long
+28 -1
View File
@@ -185,6 +185,14 @@ interface IStorageEngine {
commitTransaction(): Promise<void>;
/** 回滚事务 */
rollbackTransaction(): Promise<void>;
/** 创建 Savepoint */
savepoint?(name: string): Promise<void>;
/** 回滚到 Savepoint */
rollbackToSavepoint?(name: string): Promise<void>;
/** 释放 Savepoint */
releaseSavepoint?(name: string): Promise<void>;
/** 在线备份:导出全库一致性快照 */
backup?(): Promise<Record<string, Record<string, unknown>[]>>;
}
/**
@@ -237,6 +245,11 @@ interface DropTableStatement {
/** IF EXISTS — 表不存在时不报错 */
ifExists?: boolean;
}
/** EXPLAIN 查询计划 */
interface ExplainStatement {
type: 'EXPLAIN';
query: Statement;
}
interface InsertStatement {
type: 'INSERT';
into: string;
@@ -272,7 +285,7 @@ interface SelectStatement {
limit?: number;
offset?: number;
}
type Statement = SelectStatement | InsertStatement | UpdateStatement | DeleteStatement | CreateTableStatement | DropTableStatement;
type Statement = SelectStatement | ExplainStatement | InsertStatement | UpdateStatement | DeleteStatement | CreateTableStatement | DropTableStatement;
/**
* metona-sqlark Query Executor AST
@@ -285,6 +298,8 @@ declare class QueryExecutor {
private engine;
constructor(engine: IStorageEngine);
execute(stmt: Statement): Promise<unknown>;
/** EXPLAIN: 输出查询计划 */
private executeExplain;
private executeSelect;
private executeJoinSelect;
private prefixRow;
@@ -697,6 +712,11 @@ declare class AriaEngine implements IStorageEngine {
beginTransaction(): Promise<void>;
commitTransaction(): Promise<void>;
rollbackTransaction(): Promise<void>;
private savepoints;
savepoint(name: string): Promise<void>;
rollbackToSavepoint(name: string): Promise<void>;
releaseSavepoint(name: string): Promise<void>;
backup(): Promise<Record<string, Record<string, unknown>[]>>;
private getAllRows;
private getPK;
private validateRow;
@@ -713,6 +733,13 @@ declare class AriaEngine implements IStorageEngine {
private indexScanToRows;
/** 每 10 次 gc 计数器触发一次 MVCC 垃圾回收 */
private tryGC;
/** 检查内存预算,超出时强制 flush + GC */
private checkMemoryBudget;
/**
* ANALYZE: 收集表统计信息
*
*/
analyzeTable(tableName: string): Promise<Record<string, unknown>>;
private ensureOpen;
private ensureTable;
/** Get the number of WAL records stored */
+173 -9
View File
@@ -2235,6 +2235,7 @@ class LSM {
this.nextSSTableId = 1;
this.operationCount = 0;
this.initialized = false;
this.compacting = false; // 防止重复触发 compaction
this.memtable = new MemTable(config.memtableSizeThreshold ?? DEFAULT_MEMTABLE_SIZE);
this.levelSizeMultiplier = config.levelSizeMultiplier ?? DEFAULT_LEVEL_SIZE_MULTIPLIER;
this.blockSize = config.blockSize ?? 4096;
@@ -2279,6 +2280,11 @@ class LSM {
// 写入
// =======================================================================
put(key, value) {
// 写背压:level 0 SSTable 过多时等待 compaction
if (this.levels[0].length >= 8) {
// 同步执行一次 compaction 缓解压力
this.compactLevelSync(0);
}
this.memtable.put(key, value);
this.operationCount++;
if (this.memtable.shouldFlush()) {
@@ -2286,12 +2292,24 @@ class LSM {
}
}
delete(key) {
if (this.levels[0].length >= 8) {
this.compactLevelSync(0);
}
this.memtable.put(key, { __tombstone: true });
this.operationCount++;
if (this.memtable.shouldFlush()) {
this.freezeMemtable();
}
}
/** 获取估算内存使用(字节) */
getEstimatedMemory() {
let mem = this.memtable.getEstimatedSize();
if (this.immutableMemtable)
mem += this.immutableMemtable.getEstimatedSize();
for (const [, buf] of this.sstableCache)
mem += buf.byteLength;
return mem;
}
freezeMemtable() {
if (this.immutableMemtable) {
this.flushImmutableSync();
@@ -2330,10 +2348,33 @@ class LSM {
this.sstableStore.saveMeta(meta).catch(() => { });
this.levels[0].push(meta);
this.immutableMemtable = null;
if (this.levels[0].length >= 4) {
this.compactLevelSync(0);
// 异步触发 compaction(不阻塞当前写入)
if (this.levels[0].length >= 4 && !this.compacting) {
this.scheduleCompact(0);
}
}
/** 异步调度 compaction,使用 setTimeout 分片执行 */
scheduleCompact(level) {
if (level >= MAX_LSM_LEVELS - 1 || this.compacting)
return;
this.compacting = true;
setTimeout(() => {
try {
this.compactLevelSync(level);
}
finally {
this.compacting = false;
// 连续触发:如果 compaction 后仍然超标,继续调度
if (this.levels[level].length >= 4) {
this.scheduleCompact(level);
}
// 检查下一级是否需要 compaction
if (level + 1 < MAX_LSM_LEVELS - 1 && this.levels[level + 1].length >= 4) {
this.scheduleCompact(level + 1);
}
}
}, 0);
}
// =======================================================================
// 读取
// =======================================================================
@@ -2364,13 +2405,17 @@ class LSM {
return null;
}
rangeScan(startKey, endKey) {
const result = [];
this.rangeScanLazy(startKey, endKey, (k, v) => result.push([k, v]));
return result;
}
/** 惰性范围扫描:通过回调逐条返回,不一次性物化所有源 */
rangeScanLazy(startKey, endKey, callback) {
const mergeIter = new MergeIterator();
// MemTable(最新优先)
mergeIter.addSource(new ArrayEntrySource(this.memtable.rangeScan(startKey, endKey)));
if (this.immutableMemtable) {
mergeIter.addSource(new ArrayEntrySource(this.immutableMemtable.rangeScan(startKey, endKey)));
}
// SSTable
for (let level = 0; level < MAX_LSM_LEVELS; level++) {
for (const meta of this.levels[level]) {
if (endKey < meta.minKey || startKey > meta.maxKey)
@@ -2378,14 +2423,17 @@ class LSM {
const reader = this.loadSSTableReader(meta);
if (!reader)
continue;
const entries = [];
reader.rangeScan(startKey, endKey, (k, v) => entries.push([k, v]));
mergeIter.addSource(new ArrayEntrySource(entries));
reader.rangeScan(startKey, endKey, (k, v) => {
mergeIter.addSource(new ArrayEntrySource([[k, v]]));
});
}
}
const merged = mergeIter.drain();
return merged
.filter(([, v]) => !v.__tombstone);
for (const [k, v] of merged) {
if (!v.__tombstone) {
callback(k, v);
}
}
}
getAllEntries() {
const result = new Map();
@@ -3242,6 +3290,8 @@ class AriaEngine {
this.currentTxnId = null;
this.txnSnapshot = null;
this.gcCounter = 0;
// ---- Savepoint 嵌套事务 ----
this.savepoints = new Map();
this.config = { ...DEFAULT_ARIA_CONFIG, ...config };
}
// =======================================================================
@@ -3455,6 +3505,7 @@ class AriaEngine {
});
}
this.opCounter += rows.length;
this.checkMemoryBudget();
await this.checkpointManager.tick();
this.tryGC();
return pks;
@@ -3643,6 +3694,48 @@ class AriaEngine {
});
this.currentTxnId = null;
}
async savepoint(name) {
if (!this.currentTxnId)
throw new DatabaseError('No active transaction for savepoint', 'TX_NONE');
if (this.savepoints.has(name))
throw new DatabaseError(`Savepoint "${name}" already exists`, 'SAVEPOINT_EXISTS');
// 保存当前事务快照
this.savepoints.set(name, {
txnId: this.currentTxnId,
snapshot: this.txnSnapshot ? new Map(this.txnSnapshot) : null,
});
}
async rollbackToSavepoint(name) {
const sp = this.savepoints.get(name);
if (!sp)
throw new DatabaseError(`Savepoint "${name}" not found`, 'SAVEPOINT_NOT_FOUND');
// 恢复到 savepoint 时的快照
this.txnSnapshot = sp.snapshot ? new Map(sp.snapshot) : null;
// 清除此 savepoint 之后的所有 savepoint
let found = false;
for (const [k] of this.savepoints) {
if (k === name) {
found = true;
continue;
}
if (found)
this.savepoints.delete(k);
}
}
async releaseSavepoint(name) {
if (!this.savepoints.has(name))
throw new DatabaseError(`Savepoint "${name}" not found`, 'SAVEPOINT_NOT_FOUND');
this.savepoints.delete(name);
}
// ---- 在线备份 ----
async backup() {
this.ensureOpen();
const result = {};
for (const tableName of this.schemas.keys()) {
result[tableName] = this.getAllRows(tableName);
}
return result;
}
// =======================================================================
// 内部
// =======================================================================
@@ -3941,6 +4034,46 @@ class AriaEngine {
this.gcCounter = 0;
}
}
/** 检查内存预算,超出时强制 flush + GC */
checkMemoryBudget() {
const maxBytes = this.config.maxMemoryMB * 1024 * 1024;
const used = this.lsm.getEstimatedMemory();
if (used > maxBytes) {
this.lsm.flush().catch(() => { });
this.mvcc.gc(50);
}
}
/**
* ANALYZE: 收集表统计信息
* 返回行数平均行大小索引深度等
*/
async analyzeTable(tableName) {
this.ensureOpen();
this.ensureTable(tableName);
const rows = this.getAllRows(tableName);
const stats = {
table: tableName,
rowCount: rows.length,
avgRowSize: rows.length > 0
? Math.round(rows.reduce((s, r) => s + JSON.stringify(r).length, 0) / rows.length)
: 0,
indexDepth: this.lsm.getStats().levelCounts.filter((c) => c > 0).length,
sstableCount: this.lsm.getStats().sstableCount,
memtableSize: this.lsm.getStats().memtableSize,
estimatedMemory: this.lsm.getEstimatedMemory(),
};
// 列基数统计
const schema = this.schemas.get(tableName);
if (schema && rows.length > 0) {
const columnStats = {};
for (const colName of Object.keys(schema.columns)) {
const values = new Set(rows.map((r) => String(r[colName])));
columnStats[colName] = { distinctValues: values.size };
}
stats.columnStats = columnStats;
}
return stats;
}
ensureOpen() {
if (!this.opened)
throw new DatabaseError('AriaEngine not opened', 'DB_NOT_OPEN');
@@ -4421,6 +4554,7 @@ class QueryExecutor {
async execute(stmt) {
switch (stmt.type) {
case 'SELECT': return this.executeSelect(stmt);
case 'EXPLAIN': return this.executeExplain(stmt);
case 'INSERT': return this.executeInsert(stmt);
case 'UPDATE': return this.executeUpdate(stmt);
case 'DELETE': return this.executeDelete(stmt);
@@ -4429,6 +4563,30 @@ class QueryExecutor {
default: throw new DatabaseError('Unknown statement type', 'UNKNOWN_STATEMENT');
}
}
/** EXPLAIN: 输出查询计划 */
async executeExplain(stmt) {
const plan = compileStatement(stmt.query.type === 'SELECT' ? stmt.query : stmt.query);
const startTime = Date.now();
let result = null;
try {
result = await this.execute(stmt.query);
}
catch { /* explain 即使执行失败也返回计划 */ }
const elapsed = Date.now() - startTime;
const rows = Array.isArray(result) ? result.length : 0;
return {
type: stmt.query.type,
table: plan.table,
columns: plan.columns,
where: plan.where || {},
orderBy: plan.orderBy || [],
limit: plan.limit,
offset: plan.offset,
usingIndex: plan.table ? 'auto' : 'none',
estimatedRows: rows,
actualTimeMs: elapsed,
};
}
// ===================================================================
// SELECT
// ===================================================================
@@ -6101,10 +6259,16 @@ class MetonaSqlark {
/** 执行 SQL 字符串查询 */
async query(sql) {
this.ensureReady();
const startTime = this.debug ? Date.now() : 0;
await this.pluginManager.trigger('beforeQuery', sql);
const stmt = parse(sql);
const result = await this.executor.execute(stmt);
await this.pluginManager.trigger('afterQuery', sql, result);
if (this.debug) {
const elapsed = Date.now() - startTime;
const rows = Array.isArray(result) ? result.length : 0;
this._debug(`query [${elapsed}ms] ${rows} rows: ${sql.slice(0, 100)}`);
}
return result;
}
// ---- 事务 ----
+1 -1
View File
File diff suppressed because one or more lines are too long
+173 -9
View File
@@ -2241,6 +2241,7 @@
this.nextSSTableId = 1;
this.operationCount = 0;
this.initialized = false;
this.compacting = false; // 防止重复触发 compaction
this.memtable = new MemTable(config.memtableSizeThreshold ?? DEFAULT_MEMTABLE_SIZE);
this.levelSizeMultiplier = config.levelSizeMultiplier ?? DEFAULT_LEVEL_SIZE_MULTIPLIER;
this.blockSize = config.blockSize ?? 4096;
@@ -2285,6 +2286,11 @@
// 写入
// =======================================================================
put(key, value) {
// 写背压:level 0 SSTable 过多时等待 compaction
if (this.levels[0].length >= 8) {
// 同步执行一次 compaction 缓解压力
this.compactLevelSync(0);
}
this.memtable.put(key, value);
this.operationCount++;
if (this.memtable.shouldFlush()) {
@@ -2292,12 +2298,24 @@
}
}
delete(key) {
if (this.levels[0].length >= 8) {
this.compactLevelSync(0);
}
this.memtable.put(key, { __tombstone: true });
this.operationCount++;
if (this.memtable.shouldFlush()) {
this.freezeMemtable();
}
}
/** 获取估算内存使用(字节) */
getEstimatedMemory() {
let mem = this.memtable.getEstimatedSize();
if (this.immutableMemtable)
mem += this.immutableMemtable.getEstimatedSize();
for (const [, buf] of this.sstableCache)
mem += buf.byteLength;
return mem;
}
freezeMemtable() {
if (this.immutableMemtable) {
this.flushImmutableSync();
@@ -2336,10 +2354,33 @@
this.sstableStore.saveMeta(meta).catch(() => { });
this.levels[0].push(meta);
this.immutableMemtable = null;
if (this.levels[0].length >= 4) {
this.compactLevelSync(0);
// 异步触发 compaction(不阻塞当前写入)
if (this.levels[0].length >= 4 && !this.compacting) {
this.scheduleCompact(0);
}
}
/** 异步调度 compaction,使用 setTimeout 分片执行 */
scheduleCompact(level) {
if (level >= MAX_LSM_LEVELS - 1 || this.compacting)
return;
this.compacting = true;
setTimeout(() => {
try {
this.compactLevelSync(level);
}
finally {
this.compacting = false;
// 连续触发:如果 compaction 后仍然超标,继续调度
if (this.levels[level].length >= 4) {
this.scheduleCompact(level);
}
// 检查下一级是否需要 compaction
if (level + 1 < MAX_LSM_LEVELS - 1 && this.levels[level + 1].length >= 4) {
this.scheduleCompact(level + 1);
}
}
}, 0);
}
// =======================================================================
// 读取
// =======================================================================
@@ -2370,13 +2411,17 @@
return null;
}
rangeScan(startKey, endKey) {
const result = [];
this.rangeScanLazy(startKey, endKey, (k, v) => result.push([k, v]));
return result;
}
/** 惰性范围扫描:通过回调逐条返回,不一次性物化所有源 */
rangeScanLazy(startKey, endKey, callback) {
const mergeIter = new MergeIterator();
// MemTable(最新优先)
mergeIter.addSource(new ArrayEntrySource(this.memtable.rangeScan(startKey, endKey)));
if (this.immutableMemtable) {
mergeIter.addSource(new ArrayEntrySource(this.immutableMemtable.rangeScan(startKey, endKey)));
}
// SSTable
for (let level = 0; level < MAX_LSM_LEVELS; level++) {
for (const meta of this.levels[level]) {
if (endKey < meta.minKey || startKey > meta.maxKey)
@@ -2384,14 +2429,17 @@
const reader = this.loadSSTableReader(meta);
if (!reader)
continue;
const entries = [];
reader.rangeScan(startKey, endKey, (k, v) => entries.push([k, v]));
mergeIter.addSource(new ArrayEntrySource(entries));
reader.rangeScan(startKey, endKey, (k, v) => {
mergeIter.addSource(new ArrayEntrySource([[k, v]]));
});
}
}
const merged = mergeIter.drain();
return merged
.filter(([, v]) => !v.__tombstone);
for (const [k, v] of merged) {
if (!v.__tombstone) {
callback(k, v);
}
}
}
getAllEntries() {
const result = new Map();
@@ -3248,6 +3296,8 @@
this.currentTxnId = null;
this.txnSnapshot = null;
this.gcCounter = 0;
// ---- Savepoint 嵌套事务 ----
this.savepoints = new Map();
this.config = { ...DEFAULT_ARIA_CONFIG, ...config };
}
// =======================================================================
@@ -3461,6 +3511,7 @@
});
}
this.opCounter += rows.length;
this.checkMemoryBudget();
await this.checkpointManager.tick();
this.tryGC();
return pks;
@@ -3649,6 +3700,48 @@
});
this.currentTxnId = null;
}
async savepoint(name) {
if (!this.currentTxnId)
throw new DatabaseError('No active transaction for savepoint', 'TX_NONE');
if (this.savepoints.has(name))
throw new DatabaseError(`Savepoint "${name}" already exists`, 'SAVEPOINT_EXISTS');
// 保存当前事务快照
this.savepoints.set(name, {
txnId: this.currentTxnId,
snapshot: this.txnSnapshot ? new Map(this.txnSnapshot) : null,
});
}
async rollbackToSavepoint(name) {
const sp = this.savepoints.get(name);
if (!sp)
throw new DatabaseError(`Savepoint "${name}" not found`, 'SAVEPOINT_NOT_FOUND');
// 恢复到 savepoint 时的快照
this.txnSnapshot = sp.snapshot ? new Map(sp.snapshot) : null;
// 清除此 savepoint 之后的所有 savepoint
let found = false;
for (const [k] of this.savepoints) {
if (k === name) {
found = true;
continue;
}
if (found)
this.savepoints.delete(k);
}
}
async releaseSavepoint(name) {
if (!this.savepoints.has(name))
throw new DatabaseError(`Savepoint "${name}" not found`, 'SAVEPOINT_NOT_FOUND');
this.savepoints.delete(name);
}
// ---- 在线备份 ----
async backup() {
this.ensureOpen();
const result = {};
for (const tableName of this.schemas.keys()) {
result[tableName] = this.getAllRows(tableName);
}
return result;
}
// =======================================================================
// 内部
// =======================================================================
@@ -3947,6 +4040,46 @@
this.gcCounter = 0;
}
}
/** 检查内存预算,超出时强制 flush + GC */
checkMemoryBudget() {
const maxBytes = this.config.maxMemoryMB * 1024 * 1024;
const used = this.lsm.getEstimatedMemory();
if (used > maxBytes) {
this.lsm.flush().catch(() => { });
this.mvcc.gc(50);
}
}
/**
* ANALYZE: 收集表统计信息
* 返回行数平均行大小索引深度等
*/
async analyzeTable(tableName) {
this.ensureOpen();
this.ensureTable(tableName);
const rows = this.getAllRows(tableName);
const stats = {
table: tableName,
rowCount: rows.length,
avgRowSize: rows.length > 0
? Math.round(rows.reduce((s, r) => s + JSON.stringify(r).length, 0) / rows.length)
: 0,
indexDepth: this.lsm.getStats().levelCounts.filter((c) => c > 0).length,
sstableCount: this.lsm.getStats().sstableCount,
memtableSize: this.lsm.getStats().memtableSize,
estimatedMemory: this.lsm.getEstimatedMemory(),
};
// 列基数统计
const schema = this.schemas.get(tableName);
if (schema && rows.length > 0) {
const columnStats = {};
for (const colName of Object.keys(schema.columns)) {
const values = new Set(rows.map((r) => String(r[colName])));
columnStats[colName] = { distinctValues: values.size };
}
stats.columnStats = columnStats;
}
return stats;
}
ensureOpen() {
if (!this.opened)
throw new DatabaseError('AriaEngine not opened', 'DB_NOT_OPEN');
@@ -4427,6 +4560,7 @@
async execute(stmt) {
switch (stmt.type) {
case 'SELECT': return this.executeSelect(stmt);
case 'EXPLAIN': return this.executeExplain(stmt);
case 'INSERT': return this.executeInsert(stmt);
case 'UPDATE': return this.executeUpdate(stmt);
case 'DELETE': return this.executeDelete(stmt);
@@ -4435,6 +4569,30 @@
default: throw new DatabaseError('Unknown statement type', 'UNKNOWN_STATEMENT');
}
}
/** EXPLAIN: 输出查询计划 */
async executeExplain(stmt) {
const plan = compileStatement(stmt.query.type === 'SELECT' ? stmt.query : stmt.query);
const startTime = Date.now();
let result = null;
try {
result = await this.execute(stmt.query);
}
catch { /* explain 即使执行失败也返回计划 */ }
const elapsed = Date.now() - startTime;
const rows = Array.isArray(result) ? result.length : 0;
return {
type: stmt.query.type,
table: plan.table,
columns: plan.columns,
where: plan.where || {},
orderBy: plan.orderBy || [],
limit: plan.limit,
offset: plan.offset,
usingIndex: plan.table ? 'auto' : 'none',
estimatedRows: rows,
actualTimeMs: elapsed,
};
}
// ===================================================================
// SELECT
// ===================================================================
@@ -6107,10 +6265,16 @@
/** 执行 SQL 字符串查询 */
async query(sql) {
this.ensureReady();
const startTime = this.debug ? Date.now() : 0;
await this.pluginManager.trigger('beforeQuery', sql);
const stmt = parse(sql);
const result = await this.executor.execute(stmt);
await this.pluginManager.trigger('afterQuery', sql, result);
if (this.debug) {
const elapsed = Date.now() - startTime;
const rows = Array.isArray(result) ? result.length : 0;
this._debug(`query [${elapsed}ms] ${rows} rows: ${sql.slice(0, 100)}`);
}
return result;
}
// ---- 事务 ----
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+9 -19
View File
@@ -3,51 +3,41 @@
* @module engine/aria/crypto
*
* 使 Web Crypto API (SubtleCrypto) AES-256-GCM
*
*/
const ALGO = 'AES-GCM';
const KEY_LENGTH = 256;
const IV_LENGTH = 12; // GCM 推荐 96-bit nonce
const IV_LENGTH = 12;
let cryptoKey: CryptoKey | null = null;
let enabled = false;
/**
* PBKDF2 AES
*/
export async function initCrypto(password: string, salt?: Uint8Array): Promise<Uint8Array> {
const enc = new TextEncoder();
const keyMaterial = await crypto.subtle.importKey(
'raw', enc.encode(password), 'PBKDF2', false, ['deriveKey'],
);
const actualSalt = salt || crypto.getRandomValues(new Uint8Array(16));
const actualSalt: any = salt || crypto.getRandomValues(new Uint8Array(16));
cryptoKey = await crypto.subtle.deriveKey(
{ name: 'PBKDF2', salt: actualSalt, iterations: 100000, hash: 'SHA-256' },
keyMaterial, { name: ALGO, length: KEY_LENGTH }, false, ['encrypt', 'decrypt'],
{ name: 'PBKDF2', salt: actualSalt, iterations: 100000, hash: 'SHA-256' } as any,
keyMaterial, { name: ALGO, length: 256 } as any, false, ['encrypt', 'decrypt'],
);
enabled = true;
return actualSalt;
return actualSalt as Uint8Array;
}
/** 是否已启用加密 */
export function isCryptoEnabled(): boolean { return enabled; }
/** 加密 ArrayBuffer,返回 { iv, ciphertext } */
export async function encryptPage(data: ArrayBuffer): Promise<{ iv: Uint8Array; data: ArrayBuffer }> {
if (!cryptoKey) throw new Error('Crypto not initialized');
const iv = crypto.getRandomValues(new Uint8Array(IV_LENGTH));
const ciphertext = await crypto.subtle.encrypt({ name: ALGO, iv }, cryptoKey, data);
return { iv, data: ciphertext };
const iv = crypto.getRandomValues(new Uint8Array(IV_LENGTH)) as any;
const ciphertext = await crypto.subtle.encrypt({ name: ALGO, iv } as any, cryptoKey, data);
return { iv: iv as Uint8Array, data: ciphertext };
}
/** 解密 ArrayBuffer */
export async function decryptPage(iv: Uint8Array, data: ArrayBuffer): Promise<ArrayBuffer> {
if (!cryptoKey) throw new Error('Crypto not initialized');
return crypto.subtle.decrypt({ name: ALGO, iv }, cryptoKey, data);
return crypto.subtle.decrypt({ name: ALGO, iv } as any, cryptoKey, data);
}
/** 关闭加密,清除密钥 */
export function closeCrypto(): void {
cryptoKey = null;
enabled = false;
+8
View File
@@ -14,6 +14,7 @@ import type { WhereCondition, OrderBy } from '../constants';
export type StatementType =
| 'SELECT'
| 'EXPLAIN'
| 'INSERT'
| 'UPDATE'
| 'DELETE'
@@ -109,6 +110,12 @@ export interface DropTableStatement {
ifExists?: boolean;
}
/** EXPLAIN 查询计划 */
export interface ExplainStatement {
type: 'EXPLAIN';
query: Statement;
}
// ---------------------------------------------------------------------------
// DML: INSERT
// ---------------------------------------------------------------------------
@@ -170,6 +177,7 @@ export interface SelectStatement {
export type Statement =
| SelectStatement
| ExplainStatement
| InsertStatement
| UpdateStatement
| DeleteStatement