(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : typeof define === 'function' && define.amd ? define(['exports'], factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.MetonaSqlark = {})); })(this, (function (exports) { 'use strict'; /** * 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, }); // --------------------------------------------------------------------------- // 错误类型 // --------------------------------------------------------------------------- /** 数据库错误 */ class DatabaseError extends Error { constructor(message, code, details) { super(message); this.code = code; this.details = details; this.name = 'DatabaseError'; } } // --------------------------------------------------------------------------- // 版本 // --------------------------------------------------------------------------- const VERSION = '0.2.0'; /** * 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 || colDef.onDelete === 'RESTRICT') 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); } } 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(); 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)); }); } 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 }); } 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 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]); } } } } /** * AriaEngine Types — 内部类型定义 * @module engine/aria/types * * 页面式存储引擎的所有内部枚举、接口和常量。 */ // ============================================================================= // 页面常量 // ============================================================================= /** 页面大小:4KB */ const PAGE_SIZE = 4096; // ============================================================================= // 页面类型 // ============================================================================= 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: 'batch', checkpointInterval: 1000, compression: false, storageBackend: 'indexeddb', }; /** * 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) { // 简化:在实际生产环境中需要完整的删除修复 // 这里使用简化版,仅处理常见情况 } 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); // 写入到 buffer const finalSize = totalSize + indexBlockSize + 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); // ---- Footer ---- const footerOffset = offset; view.setUint32(footerOffset, indexOffset, false); // index_offset view.setUint32(footerOffset + 4, indexBlockSize, false); // index_size view.setUint32(footerOffset + 8, 0, false); // bloom_offset (embedded in footer) view.setUint32(footerOffset + 12, 0, 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.data = data; this.view = new DataView(data.buffer, data.byteOffset, data.byteLength); this.meta = meta; this.parseFooter(); } // ----------------------------------------------------------------------- // 查询 // ----------------------------------------------------------------------- /** 精确查找 key */ get(targetKey) { 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) { const startBlockIdx = Math.max(0, this.locateBlockGE(startKey)); const endBlockIdx = Math.min(this.indexEntries.length - 1, this.locateBlockLE(endKey)); 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); this.entryCount = this.view.getUint32(footerOffset + 20, false); // 解析索引块 this.parseIndexBlock(indexOffset, indexSize); } 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) { for (let i = 0; i < this.indexEntries.length; i++) { if (this.indexEntries[i].key >= key) return i; } return this.indexEntries.length - 1; } locateBlockLE(key) { for (let i = this.indexEntries.length - 1; i >= 0; i--) { if (this.indexEntries[i].key <= key) return i; } return 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.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; } this.initialized = true; } // ======================================================================= // 写入 // ======================================================================= put(key, value) { this.memtable.put(key, value); this.operationCount++; if (this.memtable.shouldFlush()) { this.freezeMemtable(); } } delete(key) { this.memtable.put(key, { __tombstone: true }); this.operationCount++; if (this.memtable.shouldFlush()) { this.freezeMemtable(); } } 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; if (this.levels[0].length >= 4) { this.compactLevelSync(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 mergeIter = new MergeIterator(); // MemTable(最新优先) mergeIter.addSource(new ArrayEntrySource(this.memtable.rangeScan(startKey, endKey))); if (this.immutableMemtable) { mergeIter.addSource(new ArrayEntrySource(this.immutableMemtable.rangeScan(startKey, endKey))); } // SSTable for (let level = 0; level < MAX_LSM_LEVELS; level++) { for (const meta of this.levels[level]) { if (endKey < meta.minKey || startKey > meta.maxKey) continue; const reader = this.loadSSTableReader(meta); if (!reader) continue; const entries = []; reader.rangeScan(startKey, endKey, (k, v) => entries.push([k, v])); mergeIter.addSource(new ArrayEntrySource(entries)); } } const merged = mergeIter.drain(); return merged .filter(([, v]) => !v.__tombstone); } 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 // ======================================================================= /** 同步执行 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 记录 */ 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') { 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 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; const tableName = new TextDecoder().decode(data.slice(offset, offset + tableLen)); offset += tableLen; const keyLen = view.getUint16(offset, false); offset += 2; const key = new TextDecoder().decode(data.slice(offset, offset + keyLen)); offset += keyLen; const jsonLen = view.getUint32(offset, false); offset += 4; 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 offset += 4; records.push({ lsn, type, txnId, tableName, key, data: recordData, checksum: 0, }); } catch { break; } } return records; } } /** * AriaEngine Checkpoint — 检查点机制 * @module engine/aria/wal/checkpoint */ // --------------------------------------------------------------------------- // CheckpointManager // --------------------------------------------------------------------------- class CheckpointManager { constructor(lsm, wal, flushable = null, interval = 1000) { this.opCount = 0; this.lsm = lsm; this.wal = wal; this.flushable = flushable; this.interval = interval; } async tick() { this.opCount++; if (this.opCount >= this.interval) { await this.checkpoint(); } } 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 * * 封装底层浏览器存储 API(IndexedDB / 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(); } } /** * AriaEngine — 自研页面式存储引擎主类 * @module engine/aria/index * * 实现 IStorageEngine 接口。 * * v0.2.1: 完整持久化 * - Schema 存入 __aria_schemas * - SSTable 元数据存入 __aria_lsm_meta * - WAL 恢复包含行数据 * - 启动时自动加载 Schema + SSTable */ // --------------------------------------------------------------------------- // AriaEngine // --------------------------------------------------------------------------- class AriaEngine { constructor(config = {}) { this.name = 'aria'; this.opened = false; this.dbName = ''; // 表结构 this.schemas = new Map(); this.tablePKs = new Map(); this.opCounter = 0; // 事务 this.currentTxnId = null; this.txnSnapshot = null; this.config = { ...DEFAULT_ARIA_CONFIG, ...config }; } // ======================================================================= // 生命周期 // ======================================================================= async open(dbName, _version) { if (this.opened) return; this.dbName = dbName; // 1. 存储后端 if (this.config.storageBackend === 'indexeddb') { this.backend = new IndexedDBBackend(); } else { this.backend = new MemoryBackend(); } await this.backend.open(dbName); // 2. 构建 SSTableStore const sstableStore = this.createSSTableStore(); // 3. 初始化 LSM 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 恢复(恢复未刷盘的数据) await this.wal.recover((record) => this.applyWALRecord(record)); // 8. Checkpoint Manager(BufferPool 暂简化,使用 flush 替代) this.checkpointManager = new CheckpointManager(this.lsm, this.wal, { flushAll: async () => { await this.lsm.flush(); } }, this.config.checkpointInterval); 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)); await this.persistSchemas(); 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(); 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 this.txnSnapshot.set(key, validated); } else { // Direct write to LSM this.lsm.put(key, validated); } pks.push(pkValue); this.wal.append({ type: WALRecordType.INSERT, txnId: this.currentTxnId ?? 0, tableName, key: pkValue, data: validated, }); } this.opCounter += rows.length; await this.checkpointManager.tick(); 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); } else { this.lsm.put(key, updated); } count++; this.wal.append({ type: WALRecordType.UPDATE, txnId: this.currentTxnId ?? 0, tableName, key: String(row[pkCol]), data: updated, }); } } 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 this.txnSnapshot.set(key, { __txn_deleted: true }); } else { this.lsm.delete(key); } count++; this.wal.append({ type: WALRecordType.DELETE, txnId: this.currentTxnId ?? 0, tableName, key: String(row[pkCol]), }); } } this.opCounter += count; await this.checkpointManager.tick(); return count; } async count(tableName, query) { this.ensureOpen(); 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 = Date.now(); this.txnSnapshot = new Map(); 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.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.txnSnapshot = null; this.wal.append({ type: WALRecordType.ROLLBACK, txnId: this.currentTxnId, tableName: '', key: '', }); this.currentTxnId = null; } // ======================================================================= // 内部 // ======================================================================= 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; }); } tryIndexLookup(tableName, query) { if (!query.where) return null; const pkCol = this.tablePKs.get(tableName); for (const [col, condition] of Object.entries(query.where)) { if (col !== pkCol) continue; // 等值条件 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 }] : []; } } return null; } 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); } 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; } } // ======================================================================= // 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) => { const buf = data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength); await this.backend.write(`sst_${id}`, buf); }, load: async (id) => { const buf = await this.backend.read(`sst_${id}`); return buf ? new Uint8Array(buf) : null; }, 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: break; } } // ======================================================================= // 辅助 // ======================================================================= 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); } // ---- CRUD(write-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.memoryEngine.commitTransaction(); await this.diskEngine.commitTransaction(); } 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 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'); } } /** 将 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, }; } /** * 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) { this.engine = engine; } async execute(stmt) { switch (stmt.type) { case 'SELECT': return this.executeSelect(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); default: throw new DatabaseError('Unknown statement type', 'UNKNOWN_STATEMENT'); } } // =================================================================== // 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)); } 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); } 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"; // 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, // 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(); 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'; } // =================================================================== // 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 IN(NOT 后紧跟 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) { // 按优先级插入 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); } // 安装 plugin.install(null); // 实际引用由 MetonaSqlark 注入 } /** 卸载插件 */ 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; } 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.transactionManager = new TransactionManager(this.engine); // 注册插件 if (this.config.plugins) { for (const plugin of this.config.plugins) { this.pluginManager.register(plugin); } } 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); await this.pluginManager.trigger('beforeCreateTable', schema); await this.engine.createTable(schema); await this.pluginManager.trigger('afterCreateTable', schema); // 清除缓存 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(); await this.pluginManager.trigger('beforeDropTable', name); await this.engine.dropTable(name); await this.pluginManager.trigger('afterDropTable', name); this.tableCache.delete(name); } /** 获取所有表名 */ async getTableNames() { this.ensureReady(); return this.engine.getTableNames(); } // ---- SQL 查询 ---- /** 执行 SQL 字符串查询 */ async query(sql) { this.ensureReady(); await this.pluginManager.trigger('beforeQuery', sql); const stmt = parse(sql); const result = await this.executor.execute(stmt); await this.pluginManager.trigger('afterQuery', sql, result); return result; } // ---- 事务 ---- /** 执行事务 */ async transaction(fn) { this.ensureReady(); await this.pluginManager.trigger('beforeTransaction'); const result = await this.transactionManager.execute(fn); await this.pluginManager.trigger('afterTransaction'); return result; } // ---- 导入导出 ---- /** 导出表数据为 JSON */ async exportTable(tableName) { this.ensureReady(); return this.engine.find(tableName, { table: tableName }); } /** 导入 JSON 数据到表 */ async importTable(tableName, data) { this.ensureReady(); return this.engine.insert(tableName, data); } /** 导出整个数据库为 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' ? 'memory' : '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'); } } } /** * 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.1.12 * * 前端关系型数据库,内存与磁盘双模式。 * 支持 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.OPFSEngine = OPFSEngine; exports.Table = Table; exports.VERSION = VERSION; exports.api = api; exports.create = create; exports.default = api; exports.parse = parse; exports.tokenize = tokenize; Object.defineProperty(exports, '__esModule', { value: true }); })); //# sourceMappingURL=metona-sqlark.js.map