Files
MetonaSqlark/dist/metona-sqlark.cjs.js
T
thzxx 382e1b24fe
CI / test (18.x) (push) Successful in 9m56s
CI / test (20.x) (push) Successful in 9m52s
CI / test (22.x) (push) Successful in 9m49s
CI / test (24.x) (push) In progress
fix: 无GROUP BY聚合查询结果为空 — 跳过重复列投影
2026-07-26 17:46:14 +08:00

3329 lines
116 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
/**
* metona-sqlark Constants — 类型定义 / 默认配置 / 枚举
* @module constants
*/
/** 所有存储模式 */
/** 所有字段类型 */
const FIELD_TYPES = ['string', 'number', 'boolean', 'date', 'json'];
/** 数据库默认配置 */
const DB_DEFAULTS = Object.freeze({
name: 'metona-sqlark',
mode: 'hybrid',
diskEngine: 'indexeddb',
version: 1,
});
// ---------------------------------------------------------------------------
// 错误类型
// ---------------------------------------------------------------------------
/** 数据库错误 */
class DatabaseError extends Error {
constructor(message, code, details) {
super(message);
this.code = code;
this.details = details;
this.name = 'DatabaseError';
}
}
// ---------------------------------------------------------------------------
// 版本
// ---------------------------------------------------------------------------
const VERSION = '0.1.14';
/**
* 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]);
}
}
}
}
/**
* metona-sqlark Hybrid Engine — 内存 + 磁盘混合存储引擎
* @module hybrid/index
*
* 采用 write-through 策略:
* - 所有写操作同时写入内存和磁盘
* - 所有读操作直接从内存返回
* - 数据库打开时从磁盘加载数据到内存
*/
// ---------------------------------------------------------------------------
// HybridEngine
// ---------------------------------------------------------------------------
class HybridEngine {
constructor(diskEngine = 'indexeddb') {
this.name = 'hybrid';
this.memoryEngine = new MemoryEngine();
this.diskEngineType = diskEngine;
this.diskEngine = diskEngine === 'opfs' ? new OPFSEngine() : new IndexedDBEngine();
}
// ---- 生命周期 ----
async open(dbName, version) {
// 先打开磁盘引擎
await this.diskEngine.open(dbName, version);
// 再打开内存引擎
await this.memoryEngine.open(dbName, version);
// 从磁盘加载现存表
const tableNames = await this.diskEngine.getTableNames();
for (const tableName of tableNames) {
const schema = await this.diskEngine.getTableSchema(tableName);
if (!schema)
continue;
// 在内存中创建表
await this.memoryEngine.createTable(schema);
// 从磁盘加载数据到内存
const rows = await this.diskEngine.find(tableName, { table: tableName });
if (rows.length > 0) {
try {
await this.memoryEngine.insert(tableName, rows);
}
catch (e) {
// eslint-disable-next-line no-console
console.warn(`[metona-sqlark] Failed to load table "${tableName}" data from disk:`, e);
}
}
}
}
async close() {
await this.memoryEngine.close();
await this.diskEngine.close();
}
isOpen() {
return this.memoryEngine.isOpen() && this.diskEngine.isOpen();
}
// ---- 表管理 ----
async createTable(schema) {
await this.memoryEngine.createTable(schema);
await this.diskEngine.createTable(schema);
}
async dropTable(tableName) {
await this.memoryEngine.dropTable(tableName);
await this.diskEngine.dropTable(tableName);
}
async hasTable(tableName) {
return this.memoryEngine.hasTable(tableName);
}
async getTableNames() {
return this.memoryEngine.getTableNames();
}
async getTableSchema(tableName) {
return this.memoryEngine.getTableSchema(tableName);
}
// ---- CRUDwrite-through 策略) ----
async insert(tableName, rows) {
const pks = await this.memoryEngine.insert(tableName, rows);
// write-through: 同步写入磁盘
await this.diskEngine.insert(tableName, rows);
return pks;
}
async find(tableName, query) {
// 直接从内存读取
return this.memoryEngine.find(tableName, query);
}
async update(tableName, query, updates) {
const count = await this.memoryEngine.update(tableName, query, updates);
// write-through: 同步更新磁盘
await this.diskEngine.update(tableName, query, updates);
return count;
}
async delete(tableName, query) {
const count = await this.memoryEngine.delete(tableName, query);
// write-through: 同步删除磁盘
await this.diskEngine.delete(tableName, query);
return count;
}
async count(tableName, query) {
return this.memoryEngine.count(tableName, query);
}
async clear(tableName) {
await this.memoryEngine.clear(tableName);
await this.diskEngine.clear(tableName);
}
// ---- 事务 ----
async beginTransaction() {
await this.memoryEngine.beginTransaction();
await this.diskEngine.beginTransaction();
}
async commitTransaction() {
await this.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) {
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; }
// ===================================================================
// 无 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["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:
// 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);
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(注意 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;
}
// NOT LIKE / NOT INNOT 后紧跟 LIKE 或 IN
if (this.curTokenIs(TokenType.NOT)) {
if (this.peekTokenIs(TokenType.IN)) {
// NOT IN
this.nextToken(); // skip NOT
this.nextToken(); // skip IN
this.expect(TokenType.LPAREN);
if (this.curTokenIs(TokenType.SELECT)) {
const subquery = this.parseSelect();
this.expect(TokenType.RPAREN);
const result = {};
result[column] = { $nin: { $subquery: subquery } };
return result;
}
const values = this.parseValueList();
this.expect(TokenType.RPAREN);
const result = {};
result[column] = { $nin: values };
return result;
}
else if (this.peekTokenIs(TokenType.LIKE)) {
// NOT LIKE
this.nextToken(); // skip NOT
this.nextToken(); // skip LIKE
const pattern = this.parseValue();
const result = {};
result[column] = { $not: { $like: pattern } };
return result;
}
}
// LIKE
if (this.curTokenIs(TokenType.LIKE)) {
this.nextToken();
const pattern = this.parseValue();
const result = {};
result[column] = { $like: pattern };
return result;
}
// IN
if (this.curTokenIs(TokenType.IN)) {
this.nextToken();
this.expect(TokenType.LPAREN);
// 子查询: IN (SELECT ...)
if (this.curTokenIs(TokenType.SELECT)) {
const subquery = this.parseSelect();
this.expect(TokenType.RPAREN);
const result = {};
result[column] = { $in: { $subquery: subquery } };
return result;
}
const values = this.parseValueList();
this.expect(TokenType.RPAREN);
const result = {};
result[column] = { $in: values };
return result;
}
// 比较运算符
const op = this.parseComparisonOp();
// 子查询: op (SELECT ...)
if (this.curTokenIs(TokenType.LPAREN) && this.peekTokenIs(TokenType.SELECT)) {
this.nextToken(); // skip (
const subquery = this.parseSelect();
this.expect(TokenType.RPAREN);
const result = {};
result[column] = { [op]: { $subquery: subquery } };
return result;
}
// 尝试解析列引用(identifier DOT identifier 格式)
let value;
if ((this.curToken.type === TokenType.IDENTIFIER || this._isKeywordAsIdent()) &&
this.peekTokenIs(TokenType.DOT)) {
const colRef = this.parseColumnRef();
value = { $col: colRef };
}
else {
value = this.parseValue();
}
const result = {};
result[column] = { [op]: value };
return result;
}
/** 判断当前 NOT 是否为 NOT IN / NOT LIKE 的一部分(不应作为通用 NOT 处理) */
_isNotInOrLike() {
return this.peekTokenIs(TokenType.IN) || this.peekTokenIs(TokenType.LIKE);
}
peekTokenIs(type) {
return this.peekToken.type === type;
}
parseComparisonOp() {
switch (this.curToken.type) {
case TokenType.EQ:
this.nextToken();
return '$eq';
case TokenType.NEQ:
this.nextToken();
return '$ne';
case TokenType.GT:
this.nextToken();
return '$gt';
case TokenType.GTE:
this.nextToken();
return '$gte';
case TokenType.LT:
this.nextToken();
return '$lt';
case TokenType.LTE:
this.nextToken();
return '$lte';
default:
throw this.error(`Expected comparison operator, got "${this.curToken.value}"`);
}
}
// ===================================================================
// 辅助解析
// ===================================================================
parseColumnList() {
const cols = [];
cols.push(this.parseColumnRef());
while (this.curTokenIs(TokenType.COMMA)) {
this.nextToken();
cols.push(this.parseColumnRef());
}
return cols;
}
/** 解析列引用:支持 'col'、'table.col' 和 'COUNT(*)'/'SUM(col)' 等 */
parseColumnRef() {
// 聚合函数?
if (this.curTokenIs(TokenType.COUNT) ||
this.curTokenIs(TokenType.SUM) ||
this.curTokenIs(TokenType.AVG) ||
this.curTokenIs(TokenType.MIN) ||
this.curTokenIs(TokenType.MAX)) {
return this.parseAggregateCall();
}
const first = this.expectIdentifier('column name');
if (this.curTokenIs(TokenType.DOT)) {
this.nextToken();
const second = this.expectIdentifier('column name');
return `${first}.${second}`;
}
return first;
}
/** 解析聚合函数调用: COUNT(*), SUM(col), AVG(col), MIN(col), MAX(col) */
parseAggregateCall() {
const func = this.curToken.value.toUpperCase();
this.nextToken();
this.expect(TokenType.LPAREN);
let arg;
if (this.curTokenIs(TokenType.STAR)) {
arg = '*';
this.nextToken();
}
else {
arg = this.parseColumnRef();
}
this.expect(TokenType.RPAREN);
// 可选别名: AS alias
let alias = '';
if (this.curTokenIs(TokenType.AS)) {
this.nextToken();
alias = this.expectIdentifier('alias');
}
else if (this.curToken.type === TokenType.IDENTIFIER && this._isAggregateAlias()) {
alias = this.curToken.value;
this.nextToken();
}
if (alias) {
return `${func}(${arg}) AS ${alias}`;
}
return `${func}(${arg})`;
}
_isAggregateAlias() {
return !this._isReservedAfterFrom() && !this._isJoinKeyword();
}
parseIdentifierList() {
const ids = [];
ids.push(this.expectIdentifier('identifier'));
while (this.curTokenIs(TokenType.COMMA)) {
this.nextToken();
ids.push(this.expectIdentifier('identifier'));
}
return ids;
}
parseValueList() {
const vals = [];
vals.push(this.parseValue());
while (this.curTokenIs(TokenType.COMMA)) {
this.nextToken();
vals.push(this.parseValue());
}
return vals;
}
parseOrderByList() {
const list = [];
list.push(this.parseOrderBy());
while (this.curTokenIs(TokenType.COMMA)) {
this.nextToken();
list.push(this.parseOrderBy());
}
return list;
}
parseOrderBy() {
const column = this.expectIdentifier('column name');
let direction = 'asc';
if (this.curTokenIs(TokenType.ASC)) {
this.nextToken();
}
else if (this.curTokenIs(TokenType.DESC)) {
direction = 'desc';
this.nextToken();
}
return { column, direction };
}
/** 解析字面量值 */
parseValue() {
switch (this.curToken.type) {
case TokenType.STRING: {
const val = this.curToken.value;
this.nextToken();
return val;
}
case TokenType.NUMBER: {
const val = Number(this.curToken.value);
this.nextToken();
return val;
}
case TokenType.TRUE:
this.nextToken();
return true;
case TokenType.FALSE:
this.nextToken();
return false;
case TokenType.NULL:
this.nextToken();
return null;
default:
throw this.error(`Expected value, got "${this.curToken.value}"`);
}
}
// ===================================================================
// Token 操作
// ===================================================================
nextToken() {
this.curToken = this.peekToken;
this.peekToken = this.lexer.nextToken();
}
curTokenIs(type) {
return this.curToken.type === type;
}
expect(type) {
if (this.curTokenIs(type)) {
this.nextToken();
return;
}
throw this.error(`Expected ${type}, got "${this.curToken.value}"`);
}
expectIdentifier(context) {
if (this.curToken.type === TokenType.IDENTIFIER || this._isKeywordAsIdent()) {
const val = this.curToken.value;
this.nextToken();
return val;
}
throw this.error(`Expected ${context}, got "${this.curToken.value}"`);
}
/** 关键字可以作为标识符(如列名等于关键字) */
_isKeywordAsIdent() {
return (this.curToken.type !== TokenType.EOF &&
this.curToken.type !== TokenType.ILLEGAL &&
this.curToken.type !== TokenType.STRING &&
this.curToken.type !== TokenType.NUMBER &&
this.curToken.type !== TokenType.COMMA &&
this.curToken.type !== TokenType.LPAREN &&
this.curToken.type !== TokenType.RPAREN &&
this.curToken.type !== TokenType.SEMICOLON &&
this.curToken.type !== TokenType.EQ &&
this.curToken.type !== TokenType.NEQ &&
this.curToken.type !== TokenType.GT &&
this.curToken.type !== TokenType.GTE &&
this.curToken.type !== TokenType.LT &&
this.curToken.type !== TokenType.LTE &&
this.curToken.type !== TokenType.DOT &&
this.curToken.type !== TokenType.STAR);
}
expectNumber(context) {
if (this.curToken.type === TokenType.NUMBER) {
const val = Number(this.curToken.value);
this.nextToken();
return val;
}
throw this.error(`Expected ${context}, got "${this.curToken.value}"`);
}
error(msg) {
return new DatabaseError(`Parse error at position ${this.curToken.position}: ${msg}`, 'PARSE_ERROR');
}
}
// ---------------------------------------------------------------------------
// 便捷方法
// ---------------------------------------------------------------------------
/** 解析 SQL 字符串为 AST Statement */
function parse(sql) {
const parser = new Parser(sql);
const stmt = parser.parseStatement();
return stmt;
}
/**
* metona-sqlark Transaction — 事务管理
* @module transaction
*
* v0.1.13: 支持真正的回滚 — 利用引擎层 begin/commit/rollback 实现原子性。
*/
// ---------------------------------------------------------------------------
// Transaction
// ---------------------------------------------------------------------------
class Transaction {
constructor(engine) {
this.tables = new Map();
this.completed = false;
this.engine = engine;
}
/** 获取表操作对象 */
table(tableName) {
let t = this.tables.get(tableName);
if (!t) {
t = new Table(this.engine, tableName);
this.tables.set(tableName, t);
}
return t;
}
/** 标记事务完成(由 TransactionManager 调用) */
_markCompleted() {
this.completed = true;
}
/** 是否已完成 */
isCompleted() {
return this.completed;
}
}
// ---------------------------------------------------------------------------
// TransactionManager
// ---------------------------------------------------------------------------
class TransactionManager {
constructor(engine) {
this.engine = engine;
}
/** 执行事务 — 支持自动回滚 */
async execute(fn) {
const trx = new Transaction(this.engine);
// 开始引擎层事务
await this.engine.beginTransaction();
try {
const result = await fn(trx);
// 成功 → 提交
await this.engine.commitTransaction();
trx._markCompleted();
return result;
}
catch (error) {
// 失败 → 回滚
await this.engine.rollbackTransaction();
if (error instanceof DatabaseError)
throw error;
throw new DatabaseError(`Transaction failed: ${error.message}`, 'TRANSACTION_ERROR', error);
}
}
}
/**
* metona-sqlark Plugin — 插件系统
* @module plugin
*
* 管理插件的注册、生命周期和钩子调度。
*/
// ---------------------------------------------------------------------------
// PluginManager
// ---------------------------------------------------------------------------
class PluginManager {
constructor() {
this.plugins = [];
this.hooks = new Map();
}
/** 注册插件 */
register(plugin) {
// 按优先级插入
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;
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;
//# sourceMappingURL=metona-sqlark.cjs.js.map