Files
MetonaSqlark/dist/metona-sqlark.cjs.js
T
thzxx 29f8fd52a1
CI / test (18.x) (push) Successful in 10m0s
CI / test (20.x) (push) Successful in 10m2s
CI / test (22.x) (push) Successful in 9m58s
CI / test (24.x) (push) Successful in 9m54s
fix: ALTER TABLE DROP COLUMN only modifies schema, demo page deep-copies query results to prevent reference sharing
2026-07-29 22:28:47 +08:00

7545 lines
268 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
/**
* metona-sqlark Constants — 类型定义 / 默认配置 / 枚举
* @module constants
*/
/** 所有存储模式 */
/** 所有字段类型 */
const FIELD_TYPES = ['string', 'number', 'boolean', 'date', 'json'];
/** 数据库默认配置 */
const DB_DEFAULTS = Object.freeze({
name: 'metona-sqlark',
mode: 'hybrid',
diskEngine: 'indexeddb',
version: 1,
maxRowsPerQuery: 0, // 0 = 不限制
debug: false,
});
// ---------------------------------------------------------------------------
// 错误类型
// ---------------------------------------------------------------------------
/** 数据库错误 */
class DatabaseError extends Error {
constructor(message, code, details) {
super(message);
this.code = code;
this.details = details;
this.name = 'DatabaseError';
}
}
// ---------------------------------------------------------------------------
// 版本
// ---------------------------------------------------------------------------
const VERSION = '0.2.5';
/**
* metona-sqlark Shared WHERE Matcher — 统一的条件匹配逻辑
* @module query/where-matcher
*
* MemoryEngine / IndexedDBEngine / QueryExecutor 共享此模块,
* 消除 220+ 行重复代码,统一 $and/$or/$not/$col 行为。
*/
// ---------------------------------------------------------------------------
// LIKE 正则缓存
// ---------------------------------------------------------------------------
const likeCache = new Map();
function compileLikeRegex(pattern) {
const cached = likeCache.get(pattern);
if (cached)
return cached;
const escaped = pattern
.replace(/[.+^${}()|[\]\\]/g, '\\$&')
.replace(/%/g, '.*')
.replace(/_/g, '.');
const regex = new RegExp(`^${escaped}$`, 'i');
likeCache.set(pattern, regex);
return regex;
}
// ---------------------------------------------------------------------------
// WHERE 匹配(顶层入口)
// ---------------------------------------------------------------------------
/**
* 匹配完整 WHERE 条件
* @param row 当前数据行
* @param where WHERE 条件对象
* @param options.$col 是否启用 $col 列引用解析
*/
function matchWhere(row, where, options = {}) {
for (const [field, condition] of Object.entries(where)) {
// 顶层 $and
if (field === '$and') {
const subs = condition;
if (!subs.every((sub) => matchWhere(row, sub, options)))
return false;
continue;
}
// 顶层 $or
if (field === '$or') {
const subs = condition;
if (!subs.some((sub) => matchWhere(row, sub, options)))
return false;
continue;
}
if (!matchField(row[field], condition, row, options))
return false;
}
return true;
}
// ---------------------------------------------------------------------------
// 字段匹配
// ---------------------------------------------------------------------------
function matchField(value, condition, row, options) {
// 嵌套 $and
if (typeof condition === 'object' && condition !== null && '$and' in condition) {
const subs = condition.$and;
return subs.every((sub) => matchWhere(row, sub, options));
}
// 嵌套 $or
if (typeof condition === 'object' && condition !== null && '$or' in condition) {
const subs = condition.$or;
return subs.some((sub) => matchWhere(row, sub, options));
}
// $not
if (typeof condition === 'object' && condition !== null && '$not' in condition) {
return !matchField(value, condition.$not, row, options);
}
// 简单值 => $eq
if (typeof condition !== 'object' || condition === null || Array.isArray(condition)) {
return value === condition;
}
const ops = condition;
// $col 简写: { $col: name } === { $eq: { $col: name } }(仅 JOIN ON 场景)
if (options.$col && '$col' in ops && Object.keys(ops).length === 1) {
return value === row[ops.$col];
}
// 遍历操作符
for (const [op, operand] of Object.entries(ops)) {
let actualOperand = operand;
// $col 列引用解析
if (options.$col && typeof operand === 'object' && operand !== null && '$col' in operand) {
actualOperand = row[operand.$col];
}
if (!matchOperator(value, op, actualOperand))
return false;
}
return true;
}
// ---------------------------------------------------------------------------
// 操作符匹配
// ---------------------------------------------------------------------------
function matchOperator(value, op, operand) {
switch (op) {
case '$eq': return value === operand;
case '$ne': return value !== operand;
case '$gt': return value > operand;
case '$gte': return value >= operand;
case '$lt': return value < operand;
case '$lte': return value <= operand;
case '$in': return Array.isArray(operand) && operand.includes(value);
case '$nin': return Array.isArray(operand) && !operand.includes(value);
case '$like': return compileLikeRegex(String(operand)).test(String(value));
default: return true;
}
}
// ---------------------------------------------------------------------------
// 排序
// ---------------------------------------------------------------------------
function applyOrderBy(rows, orderBy) {
return [...rows].sort((a, b) => {
for (const { column, direction } of orderBy) {
const cmp = compare(a[column], b[column]);
if (cmp !== 0)
return direction === 'desc' ? -cmp : cmp;
}
return 0;
});
}
function compare(a, b) {
if (a === b)
return 0;
if (a === null || a === undefined)
return 1;
if (b === null || b === undefined)
return -1;
if (typeof a === 'string' && typeof b === 'string')
return a.localeCompare(b);
if (typeof a === 'number' && typeof b === 'number')
return a - b;
return String(a).localeCompare(String(b));
}
// ---------------------------------------------------------------------------
// 列投影
// ---------------------------------------------------------------------------
function projectColumns(row, columns) {
const projected = {};
for (const col of columns) {
if (col in row) {
projected[col] = row[col];
}
else {
for (const key of Object.keys(row)) {
if (key.endsWith(`.${col}`) || key === col) {
projected[col] = row[key];
break;
}
}
}
}
return projected;
}
/**
* metona-sqlark Memory Engine — 基于 Map 的内存存储引擎
* @module engine/memory
*/
class MemoryEngine {
constructor() {
this.name = 'memory';
this.tables = new Map();
this.schemas = new Map();
this.indexes = new Map();
this.opened = false;
// ---- 事务快照 ----
this.snapshot = null;
}
// ---- 生命周期 ----
async open(_dbName, _version) {
if (this.opened) {
// 幂等:已打开则忽略
return;
}
this.opened = true;
}
async close() {
this.tables.clear();
this.schemas.clear();
this.indexes.clear();
this.opened = false;
}
isOpen() { return this.opened; }
// ---- 表管理 ----
async createTable(schema) {
if (this.schemas.has(schema.name))
throw new DatabaseError(`Table "${schema.name}" already exists`, 'TABLE_EXISTS');
this.schemas.set(schema.name, schema);
this.tables.set(schema.name, new Map());
const tableIndexes = new Map();
for (const [colName, colDef] of Object.entries(schema.columns)) {
if (colDef.index || colDef.unique)
tableIndexes.set(colName, new Map());
}
this.indexes.set(schema.name, tableIndexes);
}
async dropTable(tableName) {
this.ensureTable(tableName);
this.schemas.delete(tableName);
this.tables.delete(tableName);
this.indexes.delete(tableName);
}
async hasTable(tableName) { return this.schemas.has(tableName); }
async getTableNames() { return Array.from(this.schemas.keys()); }
async getTableSchema(tableName) { return this.schemas.get(tableName) ?? null; }
// ---- CRUD ----
async insert(tableName, rows) {
this.ensureTable(tableName);
const schema = this.schemas.get(tableName);
const table = this.tables.get(tableName);
const pkColumn = this.getPrimaryKey(schema);
const pks = [];
for (const row of rows) {
const validatedRow = this.validateRow(schema, row);
const pkValue = String(validatedRow[pkColumn]);
if (table.has(pkValue))
throw new DatabaseError(`Duplicate primary key "${pkValue}" in table "${tableName}"`, 'DUPLICATE_KEY');
this.checkUniqueness(schema, validatedRow);
table.set(pkValue, validatedRow);
this.updateIndexes(tableName, validatedRow, pkValue);
pks.push(pkValue);
}
return pks;
}
async find(tableName, query) {
this.ensureTable(tableName);
const table = this.tables.get(tableName);
let results = this.tryIndexLookup(tableName, table, query);
if (query.where && Object.keys(query.where).length > 0) {
results = results.filter((row) => matchWhere(row, query.where));
}
if (query.orderBy && query.orderBy.length > 0) {
results = applyOrderBy(results, query.orderBy);
}
const offset = query.offset ?? 0;
const limit = query.limit ?? results.length;
results = results.slice(offset, offset + limit);
if (query.columns && query.columns.length > 0 && query.columns[0] !== '*') {
results = results.map((row) => projectColumns(row, query.columns));
}
return results;
}
async update(tableName, query, updates) {
this.ensureTable(tableName);
const schema = this.schemas.get(tableName);
const table = this.tables.get(tableName);
let count = 0;
for (const [pk, row] of table) {
if (!query.where || Object.keys(query.where).length === 0 || matchWhere(row, query.where)) {
const updated = { ...row, ...updates };
this.validateRow(schema, updated);
table.set(pk, updated);
count++;
}
}
return count;
}
async delete(tableName, query) {
this.ensureTable(tableName);
const table = this.tables.get(tableName);
const toDelete = [];
for (const [pk, row] of table) {
if (!query.where || Object.keys(query.where).length === 0 || matchWhere(row, query.where)) {
toDelete.push(pk);
}
}
// 级联删除:检查引用此表的其他表
let cascadeCount = 0;
for (const pk of toDelete) {
const row = table.get(pk);
if (row)
cascadeCount += await this.cascadeDelete(tableName, pk, row);
}
for (const pk of toDelete)
table.delete(pk);
return toDelete.length + cascadeCount;
}
async count(tableName, query) {
this.ensureTable(tableName);
const table = this.tables.get(tableName);
if (!query?.where || Object.keys(query.where).length === 0)
return table.size;
let result = 0;
for (const row of table.values()) {
if (matchWhere(row, query.where))
result++;
}
return result;
}
async clear(tableName) {
this.ensureTable(tableName);
this.tables.get(tableName).clear();
const tableIndexes = this.indexes.get(tableName);
if (tableIndexes)
for (const colIndex of tableIndexes.values())
colIndex.clear();
}
// ---- 事务 ----
async beginTransaction() {
if (this.snapshot)
throw new DatabaseError('Transaction already in progress', 'TX_ACTIVE');
this.snapshot = {
tables: this.deepCloneMapMap(this.tables),
schemas: new Map(this.schemas),
indexes: this.deepCloneIndexes(this.indexes),
};
}
async commitTransaction() {
if (!this.snapshot)
throw new DatabaseError('No active transaction', 'TX_NONE');
this.snapshot = null;
}
async rollbackTransaction() {
if (!this.snapshot)
throw new DatabaseError('No active transaction', 'TX_NONE');
this.tables = this.snapshot.tables;
this.schemas = this.snapshot.schemas;
this.indexes = this.snapshot.indexes;
this.snapshot = null;
}
// ---- 事务快照辅助 ----
deepCloneMapMap(source) {
const clone = new Map();
for (const [k, v] of source) {
const innerClone = new Map();
for (const [ik, iv] of v)
innerClone.set(ik, { ...iv });
clone.set(k, innerClone);
}
return clone;
}
deepCloneIndexes(source) {
const clone = new Map();
for (const [tableName, tableIndexes] of source) {
const tableClone = new Map();
for (const [col, colIndex] of tableIndexes) {
const colClone = new Map();
for (const [val, pkSet] of colIndex)
colClone.set(val, new Set(pkSet));
tableClone.set(col, colClone);
}
clone.set(tableName, tableClone);
}
return clone;
}
// ---- 内部辅助 ----
ensureTable(tableName) {
if (!this.tables.has(tableName))
throw new DatabaseError(`Table "${tableName}" does not exist`, 'TABLE_NOT_FOUND');
}
getPrimaryKey(schema) {
for (const [name, col] of Object.entries(schema.columns)) {
if (col.primaryKey)
return name;
}
return Object.keys(schema.columns)[0];
}
validateRow(schema, row) {
const validated = {};
for (const [colName, colDef] of Object.entries(schema.columns)) {
let value = row[colName];
if (value === undefined && colDef.default !== undefined)
value = colDef.default;
if (colDef.required && (value === undefined || value === null)) {
throw new DatabaseError(`Column "${colName}" is required in table "${schema.name}"`, 'VALIDATION_ERROR');
}
if (value !== undefined && value !== null)
this.checkType(colName, colDef.type, value);
if (value !== undefined)
validated[colName] = value;
}
return validated;
}
checkType(colName, type, value) {
const jsType = typeof value;
switch (type) {
case 'string':
if (jsType !== 'string')
throw new DatabaseError(`Column "${colName}" expects string, got ${jsType}`, 'TYPE_ERROR');
break;
case 'number':
if (jsType !== 'number')
throw new DatabaseError(`Column "${colName}" expects number, got ${jsType}`, 'TYPE_ERROR');
break;
case 'boolean':
if (jsType !== 'boolean')
throw new DatabaseError(`Column "${colName}" expects boolean, got ${jsType}`, 'TYPE_ERROR');
break;
case 'date':
if (jsType !== 'string' || isNaN(Date.parse(value)))
throw new DatabaseError(`Column "${colName}" expects valid date`, 'TYPE_ERROR');
break;
case 'json':
if (jsType !== 'object')
throw new DatabaseError(`Column "${colName}" expects object/array, got ${jsType}`, 'TYPE_ERROR');
break;
}
}
/** O(1) 唯一性检查:利用哈希索引 */
checkUniqueness(schema, row) {
const tableIndexes = this.indexes.get(schema.name);
if (!tableIndexes)
return;
for (const [colName, colDef] of Object.entries(schema.columns)) {
if (!colDef.unique || row[colName] === undefined || row[colName] === null)
continue;
const colIndex = tableIndexes.get(colName);
if (colIndex && colIndex.has(row[colName])) {
throw new DatabaseError(`Unique constraint violation on column "${colName}" in table "${schema.name}"`, 'UNIQUE_VIOLATION');
}
}
}
/** 索引查找 */
tryIndexLookup(tableName, table, query) {
const tableIndexes = this.indexes.get(tableName);
if (!tableIndexes || !query.where)
return Array.from(table.values());
for (const [col, condition] of Object.entries(query.where)) {
if (typeof condition !== 'object' || condition === null) {
const colIndex = tableIndexes.get(col);
if (colIndex) {
const pks = colIndex.get(condition);
if (pks) {
const result = [];
for (const pk of pks) {
const r = table.get(pk);
if (r)
result.push(r);
}
return result;
}
return [];
}
}
}
return Array.from(table.values());
}
/** 更新索引 */
updateIndexes(tableName, row, pk) {
const tableIndexes = this.indexes.get(tableName);
if (!tableIndexes)
return;
for (const [colName, colIndex] of tableIndexes) {
const value = row[colName];
if (value !== undefined && value !== null) {
if (!colIndex.has(value))
colIndex.set(value, new Set());
colIndex.get(value).add(pk);
}
}
}
// ---- 外键级联 ----
/**
* 级联删除:查找引用 tableName.pkValue 的所有表的行并删除。
* @returns 级联删除的行数
*/
async cascadeDelete(tableName, pkValue, _row) {
let totalCascade = 0;
for (const [refTableName, refSchema] of this.schemas) {
if (refTableName === tableName)
continue;
for (const [colName, colDef] of Object.entries(refSchema.columns)) {
if (!colDef.references || !colDef.onDelete)
continue;
const [refTable, refCol] = colDef.references.split('.');
if (refTable !== tableName)
continue;
const refTableData = this.tables.get(refTableName);
if (!refTableData)
continue;
// 查找所有引用此主键的行
const toDelete = [];
for (const [refPk, refRow] of refTableData) {
if (String(refRow[colName]) === pkValue) {
toDelete.push(refPk);
}
}
// RESTRICT: 存在引用行时禁止删除
if (colDef.onDelete === 'RESTRICT' && toDelete.length > 0) {
throw new DatabaseError(`Cannot delete from "${tableName}": foreign key "${colName}" in "${refTableName}" has dependent rows`, 'FOREIGN_KEY_VIOLATION');
}
if (colDef.onDelete === 'CASCADE') {
// 递归级联
for (const refPk of toDelete) {
const refRow = refTableData.get(refPk);
if (refRow) {
totalCascade += await this.cascadeDelete(refTableName, refPk, refRow);
}
refTableData.delete(refPk);
totalCascade++;
}
}
else if (colDef.onDelete === 'SET NULL') {
for (const refPk of toDelete) {
const refRow = refTableData.get(refPk);
if (refRow) {
refRow[colName] = null;
}
}
}
}
}
return totalCascade;
}
}
/**
* metona-sqlark IndexedDB Engine — 基于 IndexedDB 的持久化存储引擎
* @module engine/indexeddb
*
* v0.1.13: 支持事务 — beginTransaction 延迟 IDB 写入,commit 批量刷盘,rollback 恢复快照。
*/
class IndexedDBEngine {
constructor() {
this.name = 'indexeddb';
this.db = null;
this.dbName = '';
this.version = 1;
this.memoryCache = new MemoryEngine();
// ---- 事务状态 ----
this.txActive = false;
}
async open(dbName, version) {
this.dbName = dbName;
this.version = version;
await this.memoryCache.open(dbName, version);
return new Promise((resolve, reject) => {
const request = indexedDB.open(dbName, version);
request.onsuccess = () => {
this.db = request.result;
// 多标签页冲突处理:其他标签页升级版本时自动关闭当前连接
this.db.onversionchange = () => {
if (this.db) {
this.db.close();
this.db = null;
// eslint-disable-next-line no-console
console.warn(`[metona-sqlark] Database "${dbName}" was upgraded in another tab. Connection closed. Please re-open.`);
}
};
resolve();
};
request.onerror = () => reject(new DatabaseError(`Failed to open IndexedDB "${dbName}"`, 'IDB_OPEN_ERROR', request.error));
request.onblocked = () => reject(new DatabaseError(`IndexedDB "${dbName}" is blocked`, 'IDB_BLOCKED'));
});
}
async close() {
if (this.db) {
this.db.onversionchange = null; // 清理监听器
this.db.close();
this.db = null;
}
await this.memoryCache.close();
}
isOpen() { return this.db !== null; }
// ---- 表管理 ----
async createTable(schema) {
await this.memoryCache.createTable(schema);
if (this.txActive)
return; // 事务中延迟 IDB 操作
await this.idbCreateTable(schema);
}
async dropTable(tableName) {
await this.memoryCache.dropTable(tableName);
if (this.txActive)
return;
await this.idbDropTable(tableName);
}
async hasTable(tableName) { return this.ensureDB().objectStoreNames.contains(tableName); }
async getTableNames() { return Array.from(this.ensureDB().objectStoreNames); }
async getTableSchema(tableName) { return this.memoryCache.getTableSchema(tableName); }
// ---- CRUD ----
async insert(tableName, rows) {
const pks = await this.memoryCache.insert(tableName, rows);
if (this.txActive)
return pks; // 事务中延迟写入
await this.idbInsert(tableName, rows);
return pks;
}
async find(tableName, query) {
// 事务中从内存缓存读取(保证读到未提交的变更),否则走 IDB
if (this.txActive)
return this.memoryCache.find(tableName, query);
return this.idbFind(tableName, query);
}
async update(tableName, query, updates) {
const count = await this.memoryCache.update(tableName, query, updates);
if (this.txActive)
return count;
await this.idbUpdate(tableName, query, updates);
return count;
}
async delete(tableName, query) {
const count = await this.memoryCache.delete(tableName, query);
if (this.txActive)
return count;
await this.idbDelete(tableName, query);
return count;
}
async count(tableName, query) {
if (this.txActive)
return this.memoryCache.count(tableName, query);
const results = await this.idbFind(tableName, { table: tableName, where: query?.where ?? {} });
return results.length;
}
async clear(tableName) {
await this.memoryCache.clear(tableName);
if (this.txActive)
return;
await this.idbClear(tableName);
}
// ---- 事务 ----
async beginTransaction() {
if (this.txActive)
throw new DatabaseError('Transaction already in progress', 'TX_ACTIVE');
this.txActive = true;
// 保存内存快照到 MemoryEngine 内部的 beginTransaction
await this.memoryCache.beginTransaction();
}
async commitTransaction() {
if (!this.txActive)
throw new DatabaseError('No active transaction', 'TX_NONE');
// 确认内存层的变更
await this.memoryCache.commitTransaction();
// 批量将内存数据刷到 IndexedDB
await this.flushToIDB();
this.txActive = false;
}
async rollbackTransaction() {
if (!this.txActive)
throw new DatabaseError('No active transaction', 'TX_NONE');
// 恢复内存层到快照状态
await this.memoryCache.rollbackTransaction();
this.txActive = false;
}
// ---- IDB 原生操作 ----
async idbCreateTable(schema) {
const db = this.ensureDB();
return new Promise((resolve, reject) => {
const newVersion = db.version + 1;
db.close();
const request = indexedDB.open(this.dbName, newVersion);
request.onupgradeneeded = (event) => {
const db = event.target.result;
const pkColumn = Object.entries(schema.columns).find(([, c]) => c.primaryKey)?.[0] ?? Object.keys(schema.columns)[0];
const store = db.createObjectStore(schema.name, { keyPath: pkColumn });
for (const [colName, colDef] of Object.entries(schema.columns)) {
if (colDef.index && colName !== pkColumn) {
store.createIndex(`idx_${colName}`, colName, { unique: colDef.unique ?? false });
}
}
};
request.onsuccess = () => { this.db = request.result; resolve(); };
request.onerror = () => reject(new DatabaseError(`Failed to create table "${schema.name}"`, 'IDB_UPGRADE_ERROR', request.error));
});
}
async idbDropTable(tableName) {
const db = this.ensureDB();
return new Promise((resolve, reject) => {
const newVersion = db.version + 1;
db.close();
const request = indexedDB.open(this.dbName, newVersion);
request.onupgradeneeded = (event) => {
const db = event.target.result;
if (db.objectStoreNames.contains(tableName))
db.deleteObjectStore(tableName);
};
request.onsuccess = () => { this.db = request.result; resolve(); };
request.onerror = () => reject(new DatabaseError(`Failed to drop table "${tableName}"`, 'IDB_UPGRADE_ERROR', request.error));
});
}
async idbInsert(tableName, rows) {
const db = this.ensureDB();
return new Promise((resolve, reject) => {
const tx = db.transaction(tableName, 'readwrite');
const store = tx.objectStore(tableName);
for (const row of rows)
store.add(row);
tx.oncomplete = () => resolve();
tx.onerror = () => reject(new DatabaseError(`Insert failed for "${tableName}"`, 'IDB_TX_ERROR', tx.error));
});
}
async idbFind(tableName, query) {
const db = this.ensureDB();
// 尝试使用 IDB 索引进行等值查询
if (query.where) {
const indexResult = await this.tryIDBIndexLookup(db, tableName, query);
if (indexResult !== null)
return indexResult;
}
// 回退到全量 getAll + 内存过滤
return new Promise((resolve, reject) => {
const tx = db.transaction(tableName, 'readonly');
const req = tx.objectStore(tableName).getAll();
req.onsuccess = () => {
let results = req.result ?? [];
if (query.where && Object.keys(query.where).length > 0) {
results = results.filter((row) => matchWhere(row, query.where));
}
if (query.orderBy && query.orderBy.length > 0) {
results = applyOrderBy(results, query.orderBy);
}
const offset = query.offset ?? 0;
const limit = query.limit ?? results.length;
results = results.slice(offset, offset + limit);
if (query.columns && query.columns.length > 0 && query.columns[0] !== '*') {
results = results.map((r) => projectColumns(r, query.columns));
}
resolve(results);
};
req.onerror = () => reject(new DatabaseError(`Find failed for "${tableName}"`, 'IDB_READ_ERROR', req.error));
});
}
/** 尝试使用 IDB 索引进行等值查询,成功返回结果,不适用返回 null */
async tryIDBIndexLookup(db, tableName, query) {
if (!query.where)
return null;
for (const [col, condition] of Object.entries(query.where)) {
// 跳过逻辑组合符
if (col === '$and' || col === '$or' || col === '$not')
continue;
// 只处理等值查询
let targetValue;
if (typeof condition !== 'object' || condition === null) {
targetValue = condition;
}
else if ('$eq' in condition) {
targetValue = condition.$eq;
}
else {
continue;
}
// 检查是否有对应的 IDB 索引
const indexName = `idx_${col}`;
try {
return await new Promise((resolve, reject) => {
const tx = db.transaction(tableName, 'readonly');
const store = tx.objectStore(tableName);
if (!store.indexNames.contains(indexName)) {
resolve(null); // 没有索引,回退
return;
}
const index = store.index(indexName);
const req = index.getAll(targetValue);
req.onsuccess = () => {
let results = req.result ?? [];
// 如果有其他 WHERE 条件,继续过滤
const otherKeys = Object.keys(query.where).filter((k) => k !== col && k !== '$and' && k !== '$or' && k !== '$not');
if (otherKeys.length > 0) {
results = results.filter((row) => matchWhere(row, query.where));
}
if (query.orderBy && query.orderBy.length > 0) {
results = applyOrderBy(results, query.orderBy);
}
const offset = query.offset ?? 0;
const limit = query.limit ?? results.length;
results = results.slice(offset, offset + limit);
if (query.columns && query.columns.length > 0 && query.columns[0] !== '*') {
results = results.map((r) => projectColumns(r, query.columns));
}
resolve(results);
};
req.onerror = () => reject(new DatabaseError(`Index lookup failed for "${tableName}.${col}"`, 'IDB_READ_ERROR', req.error));
});
}
catch {
return null; // 索引不可用,回退
}
}
return null;
}
async idbUpdate(tableName, query, updates) {
const db = this.ensureDB();
return new Promise((resolve, reject) => {
const tx = db.transaction(tableName, 'readwrite');
const store = tx.objectStore(tableName);
const getAllReq = store.getAll();
getAllReq.onsuccess = () => {
for (const row of getAllReq.result ?? []) {
if (!query.where || Object.keys(query.where).length === 0 || matchWhere(row, query.where)) {
Object.assign(row, updates);
store.put(row);
}
}
};
tx.oncomplete = () => resolve();
tx.onerror = () => reject(new DatabaseError(`Update failed for "${tableName}"`, 'IDB_TX_ERROR', tx.error));
});
}
async idbDelete(tableName, query) {
const db = this.ensureDB();
const schema = await this.memoryCache.getTableSchema(tableName);
if (!schema)
throw new DatabaseError(`Table "${tableName}" not found`, 'TABLE_NOT_FOUND');
const pkColumn = Object.entries(schema.columns).find(([, c]) => c.primaryKey)?.[0] ?? Object.keys(schema.columns)[0];
return new Promise((resolve, reject) => {
const tx = db.transaction(tableName, 'readwrite');
const store = tx.objectStore(tableName);
const getAllReq = store.getAll();
getAllReq.onsuccess = () => {
for (const row of getAllReq.result ?? []) {
if (!query.where || Object.keys(query.where).length === 0 || matchWhere(row, query.where)) {
store.delete(row[pkColumn]);
}
}
};
tx.oncomplete = () => resolve();
tx.onerror = () => reject(new DatabaseError(`Delete failed for "${tableName}"`, 'IDB_TX_ERROR', tx.error));
});
}
async idbClear(tableName) {
const db = this.ensureDB();
return new Promise((resolve, reject) => {
const tx = db.transaction(tableName, 'readwrite');
tx.objectStore(tableName).clear();
tx.oncomplete = () => resolve();
tx.onerror = () => reject(new DatabaseError(`Clear failed for "${tableName}"`, 'IDB_TX_ERROR', tx.error));
});
}
/** 将内存缓存中的所有表数据原子性刷新到 IndexedDB */
async flushToIDB() {
const tableNames = await this.memoryCache.getTableNames();
const db = this.ensureDB();
// 每个表在一个单独的 IDB 事务中完成 clear+insert,保证原子性
for (const tableName of tableNames) {
const rows = await this.memoryCache.find(tableName, { table: tableName });
await new Promise((resolve, reject) => {
const tx = db.transaction(tableName, 'readwrite');
const store = tx.objectStore(tableName);
store.clear(); // 清空
for (const row of rows)
store.add(row); // 批量写入
tx.oncomplete = () => resolve();
tx.onerror = () => reject(new DatabaseError(`Flush failed for "${tableName}"`, 'IDB_FLUSH_ERROR', tx.error));
});
}
}
ensureDB() {
if (!this.db)
throw new DatabaseError('Database not opened', 'DB_NOT_OPEN');
return this.db;
}
}
/**
* metona-sqlark OPFS Engine — 基于 Origin Private File System 的持久化存储引擎
* @module engine/opfs
*
* 使用 JSON-per-table 文件存储方案。
* 目录结构:{dbName}/tables/{tableName}.json
*/
// ---------------------------------------------------------------------------
// OPFSEngine
// ---------------------------------------------------------------------------
class OPFSEngine {
constructor() {
this.name = 'opfs';
this.root = null;
this.tablesDir = null;
this.dbName = '';
// 运行时内存缓存(OPFS 文件读写有延迟)
this.memoryCache = new MemoryEngine();
}
// ---- 生命周期 ----
async open(dbName, version) {
this.dbName = dbName;
await this.memoryCache.open(dbName, version);
// 获取 OPFS 根目录
this.root = await navigator.storage.getDirectory();
// 创建/打开数据库目录
this.tablesDir = await this.root.getDirectoryHandle(dbName, { create: true });
// 从 OPFS 恢复已有表数据到内存缓存
await this.loadExistingTables();
}
async close() {
this.root = null;
this.tablesDir = null;
await this.memoryCache.close();
}
isOpen() {
return this.tablesDir !== null;
}
// ---- 表管理 ----
async createTable(schema) {
await this.memoryCache.createTable(schema);
// OPFS 中表以空 JSON 数组文件形式存在
await this.writeTableData(schema.name, []);
}
async dropTable(tableName) {
await this.memoryCache.dropTable(tableName);
if (this.tablesDir) {
try {
await this.tablesDir.removeEntry(`${tableName}.json`);
}
catch {
// 文件不存在则忽略
}
}
}
async hasTable(tableName) {
if (!this.tablesDir)
return false;
try {
await this.tablesDir.getFileHandle(`${tableName}.json`);
return true;
}
catch {
return false;
}
}
async getTableNames() {
if (!this.tablesDir)
return [];
const names = [];
for await (const [name] of this.tablesDir.entries()) {
if (name.endsWith('.json')) {
names.push(name.replace('.json', ''));
}
}
return names;
}
async getTableSchema(tableName) {
return this.memoryCache.getTableSchema(tableName);
}
// ---- CRUD ----
async insert(tableName, rows) {
const pks = await this.memoryCache.insert(tableName, rows);
// 持久化到 OPFS
const allRows = await this.memoryCache.find(tableName, { table: tableName });
await this.writeTableData(tableName, allRows);
return pks;
}
async find(tableName, query) {
return this.memoryCache.find(tableName, query);
}
async update(tableName, query, updates) {
const count = await this.memoryCache.update(tableName, query, updates);
const allRows = await this.memoryCache.find(tableName, { table: tableName });
await this.writeTableData(tableName, allRows);
return count;
}
async delete(tableName, query) {
const count = await this.memoryCache.delete(tableName, query);
const allRows = await this.memoryCache.find(tableName, { table: tableName });
await this.writeTableData(tableName, allRows);
return count;
}
async count(tableName, query) {
return this.memoryCache.count(tableName, query);
}
async clear(tableName) {
await this.memoryCache.clear(tableName);
await this.writeTableData(tableName, []);
}
// ---- 事务 ----
async beginTransaction() {
await this.memoryCache.beginTransaction();
}
async commitTransaction() {
await this.memoryCache.commitTransaction();
// 将内存数据刷到 OPFS
const tableNames = await this.memoryCache.getTableNames();
for (const tableName of tableNames) {
const rows = await this.memoryCache.find(tableName, { table: tableName });
await this.writeTableData(tableName, rows);
}
}
async rollbackTransaction() {
await this.memoryCache.rollbackTransaction();
}
// ---- 内部辅助 ----
ensureDir() {
if (!this.tablesDir) {
throw new DatabaseError('Database not opened', 'DB_NOT_OPEN');
}
return this.tablesDir;
}
async writeTableData(tableName, data) {
const dir = this.ensureDir();
const fileName = `${tableName}.json`;
const fileHandle = await dir.getFileHandle(fileName, { create: true });
const writable = await fileHandle.createWritable();
await writable.write(JSON.stringify(data));
await writable.close();
}
async readTableData(tableName) {
const dir = this.ensureDir();
const fileName = `${tableName}.json`;
try {
const fileHandle = await dir.getFileHandle(fileName);
const file = await fileHandle.getFile();
const text = await file.text();
return JSON.parse(text);
}
catch {
return [];
}
}
/** 从 OPFS 加载已有表数据到内存缓存 */
async loadExistingTables() {
if (!this.tablesDir)
return;
const dir = this.tablesDir;
for await (const [name] of dir.entries()) {
if (!name.endsWith('.json'))
continue;
const tableName = name.replace('.json', '');
try {
const data = await this.readTableData(tableName);
// 从数据中推断 schema(简化:从第一行提取列信息)
if (data.length > 0) {
const firstRow = data[0];
const columns = {};
for (const key of Object.keys(firstRow)) {
const val = firstRow[key];
const type = typeof val === 'number' ? 'number' :
typeof val === 'boolean' ? 'boolean' :
typeof val === 'object' ? 'json' : 'string';
columns[key] = { type, primaryKey: key === 'id' };
}
await this.memoryCache.createTable({ name: tableName, columns });
for (const row of data) {
await this.memoryCache.insert(tableName, [row]);
}
}
}
catch {
// 单个文件损坏不影响其他表
}
}
}
/** 从 OPFS 加载表数据到内存缓存 */
async loadTableIntoMemory(tableName, schema) {
await this.memoryCache.createTable(schema);
const rows = await this.readTableData(tableName);
if (rows.length > 0) {
// 直接用 Map 设置绕过 insert 校验
for (const row of rows) {
await this.memoryCache.insert(tableName, [row]);
}
}
}
}
/**
* metona-sqlark Schema — 表结构定义与校验
* @module table/schema
*/
// ---------------------------------------------------------------------------
// Schema 工具
// ---------------------------------------------------------------------------
/** 从列定义创建 TableSchema */
function createSchema(name, columns) {
validateColumns(columns);
return { name, columns };
}
/** 校验列定义 */
function validateColumns(columns) {
const colNames = Object.keys(columns);
if (colNames.length === 0) {
throw new DatabaseError('Table must have at least one column', 'SCHEMA_ERROR');
}
let primaryKeyCount = 0;
for (const [colName, colDef] of Object.entries(columns)) {
// 类型校验
if (!FIELD_TYPES.includes(colDef.type)) {
throw new DatabaseError(`Invalid type "${colDef.type}" for column "${colName}". Valid types: ${FIELD_TYPES.join(', ')}`, 'SCHEMA_ERROR');
}
// 主键计数
if (colDef.primaryKey) {
primaryKeyCount++;
}
}
// 至少需要一个主键
if (primaryKeyCount === 0) {
throw new DatabaseError('Table must have at least one primary key column', 'SCHEMA_ERROR');
}
}
/** 检查字段类型(含约束校验) */
function checkFieldType(tableName, colName, type, value, colDef) {
const jsType = typeof value;
switch (type) {
case 'string':
if (jsType !== 'string') {
throw new DatabaseError(`Column "${colName}" in table "${tableName}" expects string, got ${jsType}`, 'TYPE_ERROR');
}
if (colDef?.maxLength !== undefined && value.length > colDef.maxLength) {
throw new DatabaseError(`Column "${colName}" in table "${tableName}" exceeds max length ${colDef.maxLength}`, 'VALIDATION_ERROR');
}
break;
case 'number':
if (jsType !== 'number') {
throw new DatabaseError(`Column "${colName}" in table "${tableName}" expects number, got ${jsType}`, 'TYPE_ERROR');
}
if (colDef?.min !== undefined && value < colDef.min) {
throw new DatabaseError(`Column "${colName}" in table "${tableName}" value ${value} below minimum ${colDef.min}`, 'VALIDATION_ERROR');
}
if (colDef?.max !== undefined && value > colDef.max) {
throw new DatabaseError(`Column "${colName}" in table "${tableName}" value ${value} above maximum ${colDef.max}`, 'VALIDATION_ERROR');
}
break;
case 'boolean':
if (jsType !== 'boolean') {
throw new DatabaseError(`Column "${colName}" in table "${tableName}" expects boolean, got ${jsType}`, 'TYPE_ERROR');
}
break;
case 'date':
if (jsType !== 'string' || isNaN(Date.parse(value))) {
throw new DatabaseError(`Column "${colName}" in table "${tableName}" expects valid date string, got ${typeof value}`, 'TYPE_ERROR');
}
break;
case 'json':
if (jsType !== 'object') {
throw new DatabaseError(`Column "${colName}" in table "${tableName}" expects object/array, got ${jsType}`, 'TYPE_ERROR');
}
break;
}
}
/** 将 AST 列定义转换为 ColumnDef */
function astColumnToColumnDef(astCol) {
return {
type: astCol.type,
primaryKey: astCol.primaryKey,
unique: astCol.unique,
required: astCol.required,
default: astCol.default,
index: astCol.index,
maxLength: astCol.maxLength,
min: astCol.min,
max: astCol.max,
references: astCol.references,
onDelete: astCol.onDelete,
onUpdate: astCol.onUpdate,
};
}
/**
* AriaEngine Types — 内部类型定义
* @module engine/aria/types
*
* 页面式存储引擎的所有内部枚举、接口和常量。
*/
// =============================================================================
// 页面常量
// =============================================================================
/** 页面大小:4KB */
const PAGE_SIZE = 4096;
/** 页面头大小:16 字节 */
const PAGE_HEADER_SIZE = 16;
// =============================================================================
// 页面类型
// =============================================================================
var PageType;
(function (PageType) {
/** 数据页面:存储行数据 */
PageType[PageType["DATA"] = 1] = "DATA";
/** 索引页面:存储索引节点 */
PageType[PageType["INDEX"] = 2] = "INDEX";
/** 溢出页面:存储大字段 */
PageType[PageType["OVERFLOW"] = 3] = "OVERFLOW";
/** 元数据页面:存储表/库元信息 */
PageType[PageType["META"] = 4] = "META";
})(PageType || (PageType = {}));
/** 列类型(内部二进制编码用) */
var ColumnEncoding;
(function (ColumnEncoding) {
ColumnEncoding[ColumnEncoding["STRING"] = 1] = "STRING";
ColumnEncoding[ColumnEncoding["NUMBER"] = 2] = "NUMBER";
ColumnEncoding[ColumnEncoding["BOOLEAN"] = 3] = "BOOLEAN";
ColumnEncoding[ColumnEncoding["DATE"] = 4] = "DATE";
ColumnEncoding[ColumnEncoding["JSON"] = 5] = "JSON";
ColumnEncoding[ColumnEncoding["NULL"] = 6] = "NULL";
})(ColumnEncoding || (ColumnEncoding = {}));
// =============================================================================
// LSM-Tree
// =============================================================================
/** MemTable 最大大小(默认 4MB */
const DEFAULT_MEMTABLE_SIZE = 4 * 1024 * 1024;
/** Bloom Filter 每 key 的默认位数 */
const DEFAULT_BLOOM_BITS_PER_KEY = 10;
/** SSTable 最大层级 */
const MAX_LSM_LEVELS = 7;
/** 每层之间的大小倍数 */
const DEFAULT_LEVEL_SIZE_MULTIPLIER = 10;
// =============================================================================
// WAL (Write-Ahead Log)
// =============================================================================
/** WAL 记录类型 */
var WALRecordType;
(function (WALRecordType) {
WALRecordType[WALRecordType["INSERT"] = 1] = "INSERT";
WALRecordType[WALRecordType["UPDATE"] = 2] = "UPDATE";
WALRecordType[WALRecordType["DELETE"] = 3] = "DELETE";
WALRecordType[WALRecordType["BEGIN"] = 4] = "BEGIN";
WALRecordType[WALRecordType["COMMIT"] = 5] = "COMMIT";
WALRecordType[WALRecordType["ROLLBACK"] = 6] = "ROLLBACK";
WALRecordType[WALRecordType["CREATE_TABLE"] = 7] = "CREATE_TABLE";
WALRecordType[WALRecordType["DROP_TABLE"] = 8] = "DROP_TABLE";
})(WALRecordType || (WALRecordType = {}));
// =============================================================================
// MVCC
// =============================================================================
/** 事务隔离级别 */
var IsolationLevel;
(function (IsolationLevel) {
IsolationLevel[IsolationLevel["READ_COMMITTED"] = 1] = "READ_COMMITTED";
IsolationLevel[IsolationLevel["SNAPSHOT"] = 2] = "SNAPSHOT";
})(IsolationLevel || (IsolationLevel = {}));
/** 事务状态 */
var TransactionState;
(function (TransactionState) {
TransactionState[TransactionState["ACTIVE"] = 1] = "ACTIVE";
TransactionState[TransactionState["COMMITTED"] = 2] = "COMMITTED";
TransactionState[TransactionState["ABORTED"] = 3] = "ABORTED";
})(TransactionState || (TransactionState = {}));
// =============================================================================
// Buffer Pool
// =============================================================================
/** Buffer Pool 默认容量:256 页 ≈ 1MB */
const DEFAULT_BUFFER_POOL_PAGES = 256;
const DEFAULT_ARIA_CONFIG = {
pageSize: PAGE_SIZE,
bufferPoolPages: DEFAULT_BUFFER_POOL_PAGES,
memtableSizeThreshold: DEFAULT_MEMTABLE_SIZE,
levelSizeMultiplier: DEFAULT_LEVEL_SIZE_MULTIPLIER,
bloomFilterBitsPerKey: DEFAULT_BLOOM_BITS_PER_KEY,
walEnabled: true,
walSyncMode: 'full',
checkpointInterval: 1000,
compression: false,
storageBackend: 'indexeddb',
walSizeThreshold: 16 * 1024 * 1024, // 16MB
maxMemoryMB: 64,
};
/**
* AriaEngine MemTable — 基于红黑树的内存表
* @module engine/aria/index/memtable
*
* 写操作先进入 MemTable,达到阈值后冻结并 flush 成 SSTable。
*/
// ---------------------------------------------------------------------------
// RB-Tree Node
// ---------------------------------------------------------------------------
var Color;
(function (Color) {
Color[Color["RED"] = 0] = "RED";
Color[Color["BLACK"] = 1] = "BLACK";
})(Color || (Color = {}));
class RBNode {
constructor(key, value) {
this.color = Color.RED;
this.left = null;
this.right = null;
this.parent = null;
this.key = key;
this.value = value;
}
}
// ---------------------------------------------------------------------------
// Red-Black Tree
// ---------------------------------------------------------------------------
class RedBlackTree {
constructor() {
this.root = null;
this._size = 0;
}
get size() { return this._size; }
// ---- 插入 ----
insert(key, value) {
const node = new RBNode(key, value);
if (!this.root) {
this.root = node;
node.color = Color.BLACK;
this._size++;
return;
}
let parent = null;
let current = this.root;
while (current) {
parent = current;
if (key < current.key) {
current = current.left;
}
else if (key > current.key) {
current = current.right;
}
else {
// 更新已存在的 key
current.value = value;
return;
}
}
node.parent = parent;
if (key < parent.key) {
parent.left = node;
}
else {
parent.right = node;
}
this._size++;
this.fixInsert(node);
}
// ---- 查找 ----
find(key) {
let current = this.root;
while (current) {
if (key < current.key) {
current = current.left;
}
else if (key > current.key) {
current = current.right;
}
else {
return current.value;
}
}
return null;
}
// ---- 删除 ----
delete(key) {
// 简化实现:标记删除(实际改为找到并调整树)
const node = this.findNode(key);
if (!node)
return false;
this.deleteNode(node);
this._size--;
return true;
}
// ---- 遍历 ----
/** 中序遍历(有序) */
inorder(callback) {
this._inorder(this.root, callback);
}
/** 范围遍历 */
rangeScan(startKey, endKey, callback) {
this._rangeScan(this.root, startKey, endKey, callback);
}
/** 获取所有条目 */
getAllEntries() {
const entries = [];
this.inorder((k, v) => entries.push([k, v]));
return entries;
}
/** 清空 */
clear() {
this.root = null;
this._size = 0;
}
// ---- 内部方法 ----
findNode(key) {
let current = this.root;
while (current) {
if (key < current.key) {
current = current.left;
}
else if (key > current.key) {
current = current.right;
}
else {
return current;
}
}
return null;
}
deleteNode(node) {
// 简化:用左子树最大或右子树最小替换
// 完整实现较复杂,这里采用简化策略
if (!node.left && !node.right) {
this.transplant(node, null);
if (node.color === Color.BLACK)
this.fixDelete(null, node.parent);
}
else if (!node.left) {
this.transplant(node, node.right);
if (node.color === Color.BLACK)
this.fixDelete(node.right, node.right.parent);
}
else if (!node.right) {
this.transplant(node, node.left);
if (node.color === Color.BLACK)
this.fixDelete(node.left, node.left.parent);
}
else {
const successor = this.minimum(node.right);
if (successor.parent !== node) {
this.transplant(successor, successor.right);
successor.right = node.right;
successor.right.parent = successor;
}
this.transplant(node, successor);
successor.left = node.left;
successor.left.parent = successor;
const origColor = successor.color;
successor.color = node.color;
if (origColor === Color.BLACK)
this.fixDelete(successor.right, successor.right?.parent ?? null);
}
}
transplant(u, v) {
if (!u.parent) {
this.root = v;
}
else if (u === u.parent.left) {
u.parent.left = v;
}
else {
u.parent.right = v;
}
if (v)
v.parent = u.parent;
}
minimum(node) {
while (node.left)
node = node.left;
return node;
}
fixInsert(node) {
while (node.parent && node.parent.color === Color.RED) {
const parent = node.parent;
const grandparent = parent.parent;
if (!grandparent)
break;
if (parent === grandparent.left) {
const uncle = grandparent.right;
if (uncle && uncle.color === Color.RED) {
parent.color = Color.BLACK;
uncle.color = Color.BLACK;
grandparent.color = Color.RED;
node = grandparent;
}
else {
if (node === parent.right) {
node = parent;
this.rotateLeft(node);
}
if (node.parent)
node.parent.color = Color.BLACK;
if (node.parent?.parent)
node.parent.parent.color = Color.RED;
if (node.parent?.parent)
this.rotateRight(node.parent.parent);
}
}
else {
const uncle = grandparent.left;
if (uncle && uncle.color === Color.RED) {
parent.color = Color.BLACK;
uncle.color = Color.BLACK;
grandparent.color = Color.RED;
node = grandparent;
}
else {
if (node === parent.left) {
node = parent;
this.rotateRight(node);
}
if (node.parent)
node.parent.color = Color.BLACK;
if (node.parent?.parent)
node.parent.parent.color = Color.RED;
if (node.parent?.parent)
this.rotateLeft(node.parent.parent);
}
}
}
if (this.root)
this.root.color = Color.BLACK;
}
fixDelete(x, parent) {
// 标准 RB-Tree 删除修复(修复"双黑"问题)
let node = x;
let nodeParent = parent;
while ((!node || node.color === Color.BLACK) && node !== this.root) {
if (!nodeParent)
break;
if (node === nodeParent.left) {
let sibling = nodeParent.right;
if (!sibling)
break;
// Case 1: 兄弟是红色
if (sibling.color === Color.RED) {
sibling.color = Color.BLACK;
nodeParent.color = Color.RED;
this.rotateLeft(nodeParent);
sibling = nodeParent.right;
if (!sibling)
break;
}
// Case 2: 兄弟的两个子节点都是黑色
const sibLeft = sibling.left;
const sibRight = sibling.right;
if ((!sibLeft || sibLeft.color === Color.BLACK) &&
(!sibRight || sibRight.color === Color.BLACK)) {
sibling.color = Color.RED;
node = nodeParent;
nodeParent = node.parent;
}
else {
// Case 3: 兄弟右子黑色(左子红色)
if (!sibRight || sibRight.color === Color.BLACK) {
if (sibLeft)
sibLeft.color = Color.BLACK;
sibling.color = Color.RED;
this.rotateRight(sibling);
sibling = nodeParent.right;
if (!sibling)
break;
}
// Case 4: 兄弟右子红色
sibling.color = nodeParent.color;
nodeParent.color = Color.BLACK;
if (sibling.right)
sibling.right.color = Color.BLACK;
this.rotateLeft(nodeParent);
node = this.root;
}
}
else {
// 镜像:node 是父节点的右子
let sibling = nodeParent.left;
if (!sibling)
break;
if (sibling.color === Color.RED) {
sibling.color = Color.BLACK;
nodeParent.color = Color.RED;
this.rotateRight(nodeParent);
sibling = nodeParent.left;
if (!sibling)
break;
}
const sibLeft = sibling.left;
const sibRight = sibling.right;
if ((!sibLeft || sibLeft.color === Color.BLACK) &&
(!sibRight || sibRight.color === Color.BLACK)) {
sibling.color = Color.RED;
node = nodeParent;
nodeParent = node.parent;
}
else {
if (!sibLeft || sibLeft.color === Color.BLACK) {
if (sibRight)
sibRight.color = Color.BLACK;
sibling.color = Color.RED;
this.rotateLeft(sibling);
sibling = nodeParent.left;
if (!sibling)
break;
}
sibling.color = nodeParent.color;
nodeParent.color = Color.BLACK;
if (sibling.left)
sibling.left.color = Color.BLACK;
this.rotateRight(nodeParent);
node = this.root;
}
}
}
if (node)
node.color = Color.BLACK;
}
rotateLeft(x) {
const y = x.right;
if (!y)
return;
x.right = y.left;
if (y.left)
y.left.parent = x;
y.parent = x.parent;
if (!x.parent) {
this.root = y;
}
else if (x === x.parent.left) {
x.parent.left = y;
}
else {
x.parent.right = y;
}
y.left = x;
x.parent = y;
}
rotateRight(x) {
const y = x.left;
if (!y)
return;
x.left = y.right;
if (y.right)
y.right.parent = x;
y.parent = x.parent;
if (!x.parent) {
this.root = y;
}
else if (x === x.parent.right) {
x.parent.right = y;
}
else {
x.parent.left = y;
}
y.right = x;
x.parent = y;
}
_inorder(node, cb) {
if (!node)
return;
this._inorder(node.left, cb);
cb(node.key, node.value);
this._inorder(node.right, cb);
}
_rangeScan(node, start, end, cb) {
if (!node)
return;
if (node.key > start)
this._rangeScan(node.left, start, end, cb);
if (node.key >= start && node.key <= end)
cb(node.key, node.value);
if (node.key < end)
this._rangeScan(node.right, start, end, cb);
}
}
// ---------------------------------------------------------------------------
// MemTable
// ---------------------------------------------------------------------------
class MemTable {
constructor(maxSize = 4 * 1024 * 1024) {
this._estimatedSize = 0;
this.tree = new RedBlackTree();
this.maxSize = maxSize;
}
/** 插入或更新 */
put(key, value) {
const oldSize = this.estimateEntrySize(key, this.tree.find(key));
const newSize = this.estimateEntrySize(key, value);
this.tree.insert(key, value);
this._estimatedSize += newSize - oldSize;
}
/** 获取 */
get(key) {
return this.tree.find(key);
}
/** 删除 */
delete(key) {
const oldVal = this.tree.find(key);
if (oldVal) {
this._estimatedSize -= this.estimateEntrySize(key, oldVal);
}
return this.tree.delete(key);
}
/** 是否应刷盘 */
shouldFlush() {
return this._estimatedSize >= this.maxSize;
}
/** 获取所有有序条目 */
getAllEntries() {
return this.tree.getAllEntries();
}
/** 范围扫描 */
rangeScan(startKey, endKey) {
const entries = [];
this.tree.rangeScan(startKey, endKey, (k, v) => entries.push([k, v]));
return entries;
}
/** 条目数 */
getEntryCount() {
return this.tree.size;
}
/** 估计大小(字节) */
getEstimatedSize() {
return this._estimatedSize;
}
/** 清空 */
clear() {
this.tree.clear();
this._estimatedSize = 0;
}
/** 检查 key 是否存在 */
contains(key) {
return this.tree.find(key) !== null;
}
// -----------------------------------------------------------------------
// 内部
// -----------------------------------------------------------------------
estimateEntrySize(key, value) {
if (!value)
return 0;
let size = key.length * 2; // UTF-16
for (const entry of Object.entries(value)) {
size += entry[0].length * 2;
const v = entry[1];
if (typeof v === 'string')
size += v.length * 2;
else if (typeof v === 'number')
size += 8;
else if (typeof v === 'boolean')
size += 1;
else if (v === null || v === undefined)
size += 1;
else
size += 16; // rough estimate
}
return size;
}
}
/**
* AriaEngine Bloom Filter — 快速判定 key 是否可能存在
* @module engine/aria/index/bloom
*
* 使用双哈希函数 + Kirsch-Mitzenmacher 优化生成 k 个哈希值。
*/
// ---------------------------------------------------------------------------
// BloomFilter
// ---------------------------------------------------------------------------
class BloomFilter {
/**
* @param numKeys 预期插入的 key 数量
* @param bitsPerKey 每个 key 的位数(默认 10,误报率约 1%)
*/
constructor(numKeys, bitsPerKey = DEFAULT_BLOOM_BITS_PER_KEY) {
this._inserted = 0;
// ceil(numKeys * bitsPerKey / 8),最少 64 位
const numBits = Math.max(64, numKeys * bitsPerKey);
const numBytes = Math.ceil(numBits / 8);
this.bits = new Uint8Array(numBytes);
// k = bitsPerKey * ln(2) ≈ bitsPerKey * 0.69
this.numHashes = Math.max(1, Math.floor(bitsPerKey * 0.69));
}
/** 从现有数据恢复 */
static fromData(data, numHashes) {
const bf = new BloomFilter(1); // dummy
bf.bits = data;
bf.numHashes = numHashes;
return bf;
}
/** 插入 key */
insert(key) {
const hashes = this.getHashes(key);
for (const h of hashes) {
const byteIdx = Math.floor(h / 8);
const bitIdx = h % 8;
this.bits[byteIdx] |= (1 << bitIdx);
}
this._inserted++;
}
/** 检查 key 可能存在(false positive 可能,false negative 不可能) */
mayContain(key) {
const hashes = this.getHashes(key);
for (const h of hashes) {
const byteIdx = Math.floor(h / 8);
const bitIdx = h % 8;
if ((this.bits[byteIdx] & (1 << bitIdx)) === 0) {
return false; // 确定不存在
}
}
return true; // 可能存在
}
/** 获取序列化数据 */
serialize() {
return this.bits;
}
/** bit 数组大小 */
getBitSize() {
return this.bits.byteLength * 8;
}
/** 已插入 key 数量 */
getInsertedCount() {
return this._inserted;
}
/** hash 函数数量 */
getHashCount() {
return this.numHashes;
}
// -----------------------------------------------------------------------
// 哈希
// -----------------------------------------------------------------------
getHashes(key) {
const bits = this.bits.byteLength * 8;
const h1 = this.fnv1a(key);
const h2 = this.murmurSimple(key);
const hashes = [];
for (let i = 0; i < this.numHashes; i++) {
// Kirsch-Mitzenmacher: h_i = h1 + i * h2
const h = Math.abs((h1 + i * h2) % bits);
hashes.push(h);
}
return hashes;
}
/** FNV-1a 哈希 */
fnv1a(str) {
let hash = 0x811c9dc5;
for (let i = 0; i < str.length; i++) {
hash ^= str.charCodeAt(i);
hash = (hash * 0x01000193) >>> 0;
}
return hash;
}
/** 简化的 Murmur-like 哈希 */
murmurSimple(str) {
let hash = 0;
for (let i = 0; i < str.length; i++) {
const ch = str.charCodeAt(i);
hash = ((hash << 5) - hash + ch) | 0;
hash = (hash ^ (hash >>> 16)) >>> 0;
}
return Math.abs(hash);
}
}
/**
* AriaEngine SSTable Builder — 构建有序字符串表
* @module engine/aria/index/sstable_builder
*
* 将排序后的 key-value 数据写入 SSTable 格式。
*
* SSTable 文件布局:
* ┌──────────────────────────────────────────────┐
* │ Data Block 0 │
* │ Data Block 1 │
* │ ... │
* │ Index Block (block offset → key range) │
* │ Bloom Filter │
* │ Footer (32 bytes) │
* │ - index_offset (u32) │
* │ - index_size (u32) │
* │ - bloom_offset (u32) │
* │ - bloom_size (u32) │
* │ - bloom_hash_count (u32) │
* │ - entry_count (u32) │
* │ - magic_number (u32, 0x53535442 ="SSTB")│
* │ - checksum (u32) │
* └──────────────────────────────────────────────┘
*/
const SSTABLE_MAGIC$1 = 0x53535442; // "SSTB"
const SSTABLE_FOOTER_SIZE = 32;
// ---------------------------------------------------------------------------
// SSTableBuilder
// ---------------------------------------------------------------------------
class SSTableBuilder {
constructor(blockSizeLimit = 4096) {
this.entries = [];
this.currentBlock = [];
this.currentBlockStartKey = '';
this.blockSizeLimit = blockSizeLimit;
}
/** 添加一个 key-value 条目(必须按键排序添加) */
add(key, value) {
if (this.currentBlock.length === 0) {
this.currentBlockStartKey = key;
}
this.currentBlock.push([key, value]);
this.entries.push([key, value]);
// 如果当前 Block 达到大小限制,切割
const estimated = this.estimateBlockSize();
if (estimated >= this.blockSizeLimit && this.currentBlock.length > 1) ;
}
/**
* 构建 SSTable 文件的二进制数据。
* 返回 { data: Uint8Array, indexEntries: IndexEntry[], bloomFilter: BloomFilter }
*/
build() {
const blocks = this.splitIntoBlocks();
const bloomFilter = new BloomFilter(this.entries.length);
// 预计算总大小
let totalSize = 0;
const blockOffsets = [];
for (const block of blocks) {
blockOffsets.push(totalSize);
const blockSize = this.computeBlockSize(block);
totalSize += blockSize;
}
// 索引块
const indexEntries = [];
for (let i = 0; i < blocks.length; i++) {
const block = blocks[i];
const lastKey = block[block.length - 1][0];
const blockSize = this.computeBlockSize(block);
indexEntries.push({
key: lastKey,
blockOffset: blockOffsets[i],
blockSize,
});
}
const indexBlockSize = this.estimateIndexBlockSize(indexEntries);
// 序列化 bloom filter 以获取其大小
const bloomData = bloomFilter.serialize();
const bloomSize = bloomData.byteLength;
// 写入到 buffer(包含 bloom block
const finalSize = totalSize + indexBlockSize + bloomSize + SSTABLE_FOOTER_SIZE;
const buf = new ArrayBuffer(finalSize);
const view = new DataView(buf);
let offset = 0;
// ---- Data Blocks ----
for (const block of blocks) {
offset = this.writeDataBlock(view, offset, block, bloomFilter);
}
// ---- Index Block ----
const indexOffset = offset;
offset = this.writeIndexBlock(view, offset, indexEntries);
// ---- Bloom Filter Block ----
const bloomOffset = offset;
new Uint8Array(view.buffer).set(bloomData, offset);
offset += bloomSize;
// ---- Footer ----
const footerOffset = offset;
view.setUint32(footerOffset, indexOffset, false); // index_offset
view.setUint32(footerOffset + 4, indexBlockSize, false); // index_size
view.setUint32(footerOffset + 8, bloomOffset, false); // bloom_offset
view.setUint32(footerOffset + 12, bloomSize, false); // bloom_size
view.setUint32(footerOffset + 16, bloomFilter.getHashCount(), false);
view.setUint32(footerOffset + 20, this.entries.length, false);
view.setUint32(footerOffset + 24, SSTABLE_MAGIC$1, false);
view.setUint32(footerOffset + 28, 0, false); // checksum (simplified: 0)
return {
sstableData: new Uint8Array(buf),
indexEntries,
};
}
/** 获取条目数 */
getEntryCount() {
return this.entries.length;
}
// -----------------------------------------------------------------------
// 内部
// -----------------------------------------------------------------------
splitIntoBlocks() {
const blocks = [];
let current = [];
for (const entry of this.entries) {
current.push(entry);
if (this.estimateBlockSizeFromEntries(current) >= this.blockSizeLimit && current.length > 1) {
blocks.push(current.slice(0, -1));
current = [entry];
}
}
if (current.length > 0)
blocks.push(current);
return blocks;
}
estimateBlockSize() {
return this.estimateBlockSizeFromEntries(this.currentBlock);
}
estimateBlockSizeFromEntries(entries) {
let size = 0;
for (const [key, value] of entries) {
size += 4 + key.length + JSON.stringify(value).length;
}
return size;
}
computeBlockSize(block) {
// entryCount (u32) + 每对: keyLen(u16) + key + valueLen(u16) + value json
let size = 4;
for (const [key, value] of block) {
const json = JSON.stringify(value);
size += 2 + key.length + 2 + json.length;
}
return size;
}
writeDataBlock(view, offset, block, bloomFilter) {
// entry count
view.setUint32(offset, block.length, false);
offset += 4;
for (const [key, value] of block) {
const encoder = new TextEncoder();
const keyBytes = encoder.encode(key);
const valueBytes = encoder.encode(JSON.stringify(value));
// key length
view.setUint16(offset, keyBytes.length, false);
offset += 2;
// key
new Uint8Array(view.buffer).set(keyBytes, offset);
offset += keyBytes.length;
// value length
view.setUint16(offset, valueBytes.length, false);
offset += 2;
// value
new Uint8Array(view.buffer).set(valueBytes, offset);
offset += valueBytes.length;
// 插入 bloom filter
bloomFilter.insert(key);
}
return offset;
}
estimateIndexBlockSize(entries) {
// entryCount(u32) + each: keyLen(u16)+key+blockOffset(u32)+blockSize(u32)
let size = 4;
for (const entry of entries) {
size += 2 + entry.key.length + 8;
}
return size;
}
writeIndexBlock(view, offset, entries) {
view.setUint32(offset, entries.length, false);
offset += 4;
for (const entry of entries) {
const encoder = new TextEncoder();
const keyBytes = encoder.encode(entry.key);
view.setUint16(offset, keyBytes.length, false);
offset += 2;
new Uint8Array(view.buffer).set(keyBytes, offset);
offset += keyBytes.length;
view.setUint32(offset, entry.blockOffset, false);
offset += 4;
view.setUint32(offset, entry.blockSize, false);
offset += 4;
}
return offset;
}
}
/**
* AriaEngine SSTable Reader — 从 SSTable 二进制数据中读取
* @module engine/aria/index/sstable
*/
const SSTABLE_MAGIC = 0x53535442; // "SSTB"
// ---------------------------------------------------------------------------
// SSTableReader
// ---------------------------------------------------------------------------
class SSTableReader {
constructor(data, meta) {
this.indexEntries = [];
this.entryCount = 0;
this.bloomFilter = null;
this.data = data;
this.view = new DataView(data.buffer, data.byteOffset, data.byteLength);
this.meta = meta;
this.parseFooter();
}
// -----------------------------------------------------------------------
// 查询
// -----------------------------------------------------------------------
/** 精确查找 key */
get(targetKey) {
// Bloom Filter 快速否定
if (this.bloomFilter && !this.bloomFilter.mayContain(targetKey))
return null;
const blockIdx = this.locateBlock(targetKey);
if (blockIdx < 0)
return null;
const entry = this.indexEntries[blockIdx];
const blockData = new Uint8Array(this.data.buffer, this.data.byteOffset + entry.blockOffset, entry.blockSize);
const blockView = new DataView(blockData.buffer, blockData.byteOffset, blockData.byteLength);
const entryCount = blockView.getUint32(0, false);
let offset = 4;
// 顺序扫描 block 内的条目(生产中应二分查找)
for (let i = 0; i < entryCount; i++) {
const keyLen = blockView.getUint16(offset, false);
offset += 2;
const key = new TextDecoder().decode(blockData.slice(offset, offset + keyLen));
offset += keyLen;
const valLen = blockView.getUint16(offset, false);
offset += 2;
const valBytes = blockData.slice(offset, offset + valLen);
offset += valLen;
if (key === targetKey) {
try {
return JSON.parse(new TextDecoder().decode(valBytes));
}
catch {
return null;
}
}
}
return null;
}
/** 范围扫描 */
rangeScan(startKey, endKey, callback) {
if (this.indexEntries.length === 0)
return;
const startBlockIdx = Math.max(0, this.locateBlockGE(startKey));
const endBlockIdx = Math.min(this.indexEntries.length - 1, this.locateBlockLE(endKey));
if (startBlockIdx < 0 || endBlockIdx < 0 || startBlockIdx > endBlockIdx)
return;
for (let bi = startBlockIdx; bi <= endBlockIdx && bi >= 0; bi++) {
const entry = this.indexEntries[bi];
const blockData = new Uint8Array(this.data.buffer, this.data.byteOffset + entry.blockOffset, entry.blockSize);
const blockView = new DataView(blockData.buffer, blockData.byteOffset, blockData.byteLength);
const blockEntryCount = blockView.getUint32(0, false);
let offset = 4;
for (let i = 0; i < blockEntryCount; i++) {
const keyLen = blockView.getUint16(offset, false);
offset += 2;
const key = new TextDecoder().decode(blockData.slice(offset, offset + keyLen));
offset += keyLen;
const valLen = blockView.getUint16(offset, false);
offset += 2;
const valBytes = blockData.slice(offset, offset + valLen);
offset += valLen;
if (key >= startKey && key <= endKey) {
try {
const value = JSON.parse(new TextDecoder().decode(valBytes));
callback(key, value);
}
catch {
// skip corrupted entry
}
}
}
}
}
/** 扫描所有条目 */
scanAll(callback) {
for (const entry of this.indexEntries) {
const blockData = new Uint8Array(this.data.buffer, this.data.byteOffset + entry.blockOffset, entry.blockSize);
const blockView = new DataView(blockData.buffer, blockData.byteOffset, blockData.byteLength);
const blockEntryCount = blockView.getUint32(0, false);
let offset = 4;
for (let i = 0; i < blockEntryCount; i++) {
const keyLen = blockView.getUint16(offset, false);
offset += 2;
const key = new TextDecoder().decode(blockData.slice(offset, offset + keyLen));
offset += keyLen;
const valLen = blockView.getUint16(offset, false);
offset += 2;
const valBytes = blockData.slice(offset, offset + valLen);
offset += valLen;
try {
const value = JSON.parse(new TextDecoder().decode(valBytes));
callback(key, value);
}
catch {
// skip corrupted entry
}
}
}
}
/** 获取元数据 */
getMeta() {
return this.meta;
}
/** 获取索引条目数 */
getIndexBlockCount() {
return this.indexEntries.length;
}
// -----------------------------------------------------------------------
// 内部
// -----------------------------------------------------------------------
parseFooter() {
if (this.data.byteLength < 32) {
throw new Error('SSTable too small: missing footer');
}
const footerOffset = this.data.byteLength - 32;
// 验证魔数
const magic = this.view.getUint32(footerOffset + 24, false);
if (magic !== SSTABLE_MAGIC) {
throw new Error(`Invalid SSTable magic: expected ${SSTABLE_MAGIC}, got ${magic}`);
}
const indexOffset = this.view.getUint32(footerOffset, false);
const indexSize = this.view.getUint32(footerOffset + 4, false);
const bloomOffset = this.view.getUint32(footerOffset + 8, false);
const bloomSize = this.view.getUint32(footerOffset + 12, false);
const bloomHashCount = this.view.getUint32(footerOffset + 16, false);
this.entryCount = this.view.getUint32(footerOffset + 20, false);
// 解析索引块
this.parseIndexBlock(indexOffset, indexSize);
// 加载 Bloom Filter
if (bloomOffset > 0 && bloomSize > 0 && bloomOffset + bloomSize <= this.data.byteLength) {
try {
const bloomBytes = this.data.slice(bloomOffset, bloomOffset + bloomSize);
this.bloomFilter = BloomFilter.fromData(bloomBytes, bloomHashCount || 10);
}
catch {
// 损坏的 bloom filter 不影响读取(仅跳过快速否定优化)
}
}
}
parseIndexBlock(offset, _size) {
const entryCount = this.view.getUint32(offset, false);
offset += 4;
for (let i = 0; i < entryCount; i++) {
const keyLen = this.view.getUint16(offset, false);
offset += 2;
const key = new TextDecoder().decode(this.data.slice(offset, offset + keyLen));
offset += keyLen;
const blockOffset = this.view.getUint32(offset, false);
offset += 4;
const blockSize = this.view.getUint32(offset, false);
offset += 4;
this.indexEntries.push({ key, blockOffset, blockSize });
}
}
/** 二分查找某 key 所在的 block 索引 */
locateBlock(key) {
let lo = 0;
let hi = this.indexEntries.length - 1;
while (lo <= hi) {
const mid = Math.floor((lo + hi) / 2);
const entry = this.indexEntries[mid];
if (key <= entry.key) {
// 检查是否在此 block 范围内
const firstKey = mid === 0 ? '' : this.indexEntries[mid - 1].key;
if (key > firstKey)
return mid;
hi = mid - 1;
}
else {
lo = mid + 1;
}
}
return -1;
}
locateBlockGE(key) {
let lo = 0, hi = this.indexEntries.length;
while (lo < hi) {
const mid = (lo + hi) >> 1;
if (this.indexEntries[mid].key < key)
lo = mid + 1;
else
hi = mid;
}
return lo < this.indexEntries.length ? lo : this.indexEntries.length - 1;
}
locateBlockLE(key) {
let lo = 0, hi = this.indexEntries.length;
while (lo < hi) {
const mid = (lo + hi) >> 1;
if (this.indexEntries[mid].key <= key)
lo = mid + 1;
else
hi = mid;
}
return lo > 0 ? lo - 1 : 0;
}
}
/**
* AriaEngine Merge Iterator — 多路归并迭代器
* @module engine/aria/index/merge_iterator
*
* 对多个有序 SSTable 或 MemTable 的结果进行归并去重(保留最新值)。
*/
/** 数组数据源的迭代器 */
class ArrayEntrySource {
constructor(entries) {
this.index = 0;
this.entries = entries;
}
next() {
if (this.index >= this.entries.length)
return null;
return this.entries[this.index++];
}
reset() {
this.index = 0;
}
}
/** 最小堆 */
class MinHeap {
constructor() {
this.heap = [];
}
push(node) {
this.heap.push(node);
this.bubbleUp(this.heap.length - 1);
}
pop() {
if (this.heap.length === 0)
return null;
if (this.heap.length === 1)
return this.heap.pop();
const result = this.heap[0];
this.heap[0] = this.heap.pop();
this.bubbleDown(0);
return result;
}
peek() {
return this.heap.length > 0 ? this.heap[0] : null;
}
get size() {
return this.heap.length;
}
bubbleUp(idx) {
while (idx > 0) {
const parent = Math.floor((idx - 1) / 2);
if (this.heap[idx].key >= this.heap[parent].key)
break;
[this.heap[idx], this.heap[parent]] = [this.heap[parent], this.heap[idx]];
idx = parent;
}
}
bubbleDown(idx) {
const n = this.heap.length;
while (true) {
let smallest = idx;
const left = 2 * idx + 1;
const right = 2 * idx + 2;
if (left < n && this.heap[left].key < this.heap[smallest].key)
smallest = left;
if (right < n && this.heap[right].key < this.heap[smallest].key)
smallest = right;
if (smallest === idx)
break;
[this.heap[idx], this.heap[smallest]] = [this.heap[smallest], this.heap[idx]];
idx = smallest;
}
}
}
// ---------------------------------------------------------------------------
// MergeIterator
// ---------------------------------------------------------------------------
/**
* 对多个有序数据源进行归并,重复 key 保留最新(后出现的)。
* 数据源按新鲜度排序:越新的数据源在下标越小(如 MemTable 在 SSTable 之前)。
*/
class MergeIterator {
constructor() {
this.sources = [];
this.heap = new MinHeap();
}
/** 添加数据源 */
addSource(source) {
this.sources.push(source);
this.seedFromSource(this.sources.length - 1);
}
/** 获取下一个归并后的条目 */
next() {
if (this.heap.size === 0)
return null;
const node = this.heap.pop();
const key = node.key;
const value = node.value;
// 刷新此来源的下一个值
this.seedFromSource(node.sourceIndex);
// 跳过重复 key:取最新的(堆顶的即是最新的,因为来源下标越小越新)
while (this.heap.peek() && this.heap.peek().key === key) {
const dup = this.heap.pop();
this.seedFromSource(dup.sourceIndex);
}
return [key, value];
}
/** 耗尽管道,返回所有归并结果 */
drain() {
const result = [];
let entry = this.next();
while (entry) {
result.push(entry);
entry = this.next();
}
return result;
}
seedFromSource(sourceIndex) {
const entry = this.sources[sourceIndex].next();
if (entry) {
this.heap.push({
key: entry[0],
value: entry[1],
sourceIndex,
});
}
}
}
/**
* AriaEngine LSM-Tree — 日志结构合并树
* @module engine/aria/index/lsm
*
* 管理 MemTable + 多级 SSTable 的读写和 Compaction。
*
* v0.2.1: 完整持久化 — SSTable 元数据和数据均存入存储后端,
* 启动时自动扫描并加载所有 SSTable。
*/
// ---------------------------------------------------------------------------
// LSM
// ---------------------------------------------------------------------------
class LSM {
constructor(config) {
this.immutableMemtable = null;
this.levels = [];
this.sstableCache = new Map();
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;
this.sstableStore = config.sstableStore;
for (let i = 0; i < MAX_LSM_LEVELS; i++) {
this.levels.push([]);
}
}
// =======================================================================
// 初始化:从存储后端加载 SSTable 元数据
// =======================================================================
async init() {
if (this.initialized)
return;
const metas = await this.sstableStore.listMeta();
// 按层级分组
for (const meta of metas) {
if (meta.level >= 0 && meta.level < MAX_LSM_LEVELS) {
this.levels[meta.level].push(meta);
}
}
// 各层级按 minKey 排序(方便后续范围查询剪枝)
for (let i = 0; i < MAX_LSM_LEVELS; i++) {
this.levels[i].sort((a, b) => (a.minKey < b.minKey ? -1 : a.minKey > b.minKey ? 1 : 0));
}
// 恢复 nextSSTableId
if (metas.length > 0) {
this.nextSSTableId = Math.max(...metas.map((m) => m.id)) + 1;
}
// 预加载所有 SSTable 数据到缓存(避免后续 cache miss 返回 null
for (const meta of metas) {
try {
await this.preloadSSTable(meta.id);
}
catch {
// 单个文件加载失败不影响整体启动
}
}
this.initialized = true;
}
// =======================================================================
// 写入
// =======================================================================
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()) {
this.freezeMemtable();
}
}
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();
}
this.immutableMemtable = this.memtable;
this.memtable = new MemTable(this.memtable.getEstimatedSize());
}
/** 同步等待 Immutable MemTable 刷盘完成 */
flushImmutableSync() {
if (!this.immutableMemtable)
return;
const entries = this.immutableMemtable.getAllEntries();
if (entries.length === 0) {
this.immutableMemtable = null;
return;
}
const id = this.nextSSTableId++;
const builder = new SSTableBuilder(this.blockSize);
for (const [key, value] of entries) {
builder.add(key, value);
}
const { sstableData, indexEntries } = builder.build();
const meta = {
id,
level: 0,
minKey: entries[0][0],
maxKey: entries[entries.length - 1][0],
blockCount: indexEntries.length,
totalSize: sstableData.byteLength,
bloomData: null,
};
// 缓存
this.sstableCache.set(id, sstableData);
// 持久化:先存数据,再存元数据
this.sstableStore.save(id, sstableData).catch(() => { });
this.sstableStore.saveMeta(meta).catch(() => { });
this.levels[0].push(meta);
this.immutableMemtable = null;
// 异步触发 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);
}
// =======================================================================
// 读取
// =======================================================================
get(key) {
// 1. 活跃 MemTable
let result = this.memtable.get(key);
if (result !== null)
return this.unwrapTombstone(result);
// 2. 不可变 MemTable
if (this.immutableMemtable) {
result = this.immutableMemtable.get(key);
if (result !== null)
return this.unwrapTombstone(result);
}
// 3. SSTable(从 Level 0 到 Level N-1
for (let level = 0; level < MAX_LSM_LEVELS; level++) {
for (const meta of this.levels[level]) {
if (key < meta.minKey || key > meta.maxKey)
continue;
const reader = this.loadSSTableReader(meta);
if (!reader)
continue;
const found = reader.get(key);
if (found !== null)
return this.unwrapTombstone(found);
}
}
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();
mergeIter.addSource(new ArrayEntrySource(this.memtable.rangeScan(startKey, endKey)));
if (this.immutableMemtable) {
mergeIter.addSource(new ArrayEntrySource(this.immutableMemtable.rangeScan(startKey, endKey)));
}
for (let level = 0; level < MAX_LSM_LEVELS; level++) {
for (const meta of this.levels[level]) {
if (endKey < meta.minKey || startKey > meta.maxKey)
continue;
const reader = this.loadSSTableReader(meta);
if (!reader)
continue;
reader.rangeScan(startKey, endKey, (k, v) => {
mergeIter.addSource(new ArrayEntrySource([[k, v]]));
});
}
}
const merged = mergeIter.drain();
for (const [k, v] of merged) {
if (!v.__tombstone) {
callback(k, v);
}
}
}
getAllEntries() {
const result = new Map();
// 从最旧层级开始聚合
for (let level = MAX_LSM_LEVELS - 1; level >= 0; level--) {
for (const meta of this.levels[level]) {
const reader = this.loadSSTableReader(meta);
if (!reader)
continue;
reader.scanAll((k, v) => result.set(k, v));
}
}
// MemTable 覆盖(最新)
for (const [k, v] of this.memtable.getAllEntries()) {
result.set(k, v);
}
if (this.immutableMemtable) {
for (const [k, v] of this.immutableMemtable.getAllEntries()) {
result.set(k, v);
}
}
return Array.from(result.entries()).filter(([, v]) => !v.__tombstone);
}
// =======================================================================
// Compaction
// =======================================================================
/** 同步执行 Compactionpublic,供 VACUUM 等外部调用) */
compactLevel(level) {
this.compactLevelSync(level);
}
/** 同步执行 Compaction(简化版,内部实现) */
compactLevelSync(level) {
if (level >= MAX_LSM_LEVELS - 1)
return;
if (this.levels[level].length < 4)
return;
const sstables = this.levels[level].splice(0, this.levels[level].length);
const mergeIter = new MergeIterator();
for (const meta of sstables) {
const reader = this.loadSSTableReader(meta);
if (!reader)
continue;
const entries = [];
reader.scanAll((k, v) => entries.push([k, v]));
mergeIter.addSource(new ArrayEntrySource(entries));
}
const merged = mergeIter.drain();
if (merged.length === 0)
return;
const id = this.nextSSTableId++;
const builder = new SSTableBuilder(this.blockSize);
for (const [key, value] of merged) {
builder.add(key, value);
}
const { sstableData, indexEntries } = builder.build();
const meta = {
id,
level: level + 1,
minKey: merged[0][0],
maxKey: merged[merged.length - 1][0],
blockCount: indexEntries.length,
totalSize: sstableData.byteLength,
bloomData: null,
};
this.sstableCache.set(id, sstableData);
this.sstableStore.save(id, sstableData).catch(() => { });
this.sstableStore.saveMeta(meta).catch(() => { });
this.levels[level + 1].push(meta);
// 删除旧 SSTable
for (const old of sstables) {
this.sstableCache.delete(old.id);
this.sstableStore.delete(old.id).catch(() => { });
this.sstableStore.deleteMeta(old.id).catch(() => { });
}
}
async flush() {
if (this.immutableMemtable) {
this.flushImmutableSync();
}
if (this.memtable.getEntryCount() > 0) {
this.freezeMemtable();
this.flushImmutableSync();
}
// 确保所有微任务完成(不使用 setTimeout,避免额外 timer 阻止进程退出)
await Promise.resolve();
}
async clear() {
this.memtable.clear();
this.immutableMemtable = null;
for (const level of this.levels) {
for (const meta of level) {
this.sstableCache.delete(meta.id);
this.sstableStore.delete(meta.id).catch(() => { });
this.sstableStore.deleteMeta(meta.id).catch(() => { });
}
}
this.levels = [];
for (let i = 0; i < MAX_LSM_LEVELS; i++) {
this.levels.push([]);
}
this.sstableCache.clear();
this.nextSSTableId = 1;
}
getStats() {
return {
memtableSize: this.memtable.getEntryCount(),
sstableCount: this.levels.reduce((sum, l) => sum + l.length, 0),
levelCounts: this.levels.map((l) => l.length),
};
}
isInitialized() {
return this.initialized;
}
// =======================================================================
// 内部
// =======================================================================
unwrapTombstone(value) {
if (!value)
return null;
if (value.__tombstone)
return null;
return value;
}
/** 尝试从缓存或存储加载 SSTable,返回 Reader */
loadSSTableReader(meta) {
// 先检查缓存
let data = this.sstableCache.get(meta.id);
if (!data) {
return null; // 异步加载已不可用,返回 null(调用方处理)
}
try {
return new SSTableReader(data, meta);
}
catch {
return null;
}
}
/** 预加载 SSTable 到缓存(供外部在需要时调用) */
async preloadSSTable(id) {
if (this.sstableCache.has(id))
return;
const data = await this.sstableStore.load(id);
if (data) {
this.sstableCache.set(id, data);
}
}
}
/**
* AriaEngine WAL — Write-Ahead Log
* @module engine/aria/wal/log
*
* 崩溃恢复前的写操作持久化日志。
*
* WAL 文件格式:
* ┌──────────┬──────────────┬──────────┐
* │ Record 1│ Record 2 │ ... │
* │ 4B LSN │ │ │
* │ 1B type │ │ │
* │ 4B txnId│ │ │
* │ 2B tblLen│ │ │
* │ N table│ │ │
* │ 2B keyLen│ │ │
* │ N key │ │ │
* │ 4B jsonLen│ │ │
* │ N json │ │ │
* │ 4B CRC │ │ │
* └──────────┴──────────────┴──────────┘
*/
// ---------------------------------------------------------------------------
// WAL
// ---------------------------------------------------------------------------
class WAL {
constructor(store, enabled = true, syncMode = 'batch') {
this.lsn = 0;
this.buffer = [];
this.store = store;
this.enabled = enabled;
this.syncMode = syncMode;
}
// =======================================================================
// 写入
// =======================================================================
/** 追加一条 WAL 记录(full 模式同步等待写入完成) */
async append(record) {
if (!this.enabled)
return;
this.lsn++;
const fullRecord = {
...record,
lsn: this.lsn,
checksum: 0, // 稍后计算
};
const bytes = this.encodeRecord(fullRecord);
if (this.syncMode === 'full') {
try {
await this.store.append(bytes);
}
catch {
// eslint-disable-next-line no-console
console.warn('[AriaEngine WAL] Failed to append record');
}
}
else if (this.syncMode === 'batch') {
this.buffer.push(bytes);
}
// 'none' mode: 不写 WAL
}
/** 批量刷新缓冲的 WAL 记录 */
async flush() {
if (!this.enabled || this.buffer.length === 0)
return;
const totalLen = this.buffer.reduce((sum, b) => sum + b.byteLength, 0);
const combined = new Uint8Array(totalLen);
let offset = 0;
for (const buf of this.buffer) {
combined.set(buf, offset);
offset += buf.byteLength;
}
await this.store.append(combined);
this.buffer = [];
}
// =======================================================================
// 恢复
// =======================================================================
/** 从 WAL 恢复未提交的事务数据 */
async recover(applyRecord) {
if (!this.enabled)
return 0;
const exists = await this.store.exists();
if (!exists)
return 0;
const data = await this.store.readAll();
if (data.byteLength === 0)
return 0;
const records = this.decodeAllRecords(data);
for (const record of records) {
applyRecord(record);
}
this.lsn = records.length > 0 ? records[records.length - 1].lsn : 0;
return records.length;
}
// =======================================================================
// Checkpoint
// =======================================================================
/** Checkpoint 后清空 WAL */
async checkpoint() {
if (!this.enabled)
return;
await this.flush();
await this.store.truncate();
this.lsn = 0;
}
// =======================================================================
// 统计
// =======================================================================
isEnabled() {
return this.enabled;
}
getLSN() {
return this.lsn;
}
getBufferedCount() {
return this.buffer.length;
}
// -----------------------------------------------------------------------
// 编解码
// -----------------------------------------------------------------------
encodeRecord(record) {
const encoder = new TextEncoder();
const tableBytes = encoder.encode(record.tableName);
const keyBytes = encoder.encode(record.key);
const jsonStr = record.data ? JSON.stringify(record.data) : '';
const jsonBytes = encoder.encode(jsonStr);
const size = 4 + // LSN
1 + // type
4 + // txnId
2 + tableBytes.length + // table
2 + keyBytes.length + // key
4 + jsonBytes.length + // json
4; // CRC
const buf = new ArrayBuffer(size);
const view = new DataView(buf);
let offset = 0;
view.setUint32(offset, record.lsn, false);
offset += 4;
view.setUint8(offset, record.type);
offset += 1;
view.setUint32(offset, record.txnId, false);
offset += 4;
view.setUint16(offset, tableBytes.length, false);
offset += 2;
new Uint8Array(buf).set(tableBytes, offset);
offset += tableBytes.length;
view.setUint16(offset, keyBytes.length, false);
offset += 2;
new Uint8Array(buf).set(keyBytes, offset);
offset += keyBytes.length;
view.setUint32(offset, jsonBytes.length, false);
offset += 4;
new Uint8Array(buf).set(jsonBytes, offset);
offset += jsonBytes.length;
// 简单 CRC
let crc = 0;
const u8 = new Uint8Array(buf, 0, offset);
for (let i = 0; i < u8.length; i++) {
crc = ((crc << 5) - crc + u8[i]) | 0;
}
view.setUint32(offset, crc >>> 0, false);
return new Uint8Array(buf);
}
decodeAllRecords(data) {
const records = [];
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
let offset = 0;
while (offset + 15 <= data.byteLength) {
try {
const recordStart = offset;
const lsn = view.getUint32(offset, false);
offset += 4;
const type = view.getUint8(offset);
offset += 1;
const txnId = view.getUint32(offset, false);
offset += 4;
const tableLen = view.getUint16(offset, false);
offset += 2;
if (offset + tableLen > data.byteLength)
break;
const tableName = new TextDecoder().decode(data.slice(offset, offset + tableLen));
offset += tableLen;
const keyLen = view.getUint16(offset, false);
offset += 2;
if (offset + keyLen > data.byteLength)
break;
const key = new TextDecoder().decode(data.slice(offset, offset + keyLen));
offset += keyLen;
const jsonLen = view.getUint32(offset, false);
offset += 4;
if (offset + jsonLen > data.byteLength)
break;
let recordData;
if (jsonLen > 0) {
const json = new TextDecoder().decode(data.slice(offset, offset + jsonLen));
try {
recordData = JSON.parse(json);
}
catch { /* ok */ }
}
offset += jsonLen;
// 验证 CRC(跨记录数据计算,不含 CRC 自身)
const storedCrc = view.getUint32(offset, false);
offset += 4;
const recordBytes = data.slice(recordStart, offset - 4);
let computedCrc = 0;
for (let i = 0; i < recordBytes.length; i++) {
computedCrc = ((computedCrc << 5) - computedCrc + recordBytes[i]) | 0;
}
if ((computedCrc >>> 0) !== storedCrc) {
// CRC 不匹配,跳过此损坏记录
console.warn(`[AriaEngine WAL] CRC mismatch at record LSN=${lsn}, skipping`);
continue;
}
records.push({
lsn,
type,
txnId,
tableName,
key,
data: recordData,
checksum: storedCrc,
});
}
catch {
break;
}
}
return records;
}
}
/**
* AriaEngine Checkpoint — 检查点机制
* @module engine/aria/wal/checkpoint
*/
// ---------------------------------------------------------------------------
// CheckpointManager
// ---------------------------------------------------------------------------
class CheckpointManager {
constructor(lsm, wal, flushable = null, interval = 1000, walSizeThreshold = 16 * 1024 * 1024) {
this.opCount = 0;
this.lsm = lsm;
this.wal = wal;
this.flushable = flushable;
this.interval = interval;
this.walSizeThreshold = walSizeThreshold;
}
async tick() {
this.opCount++;
// 检查操作计数或 WAL 大小是否超阈值
if (this.opCount >= this.interval || this.getWALEstimatedSize() >= this.walSizeThreshold) {
await this.checkpoint();
}
}
/** 估算 WAL 大小 */
getWALEstimatedSize() {
const count = typeof this.wal.getBufferedCount === 'function' ? this.wal.getBufferedCount() : 0;
return count * 200;
}
async checkpoint() {
await this.lsm.flush();
if (this.flushable) {
await this.flushable.flushAll();
}
await this.wal.checkpoint();
this.opCount = 0;
}
async forceCheckpoint() {
await this.checkpoint();
}
setInterval(ops) {
this.interval = ops;
}
getOpCount() {
return this.opCount;
}
}
/**
* AriaEngine Storage Backend — 存储后端抽象层
* @module engine/aria/store/backend
*
* 封装底层浏览器存储 APIIndexedDB / OPFS / Memory 回退),
* 供 Buffer Pool 的 PageIO 和 WAL 的 WALStore 使用。
*/
// =======================================================================
// IndexedDB Backend
// =======================================================================
class IndexedDBBackend {
constructor() {
this.db = null;
this.dbName = '';
this.storeName = 'data';
}
async open(name) {
this.dbName = `aria-${name}`;
return new Promise((resolve, reject) => {
const request = indexedDB.open(this.dbName, 1);
request.onupgradeneeded = () => {
const db = request.result;
if (!db.objectStoreNames.contains(this.storeName)) {
db.createObjectStore(this.storeName);
}
};
request.onsuccess = () => {
this.db = request.result;
resolve();
};
request.onerror = () => reject(new DatabaseError('Failed to open AriaEngine IndexedDB', 'ARIA_IDB_OPEN_ERROR'));
});
}
async close() {
if (this.db) {
this.db.close();
this.db = null;
}
}
isOpen() {
return this.db !== null;
}
async read(key) {
const db = this.ensureDB();
return new Promise((resolve, reject) => {
const tx = db.transaction(this.storeName, 'readonly');
const req = tx.objectStore(this.storeName).get(key);
req.onsuccess = () => resolve(req.result ?? null);
req.onerror = () => reject(new DatabaseError('Failed to read from AriaEngine store', 'ARIA_READ_ERROR'));
});
}
async write(key, data) {
const db = this.ensureDB();
return new Promise((resolve, reject) => {
const tx = db.transaction(this.storeName, 'readwrite');
tx.objectStore(this.storeName).put(data, key);
tx.oncomplete = () => resolve();
tx.onerror = () => reject(new DatabaseError('Failed to write to AriaEngine store', 'ARIA_WRITE_ERROR'));
});
}
async delete(key) {
const db = this.ensureDB();
return new Promise((resolve, reject) => {
const tx = db.transaction(this.storeName, 'readwrite');
tx.objectStore(this.storeName).delete(key);
tx.oncomplete = () => resolve();
tx.onerror = () => reject(new DatabaseError('Failed to delete from AriaEngine store', 'ARIA_DELETE_ERROR'));
});
}
async listKeys() {
const db = this.ensureDB();
return new Promise((resolve, reject) => {
const tx = db.transaction(this.storeName, 'readonly');
const req = tx.objectStore(this.storeName).getAllKeys();
req.onsuccess = () => resolve((req.result ?? []));
req.onerror = () => reject(new DatabaseError('Failed to list keys', 'ARIA_LIST_ERROR'));
});
}
async exists(key) {
const result = await this.read(key);
return result !== null;
}
async clear() {
const db = this.ensureDB();
return new Promise((resolve, reject) => {
const tx = db.transaction(this.storeName, 'readwrite');
tx.objectStore(this.storeName).clear();
tx.oncomplete = () => resolve();
tx.onerror = () => reject(new DatabaseError('Failed to clear AriaEngine store', 'ARIA_CLEAR_ERROR'));
});
}
ensureDB() {
if (!this.db)
throw new DatabaseError('AriaEngine storage not opened', 'ARIA_DB_NOT_OPEN');
return this.db;
}
}
// =======================================================================
// Memory Backend(回退 / 测试用)
// =======================================================================
class MemoryBackend {
constructor() {
this.store = new Map();
this.opened = false;
}
async open(_name) {
this.opened = true;
}
async close() {
this.store.clear();
this.opened = false;
}
isOpen() {
return this.opened;
}
async read(key) {
return this.store.get(key) ?? null;
}
async write(key, data) {
this.store.set(key, data);
}
async delete(key) {
this.store.delete(key);
}
async listKeys() {
return Array.from(this.store.keys());
}
async exists(key) {
return this.store.has(key);
}
async clear() {
this.store.clear();
}
}
class OPFSBackend {
constructor() {
this.root = null;
this.dbDir = null;
this.dbName = '';
this.writeQueue = Promise.resolve();
}
async open(name) {
this.dbName = name;
this.root = await navigator.storage.getDirectory();
this.dbDir = await this.root.getDirectoryHandle(name, { create: true });
}
async close() {
this.dbDir = null;
this.root = null;
}
isOpen() {
return this.dbDir !== null;
}
async read(key) {
if (!this.dbDir)
return null;
try {
const fh = await this.dbDir.getFileHandle(key);
const file = await fh.getFile();
return await file.arrayBuffer();
}
catch {
return null;
}
}
async write(key, data) {
if (!this.dbDir)
return;
this.writeQueue = this.writeQueue.then(async () => {
const fh = await this.dbDir.getFileHandle(key, { create: true });
const writable = await fh.createWritable();
await writable.write(data);
await writable.close();
});
return this.writeQueue;
}
async delete(key) {
if (!this.dbDir)
return;
this.writeQueue = this.writeQueue.then(async () => {
try {
await this.dbDir.removeEntry(key);
}
catch { /* ignore */ }
});
return this.writeQueue;
}
async listKeys() {
if (!this.dbDir)
return [];
const keys = [];
// FileSystemDirectoryHandle.entries() 返回 AsyncIterable,使用 any 绕过 dts 生成限制
const dir = this.dbDir;
for await (const [name] of dir.entries()) {
keys.push(name);
}
return keys;
}
async exists(key) {
if (!this.dbDir)
return false;
try {
await this.dbDir.getFileHandle(key);
return true;
}
catch {
return false;
}
}
async clear() {
if (!this.dbDir)
return;
const dir = this.dbDir;
for await (const [name] of dir.entries()) {
try {
await this.dbDir.removeEntry(name);
}
catch { /* ignore */ }
}
}
}
/**
* 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 — 多版本并发控制
* @module engine/aria/transaction/mvcc
*
* 实现快照隔离 (Snapshot Isolation)。
* 每个事务看到数据库在事务开始时的快照。
*/
// ---------------------------------------------------------------------------
// MVCCManager
// ---------------------------------------------------------------------------
class MVCCManager {
constructor() {
/** 所有行版本的存储:tableName.key → 版本链 */
this.versionStore = new Map();
/** 活跃事务表:txnId → TxnEntry */
this.activeTxns = new Map();
/** 事务 ID 计数器 */
this.nextTxnId = 1;
/** 全局提交序列号(用于可见性判断) */
this.globalCommitLsn = 0;
}
// =======================================================================
// 事务管理
// =======================================================================
/** 开始一个事务,返回事务 ID */
beginTransaction() {
const txnId = this.nextTxnId++;
this.activeTxns.set(txnId, {
txnId,
state: TransactionState.ACTIVE,
snapshotLsn: this.globalCommitLsn,
startTime: Date.now(),
});
return txnId;
}
/** 提交事务 */
commitTransaction(txnId) {
const txn = this.activeTxns.get(txnId);
if (!txn)
throw new Error(`Transaction ${txnId} not found`);
txn.state = TransactionState.COMMITTED;
this.globalCommitLsn++;
// 标记此事务写入的所有版本为已提交
for (const [, versions] of this.versionStore) {
for (const version of versions) {
if (version.txnId === txnId) {
version.committed = true;
}
}
}
// 清理已提交事务的记录
this.activeTxns.delete(txnId);
}
/** 回滚事务 */
rollbackTransaction(txnId) {
const txn = this.activeTxns.get(txnId);
if (!txn)
throw new Error(`Transaction ${txnId} not found`);
txn.state = TransactionState.ABORTED;
// 移除此事务写入的所有版本
for (const [tableKey, versions] of this.versionStore) {
const filtered = versions.filter((v) => v.txnId !== txnId);
if (filtered.length === 0) {
this.versionStore.delete(tableKey);
}
else {
this.versionStore.set(tableKey, filtered);
}
}
this.activeTxns.delete(txnId);
}
/** 检查事务是否活跃 */
isActive(txnId) {
const txn = this.activeTxns.get(txnId);
return txn !== undefined && txn.state === TransactionState.ACTIVE;
}
// =======================================================================
// 版本读写
// =======================================================================
/**
* 写入一行(创建新版本)。
*/
writeVersion(tableName, key, data, txnId) {
const tableKey = `${tableName}.${key}`;
const versions = this.versionStore.get(tableKey) ?? [];
const newVersion = {
txnId,
data,
prevVersion: versions.length > 0 ? versions[versions.length - 1] : null,
committed: false,
};
versions.push(newVersion);
this.versionStore.set(tableKey, versions);
}
/**
* 读取一行(对指定事务可见的最新版本)。
*/
readVersion(tableName, key, txnId) {
const txn = this.activeTxns.get(txnId);
if (!txn)
return null;
const tableKey = `${tableName}.${key}`;
const versions = this.versionStore.get(tableKey);
if (!versions || versions.length === 0)
return null;
// 从最新版本向前遍历
for (let i = versions.length - 1; i >= 0; i--) {
const version = versions[i];
// 1. 如果是当前事务写入的(未提交),可见
if (version.txnId === txnId) {
return version.data;
}
// 2. 如果是已提交的版本,且在快照 LSN 之前提交,可见
if (version.committed) {
// 简化:所有已提交版本都可见
return version.data;
}
// 3. 其他事务的未提交版本,不可见,继续找更早的版本
}
return null;
}
/**
* 删除一行(创建墓碑版本)。
*/
deleteVersion(tableName, key, txnId) {
this.writeVersion(tableName, key, { __mvcc_tombstone: true }, txnId);
}
/**
* 获取所有行的最新已提交版本(用于非事务读取)。
*/
getLatestCommittedVersions(tableName) {
const result = {};
for (const [tableKey, versions] of this.versionStore) {
if (!tableKey.startsWith(`${tableName}.`))
continue;
const key = tableKey.slice(tableName.length + 1);
for (let i = versions.length - 1; i >= 0; i--) {
const version = versions[i];
if (version.committed && !version.data.__mvcc_tombstone) {
result[key] = version.data;
break;
}
}
}
return result;
}
/**
* 清理过旧版本(GC)。
* 保留每个 key 的最新 N 个已提交版本。
*/
gc(maxVersionsPerKey = 100) {
for (const [tableKey, versions] of this.versionStore) {
if (versions.length <= maxVersionsPerKey)
continue;
// 保留最新的 maxVersionsPerKey 个版本
const pruned = versions.slice(versions.length - maxVersionsPerKey);
this.versionStore.set(tableKey, pruned);
}
}
/**
* 获取所有未提交事务中的 key 列表。
*/
getActiveWriteKeys(tableName, txnId) {
const keys = new Set();
const prefix = `${tableName}.`;
for (const [tableKey, versions] of this.versionStore) {
if (!tableKey.startsWith(prefix))
continue;
const latestVersion = versions[versions.length - 1];
if (latestVersion.txnId === txnId && !latestVersion.committed) {
keys.add(tableKey.slice(prefix.length));
}
}
return keys;
}
/**
* 清理指定表的所有版本。
*/
clearTable(tableName) {
const prefix = `${tableName}.`;
for (const [tableKey] of this.versionStore) {
if (tableKey.startsWith(prefix)) {
this.versionStore.delete(tableKey);
}
}
}
/**
* 获取活跃事务数。
*/
getActiveTxnCount() {
return this.activeTxns.size;
}
/**
* 获取全局 LSN。
*/
getGlobalLSN() {
return this.globalCommitLsn;
}
}
/**
* AriaEngine Page Format — 页面格式整合层
* @module engine/aria/page/format
*
* 组合 Header / Slot / Tuple 操作,提供统一的页面管理接口。
*/
// ---------------------------------------------------------------------------
// 页面创建
// ---------------------------------------------------------------------------
/** 创建一个新的空页面 */
function createPage(pageId, type) {
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(),
};
}
/**
* AriaEngine Buffer Pool Eviction — LRU 驱逐策略
* @module engine/aria/buffer/eviction
*/
// ---------------------------------------------------------------------------
// LRU 双向链表
// ---------------------------------------------------------------------------
/**
* LRU 链表管理器 — 双向链表,头部是 most recently used,尾部是 least recently used。
*/
class LRUList {
constructor() {
this.head = null;
this.tail = null;
this._size = 0;
}
get size() {
return this._size;
}
/** 将页面移到链表头部。如果是新页面则插入,已存在则移动。 */
moveToHead(page) {
// 如果已经在头部,无需操作
if (this.head === page)
return;
// 检测是否在链表中
const inList = page.prev !== null || page.next !== null || this.head === page || this.tail === page;
if (inList) {
// 先从当前位置移除
this.detach(page);
}
else {
this._size++;
}
// 插入头部
page.prev = null;
page.next = this.head;
if (this.head) {
this.head.prev = page;
}
this.head = page;
if (!this.tail) {
this.tail = page;
}
}
/** 从链表中移除页面 */
remove(page) {
const inList = page.prev !== null || page.next !== null || this.head === page || this.tail === page;
if (!inList)
return;
this.detach(page);
this._size = Math.max(0, this._size - 1);
}
/** 内部:只调整指针,不修改 _size */
detach(page) {
if (page.prev) {
page.prev.next = page.next;
}
else if (this.head === page) {
this.head = page.next;
}
if (page.next) {
page.next.prev = page.prev;
}
else if (this.tail === page) {
this.tail = page.prev;
}
page.prev = null;
page.next = null;
}
/** 获取 LRU 尾部(最久未使用的页面) */
getLRU() {
return this.tail;
}
/** 弹出 LRU 尾部 */
popLRU() {
const lru = this.tail;
if (lru) {
this.remove(lru);
}
return lru;
}
/** 清空链表 */
clear() {
this.head = null;
this.tail = null;
this._size = 0;
}
/** 获取所有页面(用于迭代) */
getAllPages() {
const pages = [];
let current = this.head;
while (current) {
pages.push(current);
current = current.next;
}
return pages;
}
}
/**
* 驱逐管理器 — 当 Buffer Pool 满时驱逐页面。
*/
class EvictionManager {
constructor(capacity, onEvict) {
this.lru = new LRUList();
this.capacity = capacity;
this.onEvict = onEvict;
}
/** 访问页面,更新 LRU */
access(page) {
page.lastAccess = Date.now();
this.lru.moveToHead(page);
}
/** 添加新页面到池中 */
add(page) {
this.access(page);
}
/** 移除指定页面 */
remove(page) {
this.lru.remove(page);
}
/**
* 驱逐页面直到池中有足够空间。
* 只驱逐未 pin 的干净页面(dirty=false)。
* 如果没有干净页面可驱逐,尝试刷脏页。
*/
async evictIfNeeded(count) {
let evicted = 0;
while (this.lru.size + count > this.capacity && this.lru.size > 0) {
// 找到可驱逐的页面
const victim = this.findEvictionCandidate();
if (!victim)
break;
// 脏页先刷盘
if (victim.dirty) {
await this.onEvict(victim);
victim.dirty = false;
}
this.lru.remove(victim);
evicted++;
}
return evicted;
}
/** 查找驱逐候选(优先干净页面,然后最久未用的脏页) */
findEvictionCandidate() {
// 先从尾部找未 pin 的干净页面
let current = this.lru.getLRU();
while (current) {
if (current.pins === 0 && !current.dirty)
return current;
current = current.prev;
}
// 没有干净页,找未 pin 的脏页
current = this.lru.getLRU();
while (current) {
if (current.pins === 0)
return current;
current = current.prev;
}
return null;
}
/** 获取当前大小 */
getSize() {
return this.lru.size;
}
/** 获取容量 */
getCapacity() {
return this.capacity;
}
/** 清空 */
clear() {
this.lru.clear();
}
}
/**
* AriaEngine Buffer Pool — 页面缓存池
* @module engine/aria/buffer/pool
*
* 管理固定数量页面的 LRU 缓存,减少对底层储存的访问。
*/
// ---------------------------------------------------------------------------
// Buffer Pool
// ---------------------------------------------------------------------------
class BufferPool {
constructor(pageIO, capacity = DEFAULT_BUFFER_POOL_PAGES) {
this.pages = new Map();
this.nextPageId = 0;
this.pageIO = pageIO;
this.eviction = new EvictionManager(capacity, async (page) => {
if (page.dirty) {
await this.pageIO.writePage(page.pageId, page.data);
page.dirty = false;
}
});
}
// -----------------------------------------------------------------------
// 页面获取
// -----------------------------------------------------------------------
/**
* 获取页面(必要时从磁盘读取)。
* 返回 pin 的页面,使用完成后必须调用 unpin()。
*/
async getPage(pageId) {
// 已在池中
let page = this.pages.get(pageId);
if (page) {
this.eviction.access(page);
page.pins++;
return page;
}
// 需要从磁盘加载
const buffer = await this.pageIO.readPage(pageId);
if (!buffer)
return null;
// 确保有空间
await this.eviction.evictIfNeeded(1);
const type = new DataView(buffer).getUint8(4);
page = {
pageId,
type,
data: buffer,
dirty: false,
pins: 1,
prev: null,
next: null,
lastAccess: Date.now(),
};
this.pages.set(pageId, page);
this.eviction.add(page);
return page;
}
/**
* 创建新页面。
*/
async newPage(type = PageType.DATA) {
const pageId = await this.pageIO.allocatePageId();
await this.eviction.evictIfNeeded(1);
const page = createPage(pageId, type);
page.pins = 1;
this.pages.set(pageId, page);
this.eviction.add(page);
return page;
}
/**
* 释放页面的 pin。
*/
unpin(page) {
if (page.pins > 0) {
page.pins--;
}
}
/**
* 标记页面为脏(需要写回)。
*/
markDirty(page) {
page.dirty = true;
}
/**
* 将脏页面刷新到磁盘。
*/
async flushPage(pageId) {
const page = this.pages.get(pageId);
if (page && page.dirty) {
await this.pageIO.writePage(pageId, page.data);
page.dirty = false;
}
}
/**
* 刷新所有脏页面。
*/
async flushAll() {
for (const [, page] of this.pages) {
if (page.dirty) {
await this.pageIO.writePage(page.pageId, page.data);
page.dirty = false;
}
}
}
/**
* 从缓存中删除指定页面(不刷盘)。
*/
removePage(pageId) {
const page = this.pages.get(pageId);
if (page) {
this.eviction.remove(page);
this.pages.delete(pageId);
}
}
/**
* 清空缓存池(先刷脏页)。
*/
async clear() {
await this.flushAll();
this.pages.clear();
this.eviction.clear();
}
// -----------------------------------------------------------------------
// 统计
// -----------------------------------------------------------------------
/** 获取当前缓存页面数 */
getCachedPageCount() {
return this.pages.size;
}
/** 获取缓存容量 */
getCapacity() {
return this.eviction.getCapacity();
}
/** 获取脏页面数 */
getDirtyPageCount() {
let count = 0;
for (const [, page] of this.pages) {
if (page.dirty)
count++;
}
return count;
}
}
/**
* 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 Crypto — 页面级 AES-GCM 加密
* @module engine/aria/crypto
*
* v0.2.5: 改为实例化 CryptoManager,避免多实例共享全局状态。
* 保留全局函数兼容旧代码(委托给全局单例)。
*/
const ALGO = 'AES-GCM';
const IV_LENGTH = 12;
/**
* CryptoManager — 实例级加密管理器
* 每个 AriaEngine 实例可拥有独立的加密配置。
*/
class CryptoManager {
constructor() {
this.cryptoKey = null;
this._enabled = false;
}
get enabled() { return this._enabled; }
async init(password, salt) {
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));
this.cryptoKey = await crypto.subtle.deriveKey({ name: 'PBKDF2', salt: actualSalt, iterations: 100000, hash: 'SHA-256' }, keyMaterial, { name: ALGO, length: 256 }, false, ['encrypt', 'decrypt']);
this._enabled = true;
return actualSalt;
}
async encryptPage(data) {
if (!this.cryptoKey)
throw new Error('Crypto not initialized');
const iv = crypto.getRandomValues(new Uint8Array(IV_LENGTH));
const ciphertext = await crypto.subtle.encrypt({ name: ALGO, iv }, this.cryptoKey, data);
return { iv: iv, data: ciphertext };
}
async decryptPage(iv, data) {
if (!this.cryptoKey)
throw new Error('Crypto not initialized');
return crypto.subtle.decrypt({ name: ALGO, iv }, this.cryptoKey, data);
}
close() {
this.cryptoKey = null;
this._enabled = false;
}
}
// ---------------------------------------------------------------------------
// 全局兼容层(旧代码仍可使用全局函数)
// ---------------------------------------------------------------------------
const globalCrypto = new CryptoManager();
/** @deprecated 使用 CryptoManager 实例代替 */
function isCryptoEnabled() { return globalCrypto.enabled; }
/** @deprecated 使用 CryptoManager 实例代替 */
async function encryptPage(data) {
return globalCrypto.encryptPage(data);
}
/** @deprecated 使用 CryptoManager 实例代替 */
async function decryptPage(iv, data) {
return globalCrypto.decryptPage(iv, data);
}
/**
* AriaEngine — 自研页面式存储引擎主类
* @module engine/aria/index
*
* v0.2.5: WAL 同步修复 + MVCC 接入 + 版本统一 + 生产加固
*/
// ---------------------------------------------------------------------------
// AriaEngine
// ---------------------------------------------------------------------------
class AriaEngine {
constructor(config = {}) {
this.name = 'aria';
this.opened = false;
this.dbName = '';
// 表结构
this.schemas = new Map();
this.tablePKs = new Map();
this.opCounter = 0;
// 二级索引:table.colKey → LSM
this.secondaryIndexes = new Map();
// MVCC 事务
this.mvcc = new MVCCManager();
this.currentTxnId = null;
this.txnSnapshot = null;
this.gcCounter = 0;
// ---- Savepoint 嵌套事务 ----
this.savepoints = new Map();
this.config = { ...DEFAULT_ARIA_CONFIG, ...config };
}
// =======================================================================
// 生命周期
// =======================================================================
async open(dbName, _version) {
if (this.opened)
return;
this.dbName = dbName;
// 1. 存储后端
if (this.config.storageBackend === 'opfs') {
this.backend = new OPFSBackend();
}
else if (this.config.storageBackend === 'indexeddb') {
this.backend = new IndexedDBBackend();
}
else {
this.backend = new MemoryBackend();
}
await this.backend.open(dbName);
// 2a. FileManager (PageIO 实现) + Buffer Pool
const fileManager = new FileManager(this.backend);
await fileManager.init(dbName);
this.bufferPool = new BufferPool(fileManager, this.config.bufferPoolPages);
// 2. 构建 SSTableStore
const sstableStore = this.createSSTableStore();
// 3. 初始化主 LSMPK 索引)
this.lsm = new LSM({
memtableSizeThreshold: this.config.memtableSizeThreshold,
levelSizeMultiplier: this.config.levelSizeMultiplier,
blockSize: this.config.pageSize,
bloomBitsPerKey: this.config.bloomFilterBitsPerKey,
sstableStore,
});
// 4. 初始化 WAL
this.wal = new WAL({
append: async (data) => {
// Store each record as a separate numbered key
const idx = await this.getWALCount();
const slice = new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
const copy = slice.buffer.slice(slice.byteOffset, slice.byteOffset + slice.byteLength);
await this.backend.write(`__wal_${idx}`, copy);
await this.setWALCount(idx + 1);
},
readAll: async () => {
const count = await this.getWALCount();
if (count === 0)
return new Uint8Array(0);
// Read all records and concatenate
const chunks = [];
for (let i = 0; i < count; i++) {
const d = await this.backend.read(`__wal_${i}`);
if (d)
chunks.push(new Uint8Array(d));
}
const total = chunks.reduce((s, c) => s + c.byteLength, 0);
const combined = new Uint8Array(total);
let off = 0;
for (const c of chunks) {
combined.set(c, off);
off += c.byteLength;
}
return combined;
},
truncate: async () => {
const count = await this.getWALCount();
for (let i = 0; i < count; i++) {
await this.backend.delete(`__wal_${i}`);
}
await this.setWALCount(0);
},
exists: async () => {
const count = await this.getWALCount();
return count > 0;
},
}, this.config.walEnabled, this.config.walSyncMode);
// 5. 恢复 Schema
await this.loadSchemas();
// 6. 初始化 LSM(加载 SSTable 元数据)
await this.lsm.init();
// 7. WAL 恢复(两阶段:先扫描事务边界,仅回放已提交事务)
const committedTxns = new Set();
const allRecords = [];
await this.wal.recover((r) => allRecords.push(r));
// 第一遍:确定已提交事务
for (const r of allRecords) {
if (r.type === WALRecordType.COMMIT)
committedTxns.add(r.txnId);
if (r.type === WALRecordType.ROLLBACK)
committedTxns.delete(r.txnId);
}
// 第二遍:仅应用 txnId==0(非事务)或已提交事务的数据
for (const r of allRecords) {
if (r.txnId === 0 || committedTxns.has(r.txnId)) {
this.applyWALRecord(r);
}
}
// 8. Checkpoint Manager(接入 WAL 大小阈值)
this.checkpointManager = new CheckpointManager(this.lsm, this.wal, { flushAll: async () => { await this.lsm.flush(); } }, this.config.checkpointInterval, this.config.walSizeThreshold);
this.opened = true;
}
async close() {
if (!this.opened)
return;
await this.persistSchemas();
await this.lsm.flush();
await this.wal.flush();
await this.backend.close();
this.schemas.clear();
this.opened = false;
}
isOpen() { return this.opened; }
// =======================================================================
// 表管理
// =======================================================================
async createTable(schema) {
this.ensureOpen();
if (this.schemas.has(schema.name)) {
throw new DatabaseError(`Table "${schema.name}" already exists`, 'TABLE_EXISTS');
}
this.schemas.set(schema.name, schema);
this.tablePKs.set(schema.name, this.getPK(schema));
// 为索引列创建二级索引 LSM
const sstableStore = this.createSSTableStore();
for (const [colName, colDef] of Object.entries(schema.columns)) {
if (colDef.index || colDef.unique || colDef.primaryKey) {
const idxKey = `${schema.name}:idx:${colName}`;
if (!this.secondaryIndexes.has(idxKey)) {
const idxLsm = new LSM({
memtableSizeThreshold: this.config.memtableSizeThreshold,
levelSizeMultiplier: this.config.levelSizeMultiplier,
blockSize: this.config.pageSize,
bloomBitsPerKey: this.config.bloomFilterBitsPerKey,
sstableStore,
});
await idxLsm.init();
this.secondaryIndexes.set(idxKey, idxLsm);
}
}
}
await this.persistSchemas();
await this.wal.append({
type: WALRecordType.CREATE_TABLE,
txnId: 0,
tableName: schema.name,
key: '',
data: { schema: JSON.stringify(schema) },
});
}
async dropTable(tableName) {
this.ensureOpen();
this.ensureTable(tableName);
// 删除表中所有行
const rows = this.getAllRows(tableName);
for (const row of rows) {
const pkCol = this.tablePKs.get(tableName);
this.lsm.delete(`${tableName}:${row[pkCol]}`);
}
this.schemas.delete(tableName);
this.tablePKs.delete(tableName);
await this.persistSchemas();
await this.wal.append({
type: WALRecordType.DROP_TABLE,
txnId: 0,
tableName,
key: '',
});
}
async hasTable(tableName) {
return this.schemas.has(tableName);
}
async getTableNames() {
return Array.from(this.schemas.keys());
}
async getTableSchema(tableName) {
return this.schemas.get(tableName) ?? null;
}
// =======================================================================
// CRUD
// =======================================================================
async insert(tableName, rows) {
this.ensureOpen();
this.ensureTable(tableName);
const schema = this.schemas.get(tableName);
const pkCol = this.tablePKs.get(tableName);
const pks = [];
for (const row of rows) {
const validated = this.validateRow(schema, row);
const pkValue = String(validated[pkCol]);
const key = `${tableName}:${pkValue}`;
// Check duplicate in LSM + transaction snapshot
const existing = this.currentTxnId
? (this.txnSnapshot?.get(key) ?? this.lsm.get(key))
: this.lsm.get(key);
if (existing && !existing.__txn_deleted) {
throw new DatabaseError(`Duplicate primary key "${pkValue}" in table "${tableName}"`, 'DUPLICATE_KEY');
}
if (this.currentTxnId && this.txnSnapshot) {
// Within transaction: buffer to snapshot + MVCC version chain
this.txnSnapshot.set(key, validated);
this.mvcc.writeVersion(tableName, pkValue, validated, this.currentTxnId);
}
else {
// Direct write to LSM (PK index)
this.lsm.put(key, validated);
}
// 更新二级索引
this.updateSecondaryIndexes(tableName, pkValue, validated, null);
pks.push(pkValue);
await this.wal.append({
type: WALRecordType.INSERT,
txnId: this.currentTxnId ?? 0,
tableName,
key: pkValue,
data: validated,
});
}
this.opCounter += rows.length;
this.checkMemoryBudget();
await this.checkpointManager.tick();
this.tryGC();
return pks;
}
async find(tableName, query) {
this.ensureOpen();
this.ensureTable(tableName);
let rows;
// Try index lookup
const fastPath = this.tryIndexLookup(tableName, query);
if (fastPath !== null) {
rows = fastPath;
}
else {
rows = this.getAllRows(tableName);
}
// Merge transaction snapshot writes (uncommitted data visible within txn)
if (this.currentTxnId && this.txnSnapshot) {
const pkCol = this.tablePKs.get(tableName);
const prefix = `${tableName}:`;
for (const [key, value] of this.txnSnapshot) {
if (!key.startsWith(prefix))
continue;
const pk = key.slice(prefix.length);
const del = value.__txn_deleted;
const idx = rows.findIndex((r) => r[pkCol] === pk);
if (del) {
if (idx >= 0)
rows.splice(idx, 1);
}
else {
const row = { ...value, [pkCol]: pk };
if (idx >= 0)
rows[idx] = row;
else
rows.push(row);
}
}
}
// WHERE filter
if (query.where && Object.keys(query.where).length > 0) {
rows = rows.filter((row) => matchWhere(row, query.where));
}
// ORDER
if (query.orderBy && query.orderBy.length > 0) {
rows = applyOrderBy(rows, query.orderBy);
}
// LIMIT/OFFSET
const offset = query.offset ?? 0;
const limit = query.limit ?? rows.length;
rows = rows.slice(offset, offset + limit);
// Column projection
if (query.columns && query.columns.length > 0 && query.columns[0] !== '*') {
rows = rows.map((row) => projectColumns(row, query.columns));
}
return rows;
}
async update(tableName, query, updates) {
this.ensureOpen();
this.ensureTable(tableName);
const schema = this.schemas.get(tableName);
const rows = this.getAllRows(tableName);
let count = 0;
for (const row of rows) {
const pkCol = this.tablePKs.get(tableName);
const key = `${tableName}:${row[pkCol]}`;
if (!query.where || Object.keys(query.where).length === 0 || matchWhere(row, query.where)) {
const updated = { ...row, ...updates };
this.validateRow(schema, updated);
if (this.currentTxnId && this.txnSnapshot) {
this.txnSnapshot.set(key, updated);
this.mvcc.writeVersion(tableName, String(row[pkCol]), updated, this.currentTxnId);
}
else {
this.lsm.put(key, updated);
}
count++;
await this.wal.append({
type: WALRecordType.UPDATE,
txnId: this.currentTxnId ?? 0,
tableName,
key: String(row[pkCol]),
data: updated,
});
// 更新二级索引
this.updateSecondaryIndexes(tableName, String(row[pkCol]), updated, row);
}
}
this.opCounter += count;
await this.checkpointManager.tick();
return count;
}
async delete(tableName, query) {
this.ensureOpen();
this.ensureTable(tableName);
const rows = this.getAllRows(tableName);
let count = 0;
for (const row of rows) {
const pkCol = this.tablePKs.get(tableName);
const key = `${tableName}:${row[pkCol]}`;
if (!query.where || Object.keys(query.where).length === 0 || matchWhere(row, query.where)) {
if (this.currentTxnId && this.txnSnapshot) {
// Buffer delete in snapshot + MVCC tombstone
this.txnSnapshot.set(key, { __txn_deleted: true });
this.mvcc.deleteVersion(tableName, String(row[pkCol]), this.currentTxnId);
}
else {
this.lsm.delete(key);
}
count++;
await this.wal.append({
type: WALRecordType.DELETE,
txnId: this.currentTxnId ?? 0,
tableName,
key: String(row[pkCol]),
});
// 移除二级索引
this.updateSecondaryIndexes(tableName, String(row[pkCol]), null, row);
}
}
this.opCounter += count;
await this.checkpointManager.tick();
return count;
}
async count(tableName, query) {
this.ensureOpen();
this.ensureTable(tableName);
const rows = this.getAllRows(tableName);
if (!query?.where || Object.keys(query.where).length === 0)
return rows.length;
return rows.filter((row) => matchWhere(row, query.where)).length;
}
async clear(tableName) {
this.ensureOpen();
this.ensureTable(tableName);
const rows = this.getAllRows(tableName);
for (const row of rows) {
const pkCol = this.tablePKs.get(tableName);
this.lsm.delete(`${tableName}:${row[pkCol]}`);
}
}
// =======================================================================
// 事务
// =======================================================================
async beginTransaction() {
if (this.currentTxnId)
throw new DatabaseError('Transaction already in progress', 'TX_ACTIVE');
this.currentTxnId = this.mvcc.beginTransaction();
this.txnSnapshot = new Map();
await this.wal.append({
type: WALRecordType.BEGIN,
txnId: this.currentTxnId,
tableName: '',
key: '',
});
}
async commitTransaction() {
if (!this.currentTxnId)
throw new DatabaseError('No active transaction', 'TX_NONE');
if (this.txnSnapshot) {
for (const [key, value] of this.txnSnapshot) {
if (value.__txn_deleted) {
this.lsm.delete(key);
}
else {
this.lsm.put(key, value);
}
}
}
this.mvcc.commitTransaction(this.currentTxnId);
await this.wal.append({
type: WALRecordType.COMMIT,
txnId: this.currentTxnId,
tableName: '',
key: '',
});
this.currentTxnId = null;
this.txnSnapshot = null;
await this.wal.flush();
}
async rollbackTransaction() {
if (!this.currentTxnId)
throw new DatabaseError('No active transaction', 'TX_NONE');
this.mvcc.rollbackTransaction(this.currentTxnId);
this.txnSnapshot = null;
await this.wal.append({
type: WALRecordType.ROLLBACK,
txnId: this.currentTxnId,
tableName: '',
key: '',
});
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;
}
// =======================================================================
// 内部
// =======================================================================
getAllRows(tableName) {
const pkCol = this.tablePKs.get(tableName);
const prefix = `${tableName}:`;
const entries = this.lsm.rangeScan(prefix, `${prefix}\uffff`);
return entries.map(([key, value]) => {
const row = { ...value };
row[pkCol] = key.slice(prefix.length);
return row;
});
}
getPK(schema) {
for (const [name, col] of Object.entries(schema.columns)) {
if (col.primaryKey)
return name;
}
return Object.keys(schema.columns)[0];
}
validateRow(schema, row) {
const validated = {};
for (const [colName, colDef] of Object.entries(schema.columns)) {
let value = row[colName];
if (value === undefined && colDef.default !== undefined)
value = colDef.default;
if (colDef.required && (value === undefined || value === null)) {
throw new DatabaseError(`Column "${colName}" is required in table "${schema.name}"`, 'VALIDATION_ERROR');
}
if (value !== undefined && value !== null) {
this.checkType(colName, colDef.type, value, colDef);
}
if (value !== undefined)
validated[colName] = value;
}
return validated;
}
checkType(colName, type, value, colDef) {
checkFieldType('', colName, type, value, colDef);
}
// =======================================================================
// Schema 持久化
// =======================================================================
async persistSchemas() {
const data = {};
for (const [name, schema] of this.schemas) {
data[name] = schema.columns;
}
const json = JSON.stringify(data);
const buf = new TextEncoder().encode(json).buffer;
await this.backend.write('__aria_schemas', buf);
}
async loadSchemas() {
const raw = await this.backend.read('__aria_schemas');
if (!raw)
return;
try {
const json = new TextDecoder().decode(raw);
const data = JSON.parse(json);
for (const [tableName, columns] of Object.entries(data)) {
const schema = { name: tableName, columns };
this.schemas.set(tableName, schema);
this.tablePKs.set(tableName, this.getPK(schema));
}
}
catch {
// 忽略损坏的 schema 数据
}
}
// =======================================================================
// SSTableStore 构建
// =======================================================================
createSSTableStore() {
const META_KEY = '__aria_lsm_meta';
return {
save: async (id, data) => {
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);
}
// 加密(若启用)
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);
},
load: async (id) => {
const raw = await this.backend.read(`sst_${id}`);
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;
}
return buf;
},
delete: async (id) => {
await this.backend.delete(`sst_${id}`);
},
allocateId: async () => Date.now(),
listMeta: async () => {
const raw = await this.backend.read(META_KEY);
if (!raw)
return [];
try {
return JSON.parse(new TextDecoder().decode(raw));
}
catch {
return [];
}
},
saveMeta: async (meta) => {
const existing = await this.backend.read(META_KEY);
const list = existing
? JSON.parse(new TextDecoder().decode(existing))
: [];
// 更新或添加
const idx = list.findIndex((m) => m.id === meta.id);
if (idx >= 0)
list[idx] = meta;
else
list.push(meta);
const json = JSON.stringify(list);
const buf = new TextEncoder().encode(json).buffer;
await this.backend.write(META_KEY, buf);
},
deleteMeta: async (id) => {
const existing = await this.backend.read(META_KEY);
if (!existing)
return;
const list = JSON.parse(new TextDecoder().decode(existing));
const filtered = list.filter((m) => m.id !== id);
const json = JSON.stringify(filtered);
const buf = new TextEncoder().encode(json).buffer;
await this.backend.write(META_KEY, buf);
},
};
}
// =======================================================================
// WAL 恢复
// =======================================================================
applyWALRecord(record) {
switch (record.type) {
case WALRecordType.INSERT:
case WALRecordType.UPDATE:
if (record.data) {
this.lsm.put(`${record.tableName}:${record.key}`, record.data);
}
break;
case WALRecordType.DELETE:
this.lsm.delete(`${record.tableName}:${record.key}`);
break;
case WALRecordType.CREATE_TABLE:
if (record.data?.schema) {
try {
const s = JSON.parse(record.data.schema);
if (!this.schemas.has(s.name)) {
this.schemas.set(s.name, s);
this.tablePKs.set(s.name, this.getPK(s));
}
}
catch { /* skip */ }
}
break;
case WALRecordType.COMMIT:
case WALRecordType.ROLLBACK:
case WALRecordType.BEGIN:
case WALRecordType.DROP_TABLE:
break;
}
}
// =======================================================================
// 二级索引
// =======================================================================
/** 更新行的二级索引条目 */
updateSecondaryIndexes(tableName, pkValue, newRow, oldRow) {
const schema = this.schemas.get(tableName);
if (!schema)
return;
for (const [colName, colDef] of Object.entries(schema.columns)) {
if (!colDef.index && !colDef.unique && !colDef.primaryKey)
continue;
const idxKey = `${tableName}:idx:${colName}`;
const idxLsm = this.secondaryIndexes.get(idxKey);
if (!idxLsm)
continue;
// 删除旧值
if (oldRow) {
const oldVal = oldRow[colName];
if (oldVal !== undefined && oldVal !== null) {
idxLsm.delete(`${String(oldVal)}:${pkValue}`);
}
}
// 插入新值
if (newRow) {
const newVal = newRow[colName];
if (newVal !== undefined && newVal !== null) {
idxLsm.put(`${String(newVal)}:${pkValue}`, { pk: pkValue });
}
}
}
}
/** 通过二级索引快速查找 */
tryIndexLookup(tableName, query) {
if (!query.where)
return null;
const schema = this.schemas.get(tableName);
if (!schema)
return null;
const pkCol = this.tablePKs.get(tableName);
for (const [col, condition] of Object.entries(query.where)) {
// 跳过 $and/$or/$not 逻辑组合
if (col === '$and' || col === '$or' || col === '$not')
continue;
const colDef = schema.columns[col];
const hasIndex = colDef && (colDef.index || colDef.unique || colDef.primaryKey);
if (!hasIndex && col !== pkCol)
continue;
// PK 等值 → 主 LSM 精确查找
if (col === pkCol) {
if (typeof condition !== 'object' || condition === null) {
const key = `${tableName}:${condition}`;
const value = this.lsm.get(key);
return value ? [{ ...value, [pkCol]: condition }] : [];
}
const cond = condition;
if ('$eq' in cond) {
const key = `${tableName}:${cond.$eq}`;
const value = this.lsm.get(key);
return value ? [{ ...value, [pkCol]: cond.$eq }] : [];
}
}
// 二级索引查找
const idxKey = `${tableName}:idx:${col}`;
const idxLsm = this.secondaryIndexes.get(idxKey);
if (!idxLsm)
continue;
// $eq → 精确查找
if (typeof condition !== 'object' || condition === null) {
return this.indexScanToRows(tableName, pkCol, idxLsm, col, String(condition), String(condition));
}
const c = condition;
if ('$eq' in c) {
const v = String(c.$eq);
return this.indexScanToRows(tableName, pkCol, idxLsm, col, v, v);
}
// $in → 多次精确查找
if ('$in' in c && Array.isArray(c.$in)) {
const results = [];
for (const val of c.$in) {
const rows = this.indexScanToRows(tableName, pkCol, idxLsm, col, String(val), String(val));
results.push(...rows);
}
return results;
}
// $gt / $gte / $lt / $lte → 范围扫描
if ('$gt' in c || '$gte' in c || '$lt' in c || '$lte' in c) {
let startKey = '';
let endKey = '\uffff';
if (c.$gt !== undefined)
startKey = `${String(Number(c.$gt) + 1)}:`;
else if (c.$gte !== undefined)
startKey = `${String(c.$gte)}:`;
if (c.$lt !== undefined)
endKey = `${String(Number(c.$lt) - 1)}:\uffff`;
else if (c.$lte !== undefined)
endKey = `${String(c.$lte)}:\uffff`;
return this.indexScanToRows(tableName, pkCol, idxLsm, col, startKey, endKey);
}
}
return null;
}
/** 从索引扫描结果恢复完整行 */
indexScanToRows(tableName, pkCol, idxLsm, _col, startKey, endKey) {
// 使用前缀扫描:endKey 需要包含 \uffff 以匹配所有带后缀的 key
const actualEndKey = endKey.includes('\uffff') ? endKey : `${endKey}\uffff`;
const entries = idxLsm.rangeScan(startKey, actualEndKey);
const rows = [];
for (const [, idxEntry] of entries) {
const pk = idxEntry.pk;
if (!pk)
continue;
const row = this.lsm.get(`${tableName}:${pk}`);
if (row)
rows.push({ ...row, [pkCol]: pk });
}
return rows;
}
// =======================================================================
// 辅助
// =======================================================================
/** 每 10 次 gc 计数器触发一次 MVCC 垃圾回收 */
tryGC() {
this.gcCounter++;
if (this.gcCounter >= 10) {
this.mvcc.gc(100);
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);
}
}
/** 估算 WAL 大小(字节) */
getWALEstimatedSize() {
return this.wal.getBufferedCount() * 200; // 粗略估算每条 ~200B
}
/**
* 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;
}
/**
* REINDEX: 重建指定表的所有二级索引
*/
async reindexTable(tableName) {
this.ensureOpen();
this.ensureTable(tableName);
const schema = this.schemas.get(tableName);
let rebuiltCount = 0;
for (const [colName, colDef] of Object.entries(schema.columns)) {
if (!colDef.index && !colDef.unique && !colDef.primaryKey)
continue;
const idxKey = `${tableName}:idx:${colName}`;
const idxLsm = this.secondaryIndexes.get(idxKey);
if (!idxLsm)
continue;
// 清空旧索引
await idxLsm.clear();
rebuiltCount++;
// 从主 LSM 重建索引
const rows = this.getAllRows(tableName);
for (const row of rows) {
const val = row[colName];
if (val !== undefined && val !== null) {
idxLsm.put(`${String(val)}:${row[this.tablePKs.get(tableName)]}`, { pk: row[this.tablePKs.get(tableName)] });
}
}
}
return rebuiltCount;
}
/**
* VACUUM: 压缩 LSM + 清理碎片
*/
async vacuum() {
this.ensureOpen();
// 强制 flush memtable
await this.lsm.flush();
// 压缩各层级
for (let level = 0; level < 6; level++) {
if (this.lsm.getStats().levelCounts[level] >= 2) {
this.lsm.compactLevel(level);
}
}
// GC MVCC 版本(保留最新 10 个)
const beforeGC = this.mvcc.getActiveTxnCount?.() ?? 0;
this.mvcc.gc(10);
return { compactedLevels: 6, gcVersions: beforeGC };
}
/**
* 查询优化器:估算各索引成本,选择最优方案
*/
estimateQueryCost(tableName, query) {
const schema = this.schemas.get(tableName);
if (!schema || !query.where)
return { strategy: 'full_scan', estimatedRows: 0 };
const pkCol = this.tablePKs.get(tableName);
for (const [col, condition] of Object.entries(query.where)) {
if (col === '$and' || col === '$or' || col === '$not')
continue;
// PK 等值 → 最快,估计 1 行
if (col === pkCol && (typeof condition !== 'object' || condition.$eq)) {
return { strategy: 'pk_lookup', estimatedRows: 1 };
}
// 索引列等值 → 快
const colDef = schema.columns[col];
if (colDef?.index || colDef?.unique) {
if (typeof condition !== 'object' || condition.$eq) {
return { strategy: `index_eq:${col}`, estimatedRows: 1 };
}
if (condition.$in && Array.isArray(condition.$in)) {
return { strategy: `index_in:${col}`, estimatedRows: condition.$in.length };
}
if (condition.$gt || condition.$lt || condition.$gte || condition.$lte) {
return { strategy: `index_range:${col}`, estimatedRows: 100 };
}
}
}
return { strategy: 'full_scan', estimatedRows: 1000 };
}
ensureOpen() {
if (!this.opened)
throw new DatabaseError('AriaEngine not opened', 'DB_NOT_OPEN');
}
ensureTable(tableName) {
if (!this.schemas.has(tableName)) {
throw new DatabaseError(`Table "${tableName}" does not exist`, 'TABLE_NOT_FOUND');
}
}
/** Get the number of WAL records stored */
async getWALCount() {
const raw = await this.backend.read('__wal_count');
if (!raw)
return 0;
try {
const dec = new TextDecoder();
return parseInt(dec.decode(raw), 10) || 0;
}
catch {
return 0;
}
}
/** Set the number of WAL records stored */
async setWALCount(count) {
const enc = new TextEncoder();
const buf = enc.encode(String(count)).buffer;
await this.backend.write('__wal_count', buf);
}
}
/**
* metona-sqlark Hybrid Engine — 内存 + 磁盘混合存储引擎
* @module hybrid/index
*
* 采用 write-through 策略:
* - 所有写操作同时写入内存和磁盘
* - 所有读操作直接从内存返回
* - 数据库打开时从磁盘加载数据到内存
*/
// ---------------------------------------------------------------------------
// HybridEngine
// ---------------------------------------------------------------------------
class HybridEngine {
constructor(diskEngine = 'indexeddb') {
this.name = 'hybrid';
this.memoryEngine = new MemoryEngine();
this.diskEngineType = diskEngine;
this.diskEngine = diskEngine === 'opfs' ? new OPFSEngine() : new IndexedDBEngine();
}
// ---- 生命周期 ----
async open(dbName, version) {
// 先打开磁盘引擎
await this.diskEngine.open(dbName, version);
// 再打开内存引擎
await this.memoryEngine.open(dbName, version);
// 从磁盘加载现存表
const tableNames = await this.diskEngine.getTableNames();
for (const tableName of tableNames) {
const schema = await this.diskEngine.getTableSchema(tableName);
if (!schema)
continue;
// 在内存中创建表
await this.memoryEngine.createTable(schema);
// 从磁盘加载数据到内存
const rows = await this.diskEngine.find(tableName, { table: tableName });
if (rows.length > 0) {
try {
await this.memoryEngine.insert(tableName, rows);
}
catch (e) {
// eslint-disable-next-line no-console
console.warn(`[metona-sqlark] Failed to load table "${tableName}" data from disk:`, e);
}
}
}
}
async close() {
await this.memoryEngine.close();
await this.diskEngine.close();
}
isOpen() {
return this.memoryEngine.isOpen() && this.diskEngine.isOpen();
}
// ---- 表管理 ----
async createTable(schema) {
await this.memoryEngine.createTable(schema);
await this.diskEngine.createTable(schema);
}
async dropTable(tableName) {
await this.memoryEngine.dropTable(tableName);
await this.diskEngine.dropTable(tableName);
}
async hasTable(tableName) {
return this.memoryEngine.hasTable(tableName);
}
async getTableNames() {
return this.memoryEngine.getTableNames();
}
async getTableSchema(tableName) {
return this.memoryEngine.getTableSchema(tableName);
}
// ---- CRUDwrite-through 策略) ----
async insert(tableName, rows) {
const pks = await this.memoryEngine.insert(tableName, rows);
// write-through: 同步写入磁盘
await this.diskEngine.insert(tableName, rows);
return pks;
}
async find(tableName, query) {
// 直接从内存读取
return this.memoryEngine.find(tableName, query);
}
async update(tableName, query, updates) {
const count = await this.memoryEngine.update(tableName, query, updates);
// write-through: 同步更新磁盘
await this.diskEngine.update(tableName, query, updates);
return count;
}
async delete(tableName, query) {
const count = await this.memoryEngine.delete(tableName, query);
// write-through: 同步删除磁盘
await this.diskEngine.delete(tableName, query);
return count;
}
async count(tableName, query) {
return this.memoryEngine.count(tableName, query);
}
async clear(tableName) {
await this.memoryEngine.clear(tableName);
await this.diskEngine.clear(tableName);
}
// ---- 事务 ----
async beginTransaction() {
await this.memoryEngine.beginTransaction();
await this.diskEngine.beginTransaction();
}
async commitTransaction() {
// 先写磁盘,保证持久化优先;磁盘失败则回滚内存
await this.diskEngine.commitTransaction();
try {
await this.memoryEngine.commitTransaction();
}
catch {
// 内存提交失败时回滚磁盘
await this.diskEngine.rollbackTransaction();
throw new DatabaseError('Hybrid commit failed: memory engine error after disk commit', 'TX_COMMIT_ERROR');
}
}
async rollbackTransaction() {
await this.memoryEngine.rollbackTransaction();
await this.diskEngine.rollbackTransaction();
}
// ---- 引擎信息 ----
/** 获取磁盘引擎类型 */
getDiskEngineType() {
return this.diskEngineType;
}
/** 获取内存引擎(供内部使用) */
getMemoryEngine() {
return this.memoryEngine;
}
}
/**
* metona-sqlark Query Builder — 链式查询构建器
* @module query/builder
*
* 链式调用 → 构建 AST → 执行引擎操作。
* 支持 JOIN(需要 Executor)。
*/
// ---------------------------------------------------------------------------
// SelectQueryBuilder
// ---------------------------------------------------------------------------
class SelectQueryBuilder {
constructor(engine, tableName, _columns = ['*'], executor) {
this.engine = engine;
this.tableName = tableName;
this._columns = _columns;
this._where = {};
this._orderBy = [];
this._joins = [];
this._executor = executor;
}
/** 主表别名 */
as(alias) {
this._alias = alias;
return this;
}
/** INNER JOIN */
innerJoin(table, on, alias) {
return this._addJoin('INNER', table, on, alias);
}
/** LEFT JOIN */
leftJoin(table, on, alias) {
return this._addJoin('LEFT', table, on, alias);
}
/** RIGHT JOIN */
rightJoin(table, on, alias) {
return this._addJoin('RIGHT', table, on, alias);
}
/** CROSS JOIN */
crossJoin(table, alias) {
return this._addJoin('CROSS', table, {}, alias);
}
/** 通用 JOIN */
join(table, on, alias) {
return this._addJoin('INNER', table, on, alias);
}
_addJoin(type, table, on, alias) {
this._joins.push({ type, table, on, alias });
return this;
}
/** 添加过滤条件 */
where(condition) {
this._where = { ...this._where, ...condition };
return this;
}
/** 排序 */
orderBy(column, direction = 'asc') {
this._orderBy.push({ column, direction });
return this;
}
/** 限制返回条数 */
limit(n) {
this._limit = n;
return this;
}
/** 偏移量 */
offset(n) {
this._offset = n;
return this;
}
/** 执行查询 */
async execute() {
// 有 JOIN → 通过 Executor 执行
if (this._joins.length > 0 && this._executor) {
const ast = this.toAST();
return this._executor.execute(ast);
}
// 无 JOIN → 直接调用引擎
return this.engine.find(this.tableName, {
table: this.tableName,
columns: this._columns,
where: this._where,
orderBy: this._orderBy.length > 0 ? this._orderBy : undefined,
limit: this._limit,
offset: this._offset,
});
}
/** 获取 AST */
toAST() {
return {
type: 'SELECT',
columns: this._columns,
from: this.tableName,
alias: this._alias,
joins: this._joins.length > 0 ? [...this._joins] : undefined,
where: this._where,
orderBy: this._orderBy.length > 0 ? this._orderBy : undefined,
limit: this._limit,
offset: this._offset,
};
}
}
// ---------------------------------------------------------------------------
// UpdateQueryBuilder
// ---------------------------------------------------------------------------
class UpdateQueryBuilder {
constructor(engine, tableName, _updates) {
this.engine = engine;
this.tableName = tableName;
this._updates = _updates;
this._where = {};
}
where(condition) {
this._where = { ...this._where, ...condition };
return this;
}
async execute() {
return this.engine.update(this.tableName, { table: this.tableName, where: this._where }, this._updates);
}
toAST() {
return { type: 'UPDATE', table: this.tableName, sets: this._updates, where: this._where };
}
}
// ---------------------------------------------------------------------------
// DeleteQueryBuilder
// ---------------------------------------------------------------------------
class DeleteQueryBuilder {
constructor(engine, tableName) {
this.engine = engine;
this.tableName = tableName;
this._where = {};
}
where(condition) {
this._where = { ...this._where, ...condition };
return this;
}
async execute() {
return this.engine.delete(this.tableName, { table: this.tableName, where: this._where });
}
toAST() {
return { type: 'DELETE', from: this.tableName, where: this._where };
}
}
/**
* metona-sqlark Table — 表操作 API
* @module table/table
*/
// ---------------------------------------------------------------------------
// Table
// ---------------------------------------------------------------------------
class Table {
constructor(engine, tableName, executor) {
this.schema = null;
this.engine = engine;
this.name = tableName;
this.executor = executor;
}
// ---- Schema ----
async getSchema() {
if (!this.schema) {
const s = await this.engine.getTableSchema(this.name);
if (!s)
throw new DatabaseError(`Table "${this.name}" does not exist`, 'TABLE_NOT_FOUND');
this.schema = s;
}
return this.schema;
}
// ---- 插入 ----
async insert(row) {
const pks = await this.engine.insert(this.name, [row]);
return pks[0];
}
async insertMany(rows) {
return this.engine.insert(this.name, rows);
}
// ---- 查询 ----
select(columns = ['*']) {
return new SelectQueryBuilder(this.engine, this.name, columns, this.executor);
}
// ---- 更新 ----
update(updates) {
return new UpdateQueryBuilder(this.engine, this.name, updates);
}
// ---- 删除 ----
delete() {
return new DeleteQueryBuilder(this.engine, this.name);
}
// ---- 聚合 ----
async count(where) {
return this.engine.count(this.name, where ? { table: this.name, where } : undefined);
}
// ---- 管理 ----
async clear() {
return this.engine.clear(this.name);
}
async drop() {
return this.engine.dropTable(this.name);
}
}
/**
* metona-sqlark Query Compiler — AST → 查询计划
* @module query/compiler
*
* 将 AST 语句编译为引擎可执行的 QueryPlan。
* v0.0.1: 简单直接映射,未来可加入索引选择、过滤下推等优化。
*/
// ---------------------------------------------------------------------------
// 编译 AST → QueryPlan
// ---------------------------------------------------------------------------
/**
* 编译 SELECT / DELETE / UPDATE 语句为 QueryPlan。
* INSERT 和 DDL 语句不需要 QueryPlan。
*/
function compileStatement(stmt) {
switch (stmt.type) {
case 'SELECT':
return compileSelect(stmt);
case 'DELETE':
return compileDelete(stmt);
case 'UPDATE':
return compileUpdate(stmt);
default:
throw new DatabaseError(`Cannot compile statement type "${stmt.type}" to QueryPlan`, 'COMPILE_ERROR');
}
}
function compileSelect(stmt) {
return {
table: stmt.from,
columns: stmt.columns,
where: stmt.where,
orderBy: stmt.orderBy?.length ? stmt.orderBy : undefined,
limit: stmt.limit,
offset: stmt.offset,
};
}
function compileDelete(stmt) {
return {
table: stmt.from,
where: stmt.where,
};
}
function compileUpdate(stmt) {
return {
table: stmt.table,
where: stmt.where,
};
}
/**
* metona-sqlark Query Executor — AST 执行器
* @module query/executor
*
* JOIN / GROUP BY / DISTINCT 逻辑在此层处理。
*/
// ---------------------------------------------------------------------------
// Executor
// ---------------------------------------------------------------------------
class QueryExecutor {
constructor(engine, maxRowsPerQuery = 0) {
this.engine = engine;
this.maxRowsPerQuery = maxRowsPerQuery;
}
/** 设置查询结果行数上限 */
setMaxRowsPerQuery(max) {
this.maxRowsPerQuery = max;
}
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);
case 'CREATE_TABLE': return this.executeCreateTable(stmt);
case 'DROP_TABLE': return this.executeDropTable(stmt);
case 'ALTER_TABLE': return this.executeAlterTable(stmt);
case 'TRUNCATE_TABLE': return this.executeTruncateTable(stmt);
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
// ===================================================================
async executeSelect(stmt) {
// 先解析子查询
if (stmt.where && Object.keys(stmt.where).length > 0) {
stmt.where = await this.resolveSubqueries(stmt.where);
}
const hasGroupBy = !!(stmt.groupBy && stmt.groupBy.length > 0);
const hasAggregate = !hasGroupBy && this._hasAggregateColumn(stmt.columns);
let rows;
if (!stmt.joins || stmt.joins.length === 0) {
const plan = compileStatement(hasGroupBy || hasAggregate ? { ...stmt, columns: ['*'] } : stmt);
rows = await this.engine.find(plan.table, plan);
}
else {
rows = await this.executeJoinSelect(stmt);
}
// 无 GROUP BY 但有聚合 → 计算单行聚合结果
if (hasAggregate) {
rows = [this.computeSingleAggregate(rows, stmt)];
}
if (hasGroupBy)
rows = this.executeGroupBy(rows, stmt);
if (stmt.distinct)
rows = this.executeDistinct(rows);
if (stmt.having && Object.keys(stmt.having).length > 0) {
rows = rows.filter((row) => matchWhere(row, stmt.having));
}
if (stmt.orderBy && stmt.orderBy.length > 0)
rows = applyOrderBy(rows, stmt.orderBy);
const offset = stmt.offset ?? 0;
const limit = stmt.limit ?? rows.length;
rows = rows.slice(offset, offset + limit);
if (!hasGroupBy && !hasAggregate && stmt.columns.length > 0 && stmt.columns[0] !== '*') {
rows = rows.map((row) => projectColumns(row, stmt.columns));
}
// 全局行数上限保护
if (this.maxRowsPerQuery > 0 && rows.length > this.maxRowsPerQuery) {
rows = rows.slice(0, this.maxRowsPerQuery);
}
return rows;
}
// ---- JOIN ----
async executeJoinSelect(stmt) {
const mainAlias = stmt.alias ?? stmt.from;
const mainRows = (await this.engine.find(stmt.from, { table: stmt.from }))
.map((row) => this.prefixRow(row, mainAlias));
let resultRows = mainRows;
for (const join of stmt.joins) {
const joinAlias = join.alias ?? join.table;
const joinRows = (await this.engine.find(join.table, { table: join.table }))
.map((row) => this.prefixRow(row, joinAlias));
resultRows = this.joinRows(resultRows, joinRows, join);
}
if (stmt.where && Object.keys(stmt.where).length > 0) {
resultRows = resultRows.filter((row) => matchWhere(row, stmt.where));
}
return resultRows;
}
prefixRow(row, alias) {
const prefixed = {};
for (const [key, value] of Object.entries(row))
prefixed[`${alias}.${key}`] = value;
return prefixed;
}
/** 嵌套循环连接(优化:避免 ON 时对象扩散) */
joinRows(leftRows, rightRows, join) {
if (join.type === 'CROSS') {
const result = [];
for (const l of leftRows)
for (const r of rightRows)
result.push({ ...l, ...r });
return result;
}
const result = [];
for (const l of leftRows) {
let matched = false;
for (const r of rightRows) {
// 合并后匹配 ON(避免创建临时对象再丢弃)
const merged = { ...l, ...r };
if (matchWhere(merged, join.on, { $col: true })) {
result.push(merged);
matched = true;
}
}
if (!matched && join.type === 'LEFT') {
const nullRight = {};
for (const key of Object.keys(rightRows[0] ?? {}))
nullRight[key] = null;
result.push({ ...l, ...nullRight });
}
}
if (join.type === 'RIGHT') {
for (const r of rightRows) {
const isMatched = leftRows.some((l) => {
const merged = { ...l, ...r };
return matchWhere(merged, join.on, { $col: true });
});
if (!isMatched) {
const nullLeft = {};
for (const key of Object.keys(leftRows[0] ?? {}))
nullLeft[key] = null;
result.push({ ...nullLeft, ...r });
}
}
}
return result;
}
// ---- GROUP BY ----
executeGroupBy(rows, stmt) {
const groups = new Map();
for (const row of rows) {
const key = stmt.groupBy.map((col) => String(row[col] ?? 'null')).join('|');
if (!groups.has(key))
groups.set(key, []);
groups.get(key).push(row);
}
const result = [];
for (const groupRows of groups.values()) {
const aggregated = {};
for (const col of stmt.groupBy)
aggregated[col] = groupRows[0][col];
for (const colExpr of stmt.columns) {
if (colExpr === '*')
continue;
const m = colExpr.match(/^(COUNT|SUM|AVG|MIN|MAX)\((.+?)\)(?:\s+AS\s+(\w+))?$/i);
if (m) {
const [, func, arg, alias] = m;
aggregated[alias || colExpr] = this.computeAggregate(func.toUpperCase(), groupRows, arg.trim());
}
else if (!stmt.groupBy.includes(colExpr)) {
aggregated[colExpr] = groupRows[0][colExpr];
}
}
result.push(aggregated);
}
return result;
}
computeAggregate(func, rows, col) {
const nums = rows.map((r) => r[col]).filter((v) => v !== null && v !== undefined).map(Number);
switch (func) {
case 'COUNT': return col === '*' ? rows.length : nums.length;
case 'SUM': return nums.reduce((a, b) => a + b, 0);
case 'AVG': return nums.length === 0 ? 0 : nums.reduce((a, b) => a + b, 0) / nums.length;
case 'MIN': return nums.length === 0 ? 0 : Math.min(...nums);
case 'MAX': return nums.length === 0 ? 0 : Math.max(...nums);
default: return 0;
}
}
// ---- DISTINCT(优化:列值拼接代替 JSON.stringify ----
executeDistinct(rows) {
const seen = new Set();
return rows.filter((row) => {
const key = Object.values(row).map((v) => String(v ?? '\0')).join('\x1f');
if (seen.has(key))
return false;
seen.add(key);
return true;
});
}
// ===================================================================
// 其他语句
// ===================================================================
async executeInsert(stmt) {
const schema = await this.engine.getTableSchema(stmt.into);
if (!schema)
throw new DatabaseError(`Table "${stmt.into}" does not exist`, 'TABLE_NOT_FOUND');
const colNames = stmt.columns ?? Object.keys(schema.columns);
const rows = stmt.values.map((vals) => {
const row = {};
for (let i = 0; i < colNames.length; i++) {
if (i < vals.length)
row[colNames[i]] = vals[i];
}
return row;
});
return this.engine.insert(stmt.into, rows);
}
async executeUpdate(stmt) {
const plan = compileStatement(stmt);
return this.engine.update(plan.table, plan, stmt.sets);
}
async executeDelete(stmt) {
const plan = compileStatement(stmt);
return this.engine.delete(plan.table, plan);
}
async executeCreateTable(stmt) {
// IF NOT EXISTS: 表已存在时静默返回
if (stmt.ifNotExists) {
const exists = await this.engine.hasTable(stmt.name);
if (exists)
return;
}
const columns = {};
for (const col of stmt.columns)
columns[col.name] = astColumnToColumnDef(col);
return this.engine.createTable(createSchema(stmt.name, columns));
}
async executeDropTable(stmt) {
if (stmt.ifExists) {
const exists = await this.engine.hasTable(stmt.name);
if (!exists)
return; // IF EXISTS: 表不存在时静默返回
}
return this.engine.dropTable(stmt.name);
}
async executeAlterTable(stmt) {
const exists = await this.engine.hasTable(stmt.name);
if (!exists)
throw new DatabaseError(`Table "${stmt.name}" does not exist`, 'TABLE_NOT_FOUND');
const schema = await this.engine.getTableSchema(stmt.name);
if (!schema)
return;
if (stmt.action === 'ADD') {
if (schema.columns[stmt.column.name]) {
throw new DatabaseError(`Column "${stmt.column.name}" already exists in table "${stmt.name}"`, 'COLUMN_EXISTS');
}
// 直接在 schema 引用上添加列(已有行的该列值为 undefined/default
schema.columns[stmt.column.name] = astColumnToColumnDef(stmt.column);
}
else if (stmt.action === 'DROP') {
if (!schema.columns[stmt.column.name]) {
throw new DatabaseError(`Column "${stmt.column.name}" does not exist in table "${stmt.name}"`, 'COLUMN_NOT_FOUND');
}
// 只从 schema 引用上删除列定义,不碰行数据(避免引用共享导致查询结果被篡改)
delete schema.columns[stmt.column.name];
}
}
async executeTruncateTable(stmt) {
const exists = await this.engine.hasTable(stmt.name);
if (!exists)
throw new DatabaseError(`Table "${stmt.name}" does not exist`, 'TABLE_NOT_FOUND');
return this.engine.clear(stmt.name);
}
getEngine() { return this.engine; }
// ===================================================================
// 无 GROUP BY 时的聚合计算
// ===================================================================
/** 检查 SELECT 列列表中是否包含聚合函数 */
_hasAggregateColumn(columns) {
return columns.some((col) => /^(COUNT|SUM|AVG|MIN|MAX)\(/i.test(col));
}
/** 计算单行聚合结果(无 GROUP BY) */
computeSingleAggregate(rows, stmt) {
const result = {};
for (const colExpr of stmt.columns) {
if (colExpr === '*')
continue;
const m = colExpr.match(/^(COUNT|SUM|AVG|MIN|MAX)\((.+?)\)(?:\s+AS\s+(\w+))?$/i);
if (m) {
const [, func, arg, alias] = m;
result[alias || colExpr] = this.computeAggregate(func.toUpperCase(), rows, arg.trim());
}
else {
// 非聚合列取第一行的值
result[colExpr] = rows.length > 0 ? rows[0][colExpr] : null;
}
}
return result;
}
// ===================================================================
// 子查询解析
// ===================================================================
/**
* 递归扫描 WHERE 条件,找到 $subquery 标记并执行子查询,
* 将结果替换为具体值。
*/
async resolveSubqueries(where) {
const resolved = {};
for (const [key, value] of Object.entries(where)) {
// 逻辑组合操作符
if (key === '$and' && Array.isArray(value)) {
resolved.$and = await Promise.all(value.map((sub) => this.resolveSubqueries(sub)));
continue;
}
if (key === '$or' && Array.isArray(value)) {
resolved.$or = await Promise.all(value.map((sub) => this.resolveSubqueries(sub)));
continue;
}
if (key === '$not' && typeof value === 'object' && value !== null) {
resolved.$not = await this.resolveSubqueries(value);
continue;
}
// 字段条件
if (typeof value === 'object' && value !== null) {
resolved[key] = await this.resolveOperatorSubqueries(value);
}
else {
resolved[key] = value;
}
}
return resolved;
}
/**
* 解析操作符值中嵌套的子查询
*/
async resolveOperatorSubqueries(ops) {
const resolved = {};
for (const [op, operand] of Object.entries(ops)) {
// 处理嵌套 $and/$or(在字段级条件中)
if (op === '$and' && Array.isArray(operand)) {
resolved.$and = await Promise.all(operand.map((sub) => this.resolveSubqueries(sub)));
continue;
}
if (op === '$or' && Array.isArray(operand)) {
resolved.$or = await Promise.all(operand.map((sub) => this.resolveSubqueries(sub)));
continue;
}
if (op === '$not') {
resolved.$not = typeof operand === 'object' && operand !== null
? await this.resolveOperatorSubqueries(operand)
: operand;
continue;
}
// 子查询检测
if (typeof operand === 'object' && operand !== null && '$subquery' in operand) {
const subStmt = operand.$subquery;
const subResult = await this.executeSelect(subStmt);
if (op === '$in' || op === '$nin') {
// IN 子查询 → 提取第一列的值列表
const colName = Object.keys(subResult[0] || {})[0];
const values = subResult.map((row) => row[colName]);
resolved[op] = values;
}
else {
// 标量子查询 → 取第一行第一列
if (subResult.length === 0) {
resolved[op] = null;
}
else {
const colName = Object.keys(subResult[0])[0];
resolved[op] = subResult[0][colName];
}
}
}
else {
resolved[op] = operand;
}
}
return resolved;
}
}
/**
* metona-sqlark SQL Token Types — 词法单元定义
* @module sql/tokens
*/
// ---------------------------------------------------------------------------
// Token 类型枚举
// ---------------------------------------------------------------------------
var TokenType;
(function (TokenType) {
// 关键字
TokenType["SELECT"] = "SELECT";
TokenType["FROM"] = "FROM";
TokenType["WHERE"] = "WHERE";
TokenType["INSERT"] = "INSERT";
TokenType["INTO"] = "INTO";
TokenType["VALUES"] = "VALUES";
TokenType["UPDATE"] = "UPDATE";
TokenType["SET"] = "SET";
TokenType["DELETE"] = "DELETE";
TokenType["CREATE"] = "CREATE";
TokenType["TABLE"] = "TABLE";
TokenType["DROP"] = "DROP";
TokenType["ORDER"] = "ORDER";
TokenType["BY"] = "BY";
TokenType["ASC"] = "ASC";
TokenType["DESC"] = "DESC";
TokenType["LIMIT"] = "LIMIT";
TokenType["OFFSET"] = "OFFSET";
TokenType["AND"] = "AND";
TokenType["OR"] = "OR";
TokenType["NOT"] = "NOT";
TokenType["LIKE"] = "LIKE";
TokenType["IN"] = "IN";
TokenType["PRIMARY"] = "PRIMARY";
TokenType["KEY"] = "KEY";
TokenType["UNIQUE"] = "UNIQUE";
TokenType["DEFAULT"] = "DEFAULT";
TokenType["NULL"] = "NULL";
TokenType["TRUE"] = "TRUE";
TokenType["REFERENCES"] = "REFERENCES";
TokenType["CASCADE"] = "CASCADE";
TokenType["BETWEEN"] = "BETWEEN";
TokenType["IF"] = "IF";
TokenType["EXISTS"] = "EXISTS";
TokenType["FALSE"] = "FALSE";
TokenType["ALTER"] = "ALTER";
TokenType["ADD"] = "ADD";
TokenType["TRUNCATE"] = "TRUNCATE";
// JOIN 相关
TokenType["INNER"] = "INNER";
TokenType["LEFT"] = "LEFT";
TokenType["RIGHT"] = "RIGHT";
TokenType["CROSS"] = "CROSS";
TokenType["JOIN"] = "JOIN";
TokenType["ON"] = "ON";
TokenType["AS"] = "AS";
TokenType["OUTER"] = "OUTER";
// 聚合
TokenType["GROUP"] = "GROUP";
TokenType["HAVING"] = "HAVING";
TokenType["COUNT"] = "COUNT";
TokenType["SUM"] = "SUM";
TokenType["AVG"] = "AVG";
TokenType["MIN"] = "MIN";
TokenType["MAX"] = "MAX";
TokenType["DISTINCT"] = "DISTINCT";
// 标识符 & 字面量
TokenType["IDENTIFIER"] = "IDENTIFIER";
TokenType["STRING"] = "STRING";
TokenType["NUMBER"] = "NUMBER";
// 运算符 & 分隔符
TokenType["COMMA"] = "COMMA";
TokenType["LPAREN"] = "LPAREN";
TokenType["RPAREN"] = "RPAREN";
TokenType["SEMICOLON"] = "SEMICOLON";
TokenType["EQ"] = "EQ";
TokenType["NEQ"] = "NEQ";
TokenType["GT"] = "GT";
TokenType["GTE"] = "GTE";
TokenType["LT"] = "LT";
TokenType["LTE"] = "LTE";
TokenType["STAR"] = "STAR";
TokenType["DOT"] = "DOT";
// 特殊
TokenType["EOF"] = "EOF";
TokenType["ILLEGAL"] = "ILLEGAL";
})(TokenType || (TokenType = {}));
// ---------------------------------------------------------------------------
// 关键字映射
// ---------------------------------------------------------------------------
const KEYWORDS = {
'SELECT': TokenType.SELECT,
'FROM': TokenType.FROM,
'WHERE': TokenType.WHERE,
'INSERT': TokenType.INSERT,
'INTO': TokenType.INTO,
'VALUES': TokenType.VALUES,
'UPDATE': TokenType.UPDATE,
'SET': TokenType.SET,
'DELETE': TokenType.DELETE,
'CREATE': TokenType.CREATE,
'TABLE': TokenType.TABLE,
'DROP': TokenType.DROP,
'ORDER': TokenType.ORDER,
'BY': TokenType.BY,
'ASC': TokenType.ASC,
'DESC': TokenType.DESC,
'LIMIT': TokenType.LIMIT,
'OFFSET': TokenType.OFFSET,
'AND': TokenType.AND,
'OR': TokenType.OR,
'NOT': TokenType.NOT,
'LIKE': TokenType.LIKE,
'IN': TokenType.IN,
'PRIMARY': TokenType.PRIMARY,
'KEY': TokenType.KEY,
'UNIQUE': TokenType.UNIQUE,
'DEFAULT': TokenType.DEFAULT,
'NULL': TokenType.NULL,
'TRUE': TokenType.TRUE,
'FALSE': TokenType.FALSE,
'REFERENCES': TokenType.REFERENCES,
'CASCADE': TokenType.CASCADE,
'BETWEEN': TokenType.BETWEEN,
'IF': TokenType.IF,
'EXISTS': TokenType.EXISTS,
'ALTER': TokenType.ALTER,
'ADD': TokenType.ADD,
'TRUNCATE': TokenType.TRUNCATE,
// JOIN
'INNER': TokenType.INNER,
'LEFT': TokenType.LEFT,
'RIGHT': TokenType.RIGHT,
'CROSS': TokenType.CROSS,
'JOIN': TokenType.JOIN,
'ON': TokenType.ON,
'AS': TokenType.AS,
'OUTER': TokenType.OUTER,
// 聚合
'GROUP': TokenType.GROUP,
'HAVING': TokenType.HAVING,
'COUNT': TokenType.COUNT,
'SUM': TokenType.SUM,
'AVG': TokenType.AVG,
'MIN': TokenType.MIN,
'MAX': TokenType.MAX,
'DISTINCT': TokenType.DISTINCT,
};
/**
* metona-sqlark SQL Lexer — 词法分析器
* @module sql/lexer
*
* 将 SQL 字符串切分为 Token 流。
*/
// ---------------------------------------------------------------------------
// Lexer
// ---------------------------------------------------------------------------
class Lexer {
constructor(input) {
this.position = 0;
this.readPosition = 0;
this.ch = '';
this.input = input;
this.readChar();
}
/** 读取下一个 Token */
nextToken() {
this.skipWhitespace();
let tok;
switch (this.ch) {
case ',':
tok = this.makeToken(TokenType.COMMA, ',');
break;
case '(':
tok = this.makeToken(TokenType.LPAREN, '(');
break;
case ')':
tok = this.makeToken(TokenType.RPAREN, ')');
break;
case ';':
tok = this.makeToken(TokenType.SEMICOLON, ';');
break;
case '*':
tok = this.makeToken(TokenType.STAR, '*');
break;
case '.':
tok = this.makeToken(TokenType.DOT, '.');
break;
case '=':
tok = this.makeToken(TokenType.EQ, '=');
break;
case '!':
if (this.peekChar() === '=') {
this.readChar();
tok = this.makeToken(TokenType.NEQ, '!=');
}
else {
tok = this.makeToken(TokenType.ILLEGAL, '!');
}
break;
case '>':
if (this.peekChar() === '=') {
this.readChar();
tok = this.makeToken(TokenType.GTE, '>=');
}
else {
tok = this.makeToken(TokenType.GT, '>');
}
break;
case '<':
if (this.peekChar() === '=') {
this.readChar();
tok = this.makeToken(TokenType.LTE, '<=');
}
else if (this.peekChar() === '>') {
this.readChar();
tok = this.makeToken(TokenType.NEQ, '<>');
}
else {
tok = this.makeToken(TokenType.LT, '<');
}
break;
case "'":
case '"':
tok = this.readString(this.ch);
break;
case '':
tok = { type: TokenType.EOF, value: '', position: this.position };
break;
default:
// SQL 注释: -- 行注释
if (this.ch === '-' && this.peekChar() === '-') {
this.skipLineComment();
return this.nextToken();
}
// SQL 注释: /* 块注释 */
if (this.ch === '/' && this.peekChar() === '*') {
this.skipBlockComment();
return this.nextToken();
}
if (this.isLetter(this.ch)) {
const ident = this.readIdentifier();
const keyword = KEYWORDS[ident.toUpperCase()];
tok = {
type: keyword ?? TokenType.IDENTIFIER,
value: ident,
position: this.position - ident.length,
};
return tok; // 已读取完毕,不需要再 readChar
}
else if (this.isDigit(this.ch) || (this.ch === '-' && this.isDigit(this.peekChar()))) {
const num = this.readNumber();
tok = {
type: TokenType.NUMBER,
value: num,
position: this.position - num.length,
};
return tok;
}
else {
tok = this.makeToken(TokenType.ILLEGAL, this.ch);
}
break;
}
this.readChar();
return tok;
}
// ---- 内部 ----
readChar() {
if (this.readPosition >= this.input.length) {
this.ch = '';
}
else {
this.ch = this.input[this.readPosition];
}
this.position = this.readPosition;
this.readPosition++;
}
peekChar() {
if (this.readPosition >= this.input.length)
return '';
return this.input[this.readPosition];
}
skipWhitespace() {
while (this.ch === ' ' || this.ch === '\t' || this.ch === '\n' || this.ch === '\r') {
this.readChar();
}
}
/** 跳过 -- 行注释到行尾 */
skipLineComment() {
while (this.ch !== '\n' && this.ch !== '\r' && this.ch !== '') {
this.readChar();
}
}
/** 跳过块注释 slash-star ... star-slash */
skipBlockComment() {
this.readChar(); // skip *
this.readChar(); // move past *
while (this.ch !== '' && !(this.ch === '*' && this.peekChar() === '/')) {
this.readChar();
}
if (this.ch !== '') {
this.readChar(); // skip *
this.readChar(); // skip /
}
}
readIdentifier() {
const start = this.position;
while (this.isLetter(this.ch) || this.isDigit(this.ch) || this.ch === '_') {
this.readChar();
}
return this.input.slice(start, this.position);
}
readNumber() {
const start = this.position;
// 负号
if (this.ch === '-')
this.readChar();
while (this.isDigit(this.ch)) {
this.readChar();
}
// 小数点
if (this.ch === '.' && this.isDigit(this.peekChar())) {
this.readChar();
while (this.isDigit(this.ch)) {
this.readChar();
}
}
return this.input.slice(start, this.position);
}
readString(quote) {
const start = this.position + 1; // 跳过一个引号
this.readChar(); // 跳过开始引号
let value = '';
while (this.ch !== quote && this.ch !== '') {
// 处理转义
if (this.ch === '\\' && this.peekChar() === quote) {
this.readChar();
value += quote;
}
else {
value += this.ch;
}
this.readChar();
}
// 跳过结束引号(在 readChar 之后才会调用,所以这里不需要处理)
return {
type: TokenType.STRING,
value,
position: start,
};
}
isLetter(ch) {
return /[a-zA-Z_]/.test(ch);
}
isDigit(ch) {
return /[0-9]/.test(ch);
}
makeToken(type, value) {
return { type, value, position: this.position };
}
}
// ---------------------------------------------------------------------------
// 便捷方法:一次性词法分析
// ---------------------------------------------------------------------------
/** 将 SQL 字符串解析为 Token 列表 */
function tokenize(sql) {
const lexer = new Lexer(sql);
const tokens = [];
let tok = lexer.nextToken();
while (tok.type !== TokenType.EOF) {
tokens.push(tok);
tok = lexer.nextToken();
}
tokens.push(tok); // EOF
return tokens;
}
/**
* metona-sqlark SQL Parser — 递归下降语法分析器
* @module sql/parser
*
* Token 流 → AST Statement。
* 支持的语法是标准 SQL 的子集。
*/
// ---------------------------------------------------------------------------
// Parser
// ---------------------------------------------------------------------------
class Parser {
constructor(sql) {
this.lexer = new Lexer(sql);
// 预读两个 token
this.nextToken();
this.nextToken();
}
/** 解析完整 SQL 语句 */
parseStatement() {
switch (this.curToken.type) {
case TokenType.SELECT:
return this.parseSelect();
case TokenType.INSERT:
return this.parseInsert();
case TokenType.UPDATE:
return this.parseUpdate();
case TokenType.DELETE:
return this.parseDelete();
case TokenType.CREATE:
return this.parseCreateTable();
case TokenType.DROP:
return this.parseDropTable();
case TokenType.ALTER:
return this.parseAlterTable();
case TokenType.TRUNCATE:
return this.parseTruncateTable();
default:
throw this.error(`Unexpected token "${this.curToken.value}"`);
}
}
// ===================================================================
// SELECT
// ===================================================================
parseSelect() {
this.expect(TokenType.SELECT);
// DISTINCT(可选)
let distinct = false;
if (this.curTokenIs(TokenType.DISTINCT)) {
distinct = true;
this.nextToken();
}
// 列
const columns = [];
if (this.curTokenIs(TokenType.STAR)) {
columns.push('*');
this.nextToken();
}
else {
columns.push(...this.parseColumnList());
}
// FROM
this.expect(TokenType.FROM);
const tableName = this.expectIdentifier('table name');
// 表别名(可选)
let alias;
if (this.curTokenIs(TokenType.AS)) {
this.nextToken();
alias = this.expectIdentifier('alias');
}
else if (this.curToken.type === TokenType.IDENTIFIER && !this._isReservedAfterFrom()) {
alias = this.curToken.value;
this.nextToken();
}
const stmt = {
type: 'SELECT',
columns,
distinct: distinct || undefined,
from: tableName,
alias,
where: {},
};
// JOIN 子句(可选,支持多个)
const joins = this.parseJoinClauses();
if (joins.length > 0) {
stmt.joins = joins;
}
// WHERE(可选)
if (this.curTokenIs(TokenType.WHERE)) {
this.nextToken();
stmt.where = this.parseCondition();
}
// GROUP BY(可选)
if (this.curTokenIs(TokenType.GROUP)) {
this.nextToken();
this.expect(TokenType.BY);
stmt.groupBy = this.parseIdentifierList();
}
// HAVING(可选)
if (this.curTokenIs(TokenType.HAVING)) {
this.nextToken();
stmt.having = this.parseCondition();
}
// ORDER BY(可选)
if (this.curTokenIs(TokenType.ORDER)) {
this.nextToken();
this.expect(TokenType.BY);
stmt.orderBy = this.parseOrderByList();
}
// LIMIT(可选)
if (this.curTokenIs(TokenType.LIMIT)) {
this.nextToken();
stmt.limit = this.expectNumber('LIMIT value');
}
// OFFSET(可选)
if (this.curTokenIs(TokenType.OFFSET)) {
this.nextToken();
stmt.offset = this.expectNumber('OFFSET value');
}
return stmt;
}
/** 解析 JOIN 子句列表 */
parseJoinClauses() {
const joins = [];
while (this._isJoinKeyword()) {
joins.push(this.parseJoinClause());
}
return joins;
}
_isJoinKeyword() {
return (this.curTokenIs(TokenType.INNER) ||
this.curTokenIs(TokenType.LEFT) ||
this.curTokenIs(TokenType.RIGHT) ||
this.curTokenIs(TokenType.CROSS) ||
this.curTokenIs(TokenType.JOIN));
}
/** 解析单个 JOIN 子句 */
parseJoinClause() {
let type = 'INNER';
if (this.curTokenIs(TokenType.INNER)) {
type = 'INNER';
this.nextToken();
}
else if (this.curTokenIs(TokenType.LEFT)) {
type = 'LEFT';
this.nextToken();
if (this.curTokenIs(TokenType.OUTER))
this.nextToken(); // 可选 OUTER
}
else if (this.curTokenIs(TokenType.RIGHT)) {
type = 'RIGHT';
this.nextToken();
if (this.curTokenIs(TokenType.OUTER))
this.nextToken();
}
else if (this.curTokenIs(TokenType.CROSS)) {
type = 'CROSS';
this.nextToken();
}
this.expect(TokenType.JOIN);
const tableName = this.expectIdentifier('table name');
// JOIN 表别名(可选)
let alias;
if (this.curTokenIs(TokenType.AS)) {
this.nextToken();
alias = this.expectIdentifier('alias');
}
else if (this.curToken.type === TokenType.IDENTIFIER && !this._isJoinReserved()) {
alias = this.curToken.value;
this.nextToken();
}
// ON 条件(CROSS JOIN 不需要 ON
let on = {};
if (type !== 'CROSS' && this.curTokenIs(TokenType.ON)) {
this.nextToken();
on = this.parseCondition();
}
return { type, table: tableName, alias, on };
}
/** 判断当前 token 是否为 FROM 之后的保留字 */
_isReservedAfterFrom() {
return (this.curTokenIs(TokenType.WHERE) ||
this.curTokenIs(TokenType.ORDER) ||
this.curTokenIs(TokenType.LIMIT) ||
this.curTokenIs(TokenType.OFFSET) ||
this.curTokenIs(TokenType.GROUP) ||
this._isJoinKeyword());
}
_isJoinReserved() {
return (this.curTokenIs(TokenType.ON) ||
this.curTokenIs(TokenType.WHERE) ||
this.curTokenIs(TokenType.ORDER) ||
this.curTokenIs(TokenType.LIMIT) ||
this._isJoinKeyword());
}
// ===================================================================
// INSERT
// ===================================================================
parseInsert() {
this.expect(TokenType.INSERT);
this.expect(TokenType.INTO);
const tableName = this.expectIdentifier('table name');
// 列名(可选)
let columns;
if (this.curTokenIs(TokenType.LPAREN)) {
this.nextToken();
columns = this.parseIdentifierList();
this.expect(TokenType.RPAREN);
}
// VALUES
this.expect(TokenType.VALUES);
// 值列表
const values = [];
do {
if (this.curTokenIs(TokenType.COMMA)) {
this.nextToken();
}
this.expect(TokenType.LPAREN);
const rowValues = this.parseValueList();
this.expect(TokenType.RPAREN);
values.push(rowValues);
} while (this.curTokenIs(TokenType.COMMA));
return {
type: 'INSERT',
into: tableName,
columns,
values,
};
}
// ===================================================================
// UPDATE
// ===================================================================
parseUpdate() {
this.expect(TokenType.UPDATE);
const tableName = this.expectIdentifier('table name');
this.expect(TokenType.SET);
// SET col=val, ...
const sets = {};
do {
if (this.curTokenIs(TokenType.COMMA))
this.nextToken();
const col = this.expectIdentifier('column name');
this.expect(TokenType.EQ);
sets[col] = this.parseValue();
} while (this.curTokenIs(TokenType.COMMA));
let where = {};
if (this.curTokenIs(TokenType.WHERE)) {
this.nextToken();
where = this.parseCondition();
}
return { type: 'UPDATE', table: tableName, sets, where };
}
// ===================================================================
// DELETE
// ===================================================================
parseDelete() {
this.expect(TokenType.DELETE);
this.expect(TokenType.FROM);
const tableName = this.expectIdentifier('table name');
let where = {};
if (this.curTokenIs(TokenType.WHERE)) {
this.nextToken();
where = this.parseCondition();
}
return { type: 'DELETE', from: tableName, where };
}
// ===================================================================
// CREATE TABLE
// ===================================================================
parseCreateTable() {
this.expect(TokenType.CREATE);
this.expect(TokenType.TABLE);
// IF NOT EXISTS(可选)
let ifNotExists = false;
if (this.curTokenIs(TokenType.IF)) {
this.nextToken();
this.expect(TokenType.NOT);
this.expect(TokenType.EXISTS);
ifNotExists = true;
}
const tableName = this.expectIdentifier('table name');
this.expect(TokenType.LPAREN);
const columns = [];
do {
if (this.curTokenIs(TokenType.COMMA))
this.nextToken();
columns.push(this.parseColumnDef());
} while (this.curTokenIs(TokenType.COMMA));
this.expect(TokenType.RPAREN);
return { type: 'CREATE_TABLE', name: tableName, columns, ifNotExists: ifNotExists || undefined };
}
parseColumnDef() {
const name = this.expectIdentifier('column name');
const type = this.expectIdentifier('column type').toLowerCase();
const col = { name, type };
// 修饰符
while (this.curTokenIs(TokenType.PRIMARY) ||
this.curTokenIs(TokenType.UNIQUE) ||
this.curTokenIs(TokenType.NOT) ||
this.curTokenIs(TokenType.DEFAULT) ||
this.curTokenIs(TokenType.REFERENCES)) {
if (this.curTokenIs(TokenType.PRIMARY)) {
this.nextToken();
this.expect(TokenType.KEY);
col.primaryKey = true;
}
else if (this.curTokenIs(TokenType.UNIQUE)) {
this.nextToken();
col.unique = true;
}
else if (this.curTokenIs(TokenType.NOT)) {
this.nextToken();
this.expect(TokenType.NULL);
col.required = true;
}
else if (this.curTokenIs(TokenType.DEFAULT)) {
this.nextToken();
col.default = this.parseValue();
}
else if (this.curTokenIs(TokenType.REFERENCES)) {
this.nextToken();
const refTable = this.expectIdentifier('referenced table');
this.expect(TokenType.LPAREN);
const refCol = this.expectIdentifier('referenced column');
this.expect(TokenType.RPAREN);
col.references = `${refTable}.${refCol}`;
// ON DELETE / ON UPDATE
while (this.curTokenIs(TokenType.ON)) {
this.nextToken();
if (this.curTokenIs(TokenType.DELETE)) {
this.nextToken();
col.onDelete = this.parseCascadeAction();
}
else if (this.curTokenIs(TokenType.UPDATE)) {
this.nextToken();
col.onUpdate = this.parseCascadeAction();
}
else {
break;
}
}
}
else {
break;
}
}
return col;
}
/** 解析 CASCADE | SET NULL | RESTRICT */
parseCascadeAction() {
if (this.curTokenIs(TokenType.CASCADE)) {
this.nextToken();
return 'CASCADE';
}
if (this.curTokenIs(TokenType.SET)) {
this.nextToken();
this.expect(TokenType.NULL);
return 'SET NULL';
}
// RESTRICT 或默认
if (this.curToken.type === TokenType.IDENTIFIER && this.curToken.value.toUpperCase() === 'RESTRICT') {
this.nextToken();
return 'RESTRICT';
}
return 'RESTRICT';
}
// ===================================================================
// ALTER TABLE
// ===================================================================
parseAlterTable() {
this.expect(TokenType.ALTER);
this.expect(TokenType.TABLE);
const tableName = this.expectIdentifier('table name');
// ADD COLUMN / DROP COLUMN
let action;
if (this.curTokenIs(TokenType.ADD)) {
action = 'ADD';
this.nextToken();
// Optional COLUMN keyword
if (this.curToken.type === TokenType.IDENTIFIER && this.curToken.value.toUpperCase() === 'COLUMN') {
this.nextToken();
}
const col = this.parseColumnDef();
return { type: 'ALTER_TABLE', name: tableName, action, column: col };
}
else if (this.curTokenIs(TokenType.DROP) ||
(this._isKeywordAsIdent() && this.curToken.value.toUpperCase() === 'DROP')) {
action = 'DROP';
this.nextToken();
// Optional COLUMN keyword
if (this.curToken.type === TokenType.IDENTIFIER && this.curToken.value.toUpperCase() === 'COLUMN') {
this.nextToken();
}
const colName = this.expectIdentifier('column name');
return { type: 'ALTER_TABLE', name: tableName, action, column: { name: colName, type: 'string' } };
}
else {
throw this.error('Expected ADD or DROP in ALTER TABLE');
}
}
// ===================================================================
// TRUNCATE TABLE
// ===================================================================
parseTruncateTable() {
this.expect(TokenType.TRUNCATE);
this.expect(TokenType.TABLE);
const tableName = this.expectIdentifier('table name');
return { type: 'TRUNCATE_TABLE', name: tableName };
}
// ===================================================================
// DROP TABLE
// ===================================================================
parseDropTable() {
this.expect(TokenType.DROP);
this.expect(TokenType.TABLE);
// IF EXISTS(可选)
let ifExists = false;
if (this.curTokenIs(TokenType.IF)) {
this.nextToken();
this.expect(TokenType.EXISTS);
ifExists = true;
}
const tableName = this.expectIdentifier('table name');
return { type: 'DROP_TABLE', name: tableName, ifExists: ifExists || undefined };
}
// ===================================================================
// 条件表达式
// ===================================================================
/** condition → simple_cond ((AND|OR) simple_cond)* */
parseCondition() {
let left = this.parseSimpleCondition();
while (this.curTokenIs(TokenType.AND) || this.curTokenIs(TokenType.OR)) {
const isAnd = this.curTokenIs(TokenType.AND);
this.nextToken();
const right = this.parseSimpleCondition();
if (isAnd) {
// 合并到 $and
left = { $and: [left, right] };
}
else {
left = { $or: [left, right] };
}
}
return left;
}
/** simple_cond → column op value | column IS [NOT] NULL | column [NOT] LIKE pattern
* | column [NOT] IN (values) | NOT condition | (condition) */
parseSimpleCondition() {
// NOT expr(注意 NOT IN / NOT LIKE 不作为通用 NOT
if (this.curTokenIs(TokenType.NOT) && !this._isNotInOrLike()) {
this.nextToken();
const inner = this.parseSimpleCondition();
return { $not: inner };
}
// (condition)
if (this.curTokenIs(TokenType.LPAREN)) {
this.nextToken();
const inner = this.parseCondition();
this.expect(TokenType.RPAREN);
return inner;
}
// column
const column = this.parseColumnRef();
// IS NULL / IS NOT NULL
if (this.curTokenIs(TokenType.IDENTIFIER) && this.curToken.value.toUpperCase() === 'IS') {
this.nextToken();
const isNot = this.curTokenIs(TokenType.NOT);
if (isNot)
this.nextToken();
this.expect(TokenType.NULL);
const result = {};
result[column] = isNot ? { $ne: null } : { $eq: null };
return result;
}
// BETWEEN val1 AND val2
if (this.curTokenIs(TokenType.BETWEEN)) {
this.nextToken();
const low = this.parseValue();
this.expect(TokenType.AND);
const high = this.parseValue();
const result = {};
result[column] = { $gte: low, $lte: high };
return result;
}
// NOT BETWEEN val1 AND val2
if (this.curTokenIs(TokenType.NOT) && this.peekTokenIs(TokenType.BETWEEN)) {
this.nextToken(); // skip NOT
this.nextToken(); // skip BETWEEN
const low = this.parseValue();
this.expect(TokenType.AND);
const high = this.parseValue();
const result = {};
result[column] = { $not: { $gte: low, $lte: high } };
return result;
}
// NOT LIKE / NOT INNOT 后紧跟 LIKE 或 IN
if (this.curTokenIs(TokenType.NOT)) {
if (this.peekTokenIs(TokenType.IN)) {
// NOT IN
this.nextToken(); // skip NOT
this.nextToken(); // skip IN
this.expect(TokenType.LPAREN);
if (this.curTokenIs(TokenType.SELECT)) {
const subquery = this.parseSelect();
this.expect(TokenType.RPAREN);
const result = {};
result[column] = { $nin: { $subquery: subquery } };
return result;
}
const values = this.parseValueList();
this.expect(TokenType.RPAREN);
const result = {};
result[column] = { $nin: values };
return result;
}
else if (this.peekTokenIs(TokenType.LIKE)) {
// NOT LIKE
this.nextToken(); // skip NOT
this.nextToken(); // skip LIKE
const pattern = this.parseValue();
const result = {};
result[column] = { $not: { $like: pattern } };
return result;
}
}
// LIKE
if (this.curTokenIs(TokenType.LIKE)) {
this.nextToken();
const pattern = this.parseValue();
const result = {};
result[column] = { $like: pattern };
return result;
}
// IN
if (this.curTokenIs(TokenType.IN)) {
this.nextToken();
this.expect(TokenType.LPAREN);
// 子查询: IN (SELECT ...)
if (this.curTokenIs(TokenType.SELECT)) {
const subquery = this.parseSelect();
this.expect(TokenType.RPAREN);
const result = {};
result[column] = { $in: { $subquery: subquery } };
return result;
}
const values = this.parseValueList();
this.expect(TokenType.RPAREN);
const result = {};
result[column] = { $in: values };
return result;
}
// 比较运算符
const op = this.parseComparisonOp();
// 子查询: op (SELECT ...)
if (this.curTokenIs(TokenType.LPAREN) && this.peekTokenIs(TokenType.SELECT)) {
this.nextToken(); // skip (
const subquery = this.parseSelect();
this.expect(TokenType.RPAREN);
const result = {};
result[column] = { [op]: { $subquery: subquery } };
return result;
}
// 尝试解析列引用(identifier DOT identifier 格式)
let value;
if ((this.curToken.type === TokenType.IDENTIFIER || this._isKeywordAsIdent()) &&
this.peekTokenIs(TokenType.DOT)) {
const colRef = this.parseColumnRef();
value = { $col: colRef };
}
else {
value = this.parseValue();
}
const result = {};
result[column] = { [op]: value };
return result;
}
/** 判断当前 NOT 是否为 NOT IN / NOT LIKE 的一部分(不应作为通用 NOT 处理) */
_isNotInOrLike() {
return this.peekTokenIs(TokenType.IN) || this.peekTokenIs(TokenType.LIKE);
}
peekTokenIs(type) {
return this.peekToken.type === type;
}
parseComparisonOp() {
switch (this.curToken.type) {
case TokenType.EQ:
this.nextToken();
return '$eq';
case TokenType.NEQ:
this.nextToken();
return '$ne';
case TokenType.GT:
this.nextToken();
return '$gt';
case TokenType.GTE:
this.nextToken();
return '$gte';
case TokenType.LT:
this.nextToken();
return '$lt';
case TokenType.LTE:
this.nextToken();
return '$lte';
default:
throw this.error(`Expected comparison operator, got "${this.curToken.value}"`);
}
}
// ===================================================================
// 辅助解析
// ===================================================================
parseColumnList() {
const cols = [];
cols.push(this.parseColumnRef());
while (this.curTokenIs(TokenType.COMMA)) {
this.nextToken();
cols.push(this.parseColumnRef());
}
return cols;
}
/** 解析列引用:支持 'col'、'table.col' 和 'COUNT(*)'/'SUM(col)' 等 */
parseColumnRef() {
// 聚合函数?
if (this.curTokenIs(TokenType.COUNT) ||
this.curTokenIs(TokenType.SUM) ||
this.curTokenIs(TokenType.AVG) ||
this.curTokenIs(TokenType.MIN) ||
this.curTokenIs(TokenType.MAX)) {
return this.parseAggregateCall();
}
const first = this.expectIdentifier('column name');
if (this.curTokenIs(TokenType.DOT)) {
this.nextToken();
const second = this.expectIdentifier('column name');
return `${first}.${second}`;
}
return first;
}
/** 解析聚合函数调用: COUNT(*), SUM(col), AVG(col), MIN(col), MAX(col) */
parseAggregateCall() {
const func = this.curToken.value.toUpperCase();
this.nextToken();
this.expect(TokenType.LPAREN);
let arg;
if (this.curTokenIs(TokenType.STAR)) {
arg = '*';
this.nextToken();
}
else {
arg = this.parseColumnRef();
}
this.expect(TokenType.RPAREN);
// 可选别名: AS alias
let alias = '';
if (this.curTokenIs(TokenType.AS)) {
this.nextToken();
alias = this.expectIdentifier('alias');
}
else if (this.curToken.type === TokenType.IDENTIFIER && this._isAggregateAlias()) {
alias = this.curToken.value;
this.nextToken();
}
if (alias) {
return `${func}(${arg}) AS ${alias}`;
}
return `${func}(${arg})`;
}
_isAggregateAlias() {
return !this._isReservedAfterFrom() && !this._isJoinKeyword();
}
parseIdentifierList() {
const ids = [];
ids.push(this.expectIdentifier('identifier'));
while (this.curTokenIs(TokenType.COMMA)) {
this.nextToken();
ids.push(this.expectIdentifier('identifier'));
}
return ids;
}
parseValueList() {
const vals = [];
vals.push(this.parseValue());
while (this.curTokenIs(TokenType.COMMA)) {
this.nextToken();
vals.push(this.parseValue());
}
return vals;
}
parseOrderByList() {
const list = [];
list.push(this.parseOrderBy());
while (this.curTokenIs(TokenType.COMMA)) {
this.nextToken();
list.push(this.parseOrderBy());
}
return list;
}
parseOrderBy() {
const column = this.expectIdentifier('column name');
let direction = 'asc';
if (this.curTokenIs(TokenType.ASC)) {
this.nextToken();
}
else if (this.curTokenIs(TokenType.DESC)) {
direction = 'desc';
this.nextToken();
}
return { column, direction };
}
/** 解析字面量值 */
parseValue() {
switch (this.curToken.type) {
case TokenType.STRING: {
const val = this.curToken.value;
this.nextToken();
return val;
}
case TokenType.NUMBER: {
const val = Number(this.curToken.value);
this.nextToken();
return val;
}
case TokenType.TRUE:
this.nextToken();
return true;
case TokenType.FALSE:
this.nextToken();
return false;
case TokenType.NULL:
this.nextToken();
return null;
default:
throw this.error(`Expected value, got "${this.curToken.value}"`);
}
}
// ===================================================================
// Token 操作
// ===================================================================
nextToken() {
this.curToken = this.peekToken;
this.peekToken = this.lexer.nextToken();
}
curTokenIs(type) {
return this.curToken.type === type;
}
expect(type) {
if (this.curTokenIs(type)) {
this.nextToken();
return;
}
throw this.error(`Expected ${type}, got "${this.curToken.value}"`);
}
expectIdentifier(context) {
if (this.curToken.type === TokenType.IDENTIFIER || this._isKeywordAsIdent()) {
const val = this.curToken.value;
this.nextToken();
return val;
}
throw this.error(`Expected ${context}, got "${this.curToken.value}"`);
}
/** 关键字可以作为标识符(如列名等于关键字) */
_isKeywordAsIdent() {
return (this.curToken.type !== TokenType.EOF &&
this.curToken.type !== TokenType.ILLEGAL &&
this.curToken.type !== TokenType.STRING &&
this.curToken.type !== TokenType.NUMBER &&
this.curToken.type !== TokenType.COMMA &&
this.curToken.type !== TokenType.LPAREN &&
this.curToken.type !== TokenType.RPAREN &&
this.curToken.type !== TokenType.SEMICOLON &&
this.curToken.type !== TokenType.EQ &&
this.curToken.type !== TokenType.NEQ &&
this.curToken.type !== TokenType.GT &&
this.curToken.type !== TokenType.GTE &&
this.curToken.type !== TokenType.LT &&
this.curToken.type !== TokenType.LTE &&
this.curToken.type !== TokenType.DOT &&
this.curToken.type !== TokenType.STAR);
}
expectNumber(context) {
if (this.curToken.type === TokenType.NUMBER) {
const val = Number(this.curToken.value);
this.nextToken();
return val;
}
throw this.error(`Expected ${context}, got "${this.curToken.value}"`);
}
error(msg) {
return new DatabaseError(`Parse error at position ${this.curToken.position}: ${msg}`, 'PARSE_ERROR');
}
}
// ---------------------------------------------------------------------------
// 便捷方法
// ---------------------------------------------------------------------------
/** 解析 SQL 字符串为 AST Statement */
function parse(sql) {
const parser = new Parser(sql);
const stmt = parser.parseStatement();
return stmt;
}
/**
* metona-sqlark Transaction — 事务管理
* @module transaction
*
* v0.1.13: 支持真正的回滚 — 利用引擎层 begin/commit/rollback 实现原子性。
*/
// ---------------------------------------------------------------------------
// Transaction
// ---------------------------------------------------------------------------
class Transaction {
constructor(engine) {
this.tables = new Map();
this.completed = false;
this.engine = engine;
}
/** 获取表操作对象 */
table(tableName) {
let t = this.tables.get(tableName);
if (!t) {
t = new Table(this.engine, tableName);
this.tables.set(tableName, t);
}
return t;
}
/** 标记事务完成(由 TransactionManager 调用) */
_markCompleted() {
this.completed = true;
}
/** 是否已完成 */
isCompleted() {
return this.completed;
}
}
// ---------------------------------------------------------------------------
// TransactionManager
// ---------------------------------------------------------------------------
class TransactionManager {
constructor(engine) {
this.engine = engine;
}
/** 执行事务 — 支持自动回滚 */
async execute(fn) {
const trx = new Transaction(this.engine);
// 开始引擎层事务
await this.engine.beginTransaction();
try {
const result = await fn(trx);
// 成功 → 提交
await this.engine.commitTransaction();
trx._markCompleted();
return result;
}
catch (error) {
// 失败 → 回滚
await this.engine.rollbackTransaction();
if (error instanceof DatabaseError)
throw error;
throw new DatabaseError(`Transaction failed: ${error.message}`, 'TRANSACTION_ERROR', error);
}
}
}
/**
* metona-sqlark Plugin — 插件系统
* @module plugin
*
* 管理插件的注册、生命周期和钩子调度。
*/
// ---------------------------------------------------------------------------
// PluginManager
// ---------------------------------------------------------------------------
class PluginManager {
constructor() {
this.plugins = [];
this.hooks = new Map();
}
/** 注册插件 */
register(plugin, db) {
// 按优先级插入
const priority = plugin.priority ?? 0;
const insertIndex = this.plugins.findIndex((p) => (p.priority ?? 0) < priority);
if (insertIndex === -1) {
this.plugins.push(plugin);
}
else {
this.plugins.splice(insertIndex, 0, plugin);
}
// 安装(传入 db 实例)
plugin.install(db);
}
/** 卸载插件 */
unregister(pluginName) {
const idx = this.plugins.findIndex((p) => p.name === pluginName);
if (idx !== -1) {
this.plugins[idx].destroy();
this.plugins.splice(idx, 1);
}
}
/** 获取所有已注册插件 */
getPlugins() {
return [...this.plugins];
}
/** 添加钩子回调 */
on(hook, callback) {
const callbacks = this.hooks.get(hook) ?? [];
callbacks.push(callback);
this.hooks.set(hook, callbacks);
}
/** 移除钩子回调 */
off(hook, callback) {
const callbacks = this.hooks.get(hook);
if (callbacks) {
const idx = callbacks.indexOf(callback);
if (idx !== -1)
callbacks.splice(idx, 1);
}
}
/** 触发钩子 */
async trigger(hook, ...args) {
const callbacks = this.hooks.get(hook);
if (callbacks) {
for (const cb of callbacks) {
await cb(...args);
}
}
}
/** 销毁所有插件 */
destroy() {
for (const plugin of this.plugins) {
try {
plugin.destroy();
}
catch (e) {
// eslint-disable-next-line no-console
console.warn(`[metona-sqlark] Plugin "${plugin.name}" destroy error:`, e);
}
}
this.plugins = [];
this.hooks.clear();
}
}
/**
* metona-sqlark Core — 数据库主类
* @module core
*
* 管理数据库生命周期、引擎调度、表操作、SQL 查询、事务和插件。
*/
// ---------------------------------------------------------------------------
// MetonaSqlark
// ---------------------------------------------------------------------------
class MetonaSqlark {
/** 获取版本号 */
get version() { return this._version; }
/** 查询结果行数上限 */
get maxRowsPerQuery() { return this.config.maxRowsPerQuery ?? 0; }
/** 调试模式 */
get debug() { return this.config.debug ?? false; }
constructor(config) {
this.ready = false;
this.tableCache = new Map();
// ---- 发布订阅 ----
this.listeners = new Map();
// ---- 迁移 ----
this.migrations = new Map();
this.config = config;
this.name = config.name ?? DB_DEFAULTS.name;
this.mode = config.mode ?? DB_DEFAULTS.mode;
this._version = config.version ?? DB_DEFAULTS.version;
this.pluginManager = new PluginManager();
}
// ---- 初始化 ----
/** 初始化数据库(创建引擎、打开连接) */
async init() {
// 创建引擎
this.engine = this.createEngine();
// 打开连接
await this.engine.open(this.name, this.version);
// 初始化执行器和事务管理器
this.executor = new QueryExecutor(this.engine, this.maxRowsPerQuery);
this.transactionManager = new TransactionManager(this.engine);
// 注册插件
if (this.config.plugins) {
for (const plugin of this.config.plugins) {
this.pluginManager.register(plugin, this);
}
}
this.ready = true;
// 回调
if (this.config.onReady) {
this.config.onReady(this);
}
}
/** 检查是否就绪 */
isReady() {
return this.ready;
}
// ---- 表管理 ----
/** 创建表 */
async defineTable(name, columns) {
this.ensureReady();
const schema = createSchema(name, columns);
try {
await this.pluginManager.trigger('beforeCreateTable', schema);
await this.engine.createTable(schema);
await this.pluginManager.trigger('afterCreateTable', schema);
}
catch (error) {
this._onError(error);
throw error;
}
// 清除缓存
this.tableCache.delete(name);
}
/** 获取表操作对象 */
table(name) {
this.ensureReady();
let t = this.tableCache.get(name);
if (!t) {
t = new Table(this.engine, name, this.executor);
this.tableCache.set(name, t);
}
return t;
}
/** 删除表 */
async dropTable(name) {
this.ensureReady();
try {
await this.pluginManager.trigger('beforeDropTable', name);
await this.engine.dropTable(name);
await this.pluginManager.trigger('afterDropTable', name);
}
catch (error) {
this._onError(error);
throw error;
}
this.tableCache.delete(name);
}
/** 获取所有表名 */
async getTableNames() {
this.ensureReady();
return this.engine.getTableNames();
}
// ---- SQL 查询 ----
/** 执行 SQL 字符串查询 */
async query(sql) {
this.ensureReady();
const startTime = this.debug ? Date.now() : 0;
await this.pluginManager.trigger('beforeQuery', sql);
let result;
try {
const stmt = parse(sql);
result = await this.executor.execute(stmt);
}
catch (error) {
this._onError(error);
throw error;
}
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;
}
// ---- 事务 ----
/** 执行事务 */
async transaction(fn) {
this.ensureReady();
await this.pluginManager.trigger('beforeTransaction');
try {
const result = await this.transactionManager.execute(fn);
await this.pluginManager.trigger('afterTransaction');
return result;
}
catch (error) {
this._onError(error);
throw error;
}
}
// ---- 导入导出 ----
/** 导出表数据为 JSON */
async exportTable(tableName) {
this.ensureReady();
return this.engine.find(tableName, { table: tableName });
}
/** 导入 JSON 数据到表 */
async importTable(tableName, data) {
this.ensureReady();
try {
return await this.engine.insert(tableName, data);
}
catch (error) {
this._onError(error);
throw error;
}
}
/** 导出整个数据库为 JSON */
async exportAll() {
this.ensureReady();
const result = {};
const names = await this.engine.getTableNames();
for (const name of names) {
result[name] = await this.engine.find(name, { table: name });
}
return result;
}
/** 订阅表变更 */
subscribe(tableName, callback) {
const key = `change:${tableName}`;
if (!this.listeners.has(key))
this.listeners.set(key, new Set());
this.listeners.get(key).add(callback);
return () => this.listeners.get(key)?.delete(callback);
}
/** 触发变更事件 */
emit(tableName, event) {
const key = `change:${tableName}`;
this.listeners.get(key)?.forEach((cb) => cb(event));
}
/** 注册迁移 */
addMigration(version, up) {
this.migrations.set(version, up);
}
/** 执行迁移到指定版本 */
async migrateTo(targetVersion) {
this.ensureReady();
for (const [version, up] of [...this.migrations.entries()].sort((a, b) => a[0] - b[0])) {
if (version <= targetVersion && version > this.version) {
await up(this);
this._version = version;
}
}
}
// ---- 插件 ----
/** 获取插件管理器 */
getPluginManager() {
return this.pluginManager;
}
/** 注册钩子 */
on(hook, callback) {
this.pluginManager.on(hook, callback);
}
// ---- 生命周期 ----
/** 关闭数据库 */
async close() {
this.pluginManager.destroy();
await this.engine.close();
this.tableCache.clear();
this.ready = false;
}
/** 获取底层引擎 */
getEngine() {
return this.engine;
}
// ---- 内部 ----
createEngine() {
const mode = this.mode;
const diskEngine = this.config.diskEngine ?? 'indexeddb';
switch (mode) {
case 'memory':
return new MemoryEngine();
case 'disk':
return diskEngine === 'opfs' ? new OPFSEngine() : new IndexedDBEngine();
case 'aria':
return new AriaEngine({ storageBackend: diskEngine === 'opfs' ? 'opfs' : 'indexeddb' });
case 'hybrid':
return new HybridEngine(diskEngine);
default:
throw new DatabaseError(`Unknown storage mode: ${mode}`, 'CONFIG_ERROR');
}
}
ensureReady() {
if (!this.ready) {
throw new DatabaseError('Database not initialized. Call await db.init() first.', 'DB_NOT_READY');
}
}
/** 错误回调分发 */
_onError(error) {
if (this.config.onError) {
try {
this.config.onError(error);
}
catch { /* 避免回调自身异常影响主流程 */ }
}
}
/** 调试日志 */
_debug(msg, ...args) {
if (this.debug) {
console.debug(`[MetonaSqlark:${this.name}] ${msg}`, ...args);
}
}
}
/**
* metona-sqlark Connection Manager — 数据库实例连接池
* @module connection-manager
*
* v0.1.13: 避免重复创建同名数据库实例,通过 connect() 复用已有连接。
* 管理实例生命周期,防止重复 open IndexedDB。
*/
// ---------------------------------------------------------------------------
// ConnectionManager
// ---------------------------------------------------------------------------
class ConnectionManager {
constructor() {
/** 活跃连接:dbName → MetonaSqlark */
this.connections = new Map();
/** 连接引用计数:dbName → count */
this.refCount = new Map();
}
/**
* 获取或创建数据库实例
*
* 如果同名数据库已打开,复用已有实例并增加引用计数。
* 否则创建新实例。
*
* @example
* ```ts
* const db = await MetonaSqlark.connect({ name: 'my-app', mode: 'hybrid' });
* // ... use db
* await db.disconnect(); // 引用计数 -1,归零时自动关闭
* ```
*/
async connect(config) {
const name = config.name;
// 已有连接 → 复用
const existing = this.connections.get(name);
if (existing && existing.isReady()) {
const count = (this.refCount.get(name) ?? 0) + 1;
this.refCount.set(name, count);
return existing;
}
// 创建新连接
const db = new MetonaSqlark(config);
await db.init();
this.connections.set(name, db);
this.refCount.set(name, 1);
// 注入 disconnect 方法
db.disconnect = async () => {
await this.release(name);
};
return db;
}
/**
* 释放连接引用。引用计数归零时自动关闭数据库。
*/
async release(dbName) {
const count = (this.refCount.get(dbName) ?? 1) - 1;
if (count <= 0) {
const db = this.connections.get(dbName);
if (db) {
await db.close();
this.connections.delete(dbName);
}
this.refCount.delete(dbName);
}
else {
this.refCount.set(dbName, count);
}
}
/**
* 强制关闭指定数据库(忽略引用计数)
*/
async forceClose(dbName) {
const db = this.connections.get(dbName);
if (db) {
await db.close();
this.connections.delete(dbName);
}
this.refCount.delete(dbName);
}
/**
* 强制关闭所有连接
*/
async closeAll() {
for (const [, db] of this.connections) {
try {
await db.close();
}
catch { /* ignore */ }
}
this.connections.clear();
this.refCount.clear();
}
/**
* 获取所有活跃连接名
*/
getActiveConnections() {
return Array.from(this.connections.keys());
}
/**
* 获取连接的引用计数
*/
getRefCount(dbName) {
return this.refCount.get(dbName) ?? 0;
}
}
// ---------------------------------------------------------------------------
// 全局单例
// ---------------------------------------------------------------------------
const manager = new ConnectionManager();
// 挂载到 MetonaSqlark 静态方法(通过 any 绕过 TS 类型检查)
const M = MetonaSqlark;
M.connect = (config) => manager.connect(config);
M.disconnect = (dbName) => manager.release(dbName);
M.disconnectAll = () => manager.closeAll();
M.getActiveConnections = () => manager.getActiveConnections();
/**
* metona-sqlark — 入口文件
* @module metona-sqlark
* @version 0.2.5
*
* 前端关系型数据库,内存与磁盘双模式。
* 支持 Query Builder 链式 API 和 SQL 字符串查询。
*/
// ---------------------------------------------------------------------------
// 工厂函数
// ---------------------------------------------------------------------------
/**
* 创建数据库实例并初始化
*
* @example
* ```ts
* const db = await MetonaSqlark.create({
* name: 'my-app',
* mode: 'hybrid',
* });
*
* await db.defineTable('users', {
* id: { type: 'string', primaryKey: true },
* name: { type: 'string', required: true },
* });
*
* await db.table('users').insert({ id: '1', name: 'Alice' });
* const results = await db.query('SELECT * FROM users');
* ```
*/
async function create(config) {
const db = new MetonaSqlark(config);
await db.init();
return db;
}
// ---------------------------------------------------------------------------
// 全局 API
// ---------------------------------------------------------------------------
const api = {
VERSION,
version: VERSION,
create,
MetonaSqlark,
MeSqlark: MetonaSqlark,
};
if (typeof window !== 'undefined') {
window.MetonaSqlark = api;
window.MeSqlark = api;
}
// 别名
const MeSqlark = MetonaSqlark;
exports.AriaEngine = AriaEngine;
exports.HybridEngine = HybridEngine;
exports.IndexedDBEngine = IndexedDBEngine;
exports.MeSqlark = MeSqlark;
exports.MemoryEngine = MemoryEngine;
exports.MetonaSqlark = MetonaSqlark;
exports.OPFSBackend = OPFSBackend;
exports.OPFSEngine = OPFSEngine;
exports.Table = Table;
exports.VERSION = VERSION;
exports.api = api;
exports.create = create;
exports.default = api;
exports.parse = parse;
exports.tokenize = tokenize;
//# sourceMappingURL=metona-sqlark.cjs.js.map