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
+717
View File
@@ -0,0 +1,717 @@
/**
* metona-sqlark SQL Parser — 递归下降语法分析器
* @module sql/parser
*
* Token 流 → AST Statement。
* 支持的语法是标准 SQL 的子集。
*/
import { Lexer } from './lexer';
import { TokenType, type Token } from './tokens';
import type {
Statement,
SelectStatement,
InsertStatement,
UpdateStatement,
DeleteStatement,
CreateTableStatement,
DropTableStatement,
ASTColumnDef,
} from '../query/ast';
import type { WhereCondition, OrderBy, SortDirection } from '../constants';
import { DatabaseError } from '../constants';
// ---------------------------------------------------------------------------
// Parser
// ---------------------------------------------------------------------------
export class Parser {
private lexer: Lexer;
private curToken!: Token;
private peekToken!: Token;
constructor(sql: string) {
this.lexer = new Lexer(sql);
// 预读两个 token
this.nextToken();
this.nextToken();
}
/** 解析完整 SQL 语句 */
parseStatement(): Statement {
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
// ===================================================================
private parseSelect(): SelectStatement {
this.expect(TokenType.SELECT);
// DISTINCT(可选)
let distinct = false;
if (this.curTokenIs(TokenType.DISTINCT)) {
distinct = true;
this.nextToken();
}
// 列
const columns: string[] = [];
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: string | undefined;
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: SelectStatement = {
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 子句列表 */
private parseJoinClauses(): import('../query/ast').JoinClause[] {
const joins: import('../query/ast').JoinClause[] = [];
while (this._isJoinKeyword()) {
joins.push(this.parseJoinClause());
}
return joins;
}
private _isJoinKeyword(): boolean {
return (
this.curTokenIs(TokenType.INNER) ||
this.curTokenIs(TokenType.LEFT) ||
this.curTokenIs(TokenType.RIGHT) ||
this.curTokenIs(TokenType.CROSS) ||
this.curTokenIs(TokenType.JOIN)
);
}
/** 解析单个 JOIN 子句 */
private parseJoinClause(): import('../query/ast').JoinClause {
let type: import('../query/ast').JoinType = '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: string | undefined;
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 之后的保留字 */
private _isReservedAfterFrom(): boolean {
return (
this.curTokenIs(TokenType.WHERE) ||
this.curTokenIs(TokenType.ORDER) ||
this.curTokenIs(TokenType.LIMIT) ||
this.curTokenIs(TokenType.OFFSET) ||
this.curTokenIs(TokenType.GROUP) ||
this._isJoinKeyword()
);
}
private _isJoinReserved(): boolean {
return (
this.curTokenIs(TokenType.ON) ||
this.curTokenIs(TokenType.WHERE) ||
this.curTokenIs(TokenType.ORDER) ||
this.curTokenIs(TokenType.LIMIT) ||
this._isJoinKeyword()
);
}
// ===================================================================
// INSERT
// ===================================================================
private parseInsert(): InsertStatement {
this.expect(TokenType.INSERT);
this.expect(TokenType.INTO);
const tableName = this.expectIdentifier('table name');
// 列名(可选)
let columns: string[] | undefined;
if (this.curTokenIs(TokenType.LPAREN)) {
this.nextToken();
columns = this.parseIdentifierList();
this.expect(TokenType.RPAREN);
}
// VALUES
this.expect(TokenType.VALUES);
// 值列表
const values: unknown[][] = [];
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
// ===================================================================
private parseUpdate(): UpdateStatement {
this.expect(TokenType.UPDATE);
const tableName = this.expectIdentifier('table name');
this.expect(TokenType.SET);
// SET col=val, ...
const sets: Record<string, unknown> = {};
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: WhereCondition = {};
if (this.curTokenIs(TokenType.WHERE)) {
this.nextToken();
where = this.parseCondition();
}
return { type: 'UPDATE', table: tableName, sets, where };
}
// ===================================================================
// DELETE
// ===================================================================
private parseDelete(): DeleteStatement {
this.expect(TokenType.DELETE);
this.expect(TokenType.FROM);
const tableName = this.expectIdentifier('table name');
let where: WhereCondition = {};
if (this.curTokenIs(TokenType.WHERE)) {
this.nextToken();
where = this.parseCondition();
}
return { type: 'DELETE', from: tableName, where };
}
// ===================================================================
// CREATE TABLE
// ===================================================================
private parseCreateTable(): CreateTableStatement {
this.expect(TokenType.CREATE);
this.expect(TokenType.TABLE);
const tableName = this.expectIdentifier('table name');
this.expect(TokenType.LPAREN);
const columns: ASTColumnDef[] = [];
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 };
}
private parseColumnDef(): ASTColumnDef {
const name = this.expectIdentifier('column name');
const type = this.expectIdentifier('column type').toLowerCase();
const col: ASTColumnDef = { name, type };
// 修饰符
while (
this.curTokenIs(TokenType.PRIMARY) ||
this.curTokenIs(TokenType.UNIQUE) ||
this.curTokenIs(TokenType.NOT) ||
this.curTokenIs(TokenType.DEFAULT)
) {
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();
// 支持 NOT NULL → required
this.expect(TokenType.NULL);
col.required = true;
} else if (this.curTokenIs(TokenType.DEFAULT)) {
this.nextToken();
col.default = this.parseValue();
} else {
break;
}
}
return col;
}
// ===================================================================
// DROP TABLE
// ===================================================================
private parseDropTable(): DropTableStatement {
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)* */
private parseCondition(): WhereCondition {
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] } as unknown as WhereCondition;
} else {
left = { $or: [left, right] } as unknown as WhereCondition;
}
}
return left;
}
/** simple_cond → column op value | column IS [NOT] NULL | column [NOT] LIKE pattern
* | column [NOT] IN (values) | NOT condition | (condition) */
private parseSimpleCondition(): WhereCondition {
// NOT expr
if (this.curTokenIs(TokenType.NOT)) {
this.nextToken();
const inner = this.parseSimpleCondition();
return { $not: inner } as unknown as WhereCondition;
}
// (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: WhereCondition = {};
result[column] = isNot ? { $ne: null } : { $eq: null };
return result;
}
// LIKE
if (this.curTokenIs(TokenType.LIKE)) {
this.nextToken();
const pattern = this.parseValue();
const result: WhereCondition = {};
result[column] = { $like: pattern };
return result;
}
// IN
if (this.curTokenIs(TokenType.IN)) {
this.nextToken();
this.expect(TokenType.LPAREN);
const values = this.parseValueList();
this.expect(TokenType.RPAREN);
const result: WhereCondition = {};
result[column] = { $in: values };
return result;
}
// 比较运算符
const op = this.parseComparisonOp();
// 尝试解析列引用(identifier DOT identifier 格式)
let value: unknown;
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: WhereCondition = {};
result[column] = { [op]: value };
return result;
}
private peekTokenIs(type: TokenType): boolean {
return this.peekToken.type === type;
}
private parseComparisonOp(): string {
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}"`);
}
}
// ===================================================================
// 辅助解析
// ===================================================================
private parseColumnList(): string[] {
const cols: string[] = [];
cols.push(this.parseColumnRef());
while (this.curTokenIs(TokenType.COMMA)) {
this.nextToken();
cols.push(this.parseColumnRef());
}
return cols;
}
/** 解析列引用:支持 'col'、'table.col' 和 'COUNT(*)'/'SUM(col)' 等 */
private parseColumnRef(): string {
// 聚合函数?
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) */
private parseAggregateCall(): string {
const func = this.curToken.value.toUpperCase();
this.nextToken();
this.expect(TokenType.LPAREN);
let arg: string;
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})`;
}
private _isAggregateAlias(): boolean {
return !this._isReservedAfterFrom() && !this._isJoinKeyword();
}
private parseIdentifierList(): string[] {
const ids: string[] = [];
ids.push(this.expectIdentifier('identifier'));
while (this.curTokenIs(TokenType.COMMA)) {
this.nextToken();
ids.push(this.expectIdentifier('identifier'));
}
return ids;
}
private parseValueList(): unknown[] {
const vals: unknown[] = [];
vals.push(this.parseValue());
while (this.curTokenIs(TokenType.COMMA)) {
this.nextToken();
vals.push(this.parseValue());
}
return vals;
}
private parseOrderByList(): OrderBy[] {
const list: OrderBy[] = [];
list.push(this.parseOrderBy());
while (this.curTokenIs(TokenType.COMMA)) {
this.nextToken();
list.push(this.parseOrderBy());
}
return list;
}
private parseOrderBy(): OrderBy {
const column = this.expectIdentifier('column name');
let direction: SortDirection = 'asc';
if (this.curTokenIs(TokenType.ASC)) {
this.nextToken();
} else if (this.curTokenIs(TokenType.DESC)) {
direction = 'desc';
this.nextToken();
}
return { column, direction };
}
/** 解析字面量值 */
private parseValue(): unknown {
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 操作
// ===================================================================
private nextToken(): void {
this.curToken = this.peekToken;
this.peekToken = this.lexer.nextToken();
}
private curTokenIs(type: TokenType): boolean {
return this.curToken.type === type;
}
private expect(type: TokenType): void {
if (this.curTokenIs(type)) {
this.nextToken();
return;
}
throw this.error(`Expected ${type}, got "${this.curToken.value}"`);
}
private expectIdentifier(context: string): string {
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}"`);
}
/** 关键字可以作为标识符(如列名等于关键字) */
private _isKeywordAsIdent(): boolean {
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
);
}
private expectNumber(context: string): number {
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}"`);
}
private error(msg: string): DatabaseError {
return new DatabaseError(
`Parse error at position ${this.curToken.position}: ${msg}`,
'PARSE_ERROR',
);
}
}
// ---------------------------------------------------------------------------
// 便捷方法
// ---------------------------------------------------------------------------
/** 解析 SQL 字符串为 AST Statement */
export function parse(sql: string): Statement {
const parser = new Parser(sql);
const stmt = parser.parseStatement();
return stmt;
}