/** * 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 = {}; 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; }