- 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%覆盖率 - 零运行时依赖
718 lines
20 KiB
TypeScript
718 lines
20 KiB
TypeScript
/**
|
||
* 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;
|
||
}
|