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
@@ -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;
}
// ---- 事务 ----