feat: metona-sqlark v0.1.12 — 前端TypeScript关系型数据库

- 4种存储引擎:Memory / IndexedDB / OPFS / Hybrid
- 完整SQL支持:SELECT/INSERT/UPDATE/DELETE/JOIN/GROUP BY/HAVING/DISTINCT
- Query Builder链式API + TypeScript泛型支持
- 聚合函数:COUNT/SUM/AVG/MIN/MAX
- 事务、插件系统(14 hooks)、发布订阅、数据迁移、导入导出
- React/Vue框架集成
- 264个测试用例,93.46%覆盖率
- 零运行时依赖
This commit is contained in:
thzxx
2026-07-26 15:00:01 +08:00
commit e2a590c5b1
60 changed files with 16359 additions and 0 deletions
+173
View File
@@ -0,0 +1,173 @@
/**
* metona-sqlark Shared WHERE Matcher — 统一的条件匹配逻辑
* @module query/where-matcher
*
* MemoryEngine / IndexedDBEngine / QueryExecutor 共享此模块,
* 消除 220+ 行重复代码,统一 $and/$or/$not/$col 行为。
*/
import type { WhereCondition, OrderBy } from '../constants';
// ---------------------------------------------------------------------------
// LIKE 正则缓存
// ---------------------------------------------------------------------------
const likeCache = new Map<string, RegExp>();
function compileLikeRegex(pattern: string): RegExp {
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 列引用解析
*/
export function matchWhere(
row: Record<string, unknown>,
where: WhereCondition,
options: { $col?: boolean } = {},
): boolean {
for (const [field, condition] of Object.entries(where)) {
// 顶层 $and
if (field === '$and') {
const subs = condition as WhereCondition[];
if (!subs.every((sub) => matchWhere(row, sub, options))) return false;
continue;
}
// 顶层 $or
if (field === '$or') {
const subs = condition as WhereCondition[];
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: unknown,
condition: unknown,
row: Record<string, unknown>,
options: { $col?: boolean },
): boolean {
// 嵌套 $and
if (typeof condition === 'object' && condition !== null && '$and' in (condition as Record<string, unknown>)) {
const subs = (condition as Record<string, unknown>).$and as WhereCondition[];
return subs.every((sub) => matchWhere(row, sub, options));
}
// 嵌套 $or
if (typeof condition === 'object' && condition !== null && '$or' in (condition as Record<string, unknown>)) {
const subs = (condition as Record<string, unknown>).$or as WhereCondition[];
return subs.some((sub) => matchWhere(row, sub, options));
}
// $not
if (typeof condition === 'object' && condition !== null && '$not' in (condition as Record<string, unknown>)) {
return !matchField(value, (condition as Record<string, unknown>).$not, row, options);
}
// 简单值 => $eq
if (typeof condition !== 'object' || condition === null || Array.isArray(condition)) {
return value === condition;
}
const ops = condition as Record<string, unknown>;
// $col 简写: { $col: name } === { $eq: { $col: name } }(仅 JOIN ON 场景)
if (options.$col && '$col' in ops && Object.keys(ops).length === 1) {
return value === row[ops.$col as string];
}
// 遍历操作符
for (const [op, operand] of Object.entries(ops)) {
let actualOperand = operand;
// $col 列引用解析
if (options.$col && typeof operand === 'object' && operand !== null && '$col' in (operand as Record<string, unknown>)) {
actualOperand = row[(operand as Record<string, unknown>).$col as string];
}
if (!matchOperator(value, op, actualOperand)) return false;
}
return true;
}
// ---------------------------------------------------------------------------
// 操作符匹配
// ---------------------------------------------------------------------------
function matchOperator(value: unknown, op: string, operand: unknown): boolean {
switch (op) {
case '$eq': return value === operand;
case '$ne': return value !== operand;
case '$gt': return (value as number) > (operand as number);
case '$gte': return (value as number) >= (operand as number);
case '$lt': return (value as number) < (operand as number);
case '$lte': return (value as number) <= (operand as number);
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;
}
}
// ---------------------------------------------------------------------------
// 排序
// ---------------------------------------------------------------------------
export function applyOrderBy(rows: Record<string, unknown>[], orderBy: OrderBy[]): Record<string, unknown>[] {
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: unknown, b: unknown): number {
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));
}
// ---------------------------------------------------------------------------
// 列投影
// ---------------------------------------------------------------------------
export function projectColumns(row: Record<string, unknown>, columns: string[]): Record<string, unknown> {
const projected: Record<string, unknown> = {};
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;
}