/** * 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.1.13'; /** * 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) { 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; 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.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(); for (const tableName of tableNames) { const rows = await this.memoryCache.find(tableName, { table: tableName }); // Clear + bulk insert for simplicity await this.idbClear(tableName); if (rows.length > 0) { await this.idbInsert(tableName, rows); } } } 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]); } } } } /** * 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); let rows; if (!stmt.joins || stmt.joins.length === 0) { const plan = compileStatement(hasGroupBy ? { ...stmt, columns: ['*'] } : stmt); rows = await this.engine.find(plan.table, plan); } else { rows = await this.executeJoinSelect(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 && 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) { const columns = {}; for (const col of stmt.columns) columns[col.name] = astColumnToColumnDef(col); return this.engine.createTable(createSchema(stmt.name, columns)); } async executeDropTable(stmt) { return this.engine.dropTable(stmt.name); } getEngine() { return this.engine; } // =================================================================== // 子查询解析 // =================================================================== /** * 递归扫描 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["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, // 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: 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(); } } 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); 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 }; } 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); const tableName = this.expectIdentifier('table name'); return { type: 'DROP_TABLE', name: tableName }; } // =================================================================== // 条件表达式 // =================================================================== /** 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 if (this.curTokenIs(TokenType.NOT)) { 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; } // 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; } 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 '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; export { HybridEngine, IndexedDBEngine, MeSqlark, MemoryEngine, MetonaSqlark, OPFSEngine, Table, VERSION, api, create, api as default, parse, tokenize }; //# sourceMappingURL=metona-sqlark.esm.js.map