/** * 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, SelectUnionStatement, InsertStatement, UpdateStatement, DeleteStatement, CreateTableStatement, DropTableStatement, AlterTableStatement, TruncateTableStatement, CreateIndexStatement, DropIndexStatement, BeginTransactionStatement, CommitTransactionStatement, ASTColumnDef, } from '../query/ast'; import type { WhereCondition, FieldCondition, OrderBy, SortDirection } from '../constants'; import { DatabaseError } from '../constants'; // --------------------------------------------------------------------------- // Parser // --------------------------------------------------------------------------- export class Parser { private lexer: Lexer; private curToken!: Token; private peekToken!: Token; private sql: string; constructor(sql: string) { this.sql = sql; 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.parseCreateStatement(); case TokenType.DROP: return this.parseDropStatement(); case TokenType.ALTER: return this.parseAlterTable(); case TokenType.TRUNCATE: return this.parseTruncateTable(); case TokenType.BEGIN: return this.parseBegin(); case TokenType.COMMIT: return this.parseCommit(); case TokenType.ROLLBACK: return this.parseRollback(); // v0.5.1: 维护语句入口 case TokenType.EXPLAIN: return this.parseExplain(); case TokenType.ANALYZE: return this.parseAnalyze(); case TokenType.REINDEX: return this.parseReindex(); case TokenType.VACUUM: return this.parseVacuum(); case TokenType.SAVEPOINT: return this.parseSavepoint(); case TokenType.RELEASE: return this.parseSavepoint(); default: throw this.error(`Unexpected token "${this.curToken.value}"`); } } /** 解析所有语句(分号分隔的多语句支持) */ parseAllStatements(): Statement[] { const statements: Statement[] = []; while (!this.curTokenIs(TokenType.EOF)) { // 跳过多余的分号 while (this.curTokenIs(TokenType.SEMICOLON)) this.nextToken(); if (this.curTokenIs(TokenType.EOF)) break; statements.push(this.parseStatement()); // 语句后应紧跟分号或 EOF if (this.curTokenIs(TokenType.SEMICOLON)) { this.nextToken(); } else if (!this.curTokenIs(TokenType.EOF)) { throw this.error(`Expected ';' after statement, got "${this.curToken.value}"`); } } return statements; } // ---- 维护语句(v0.5.1) ---- /** EXPLAIN — 输出查询计划 */ private parseExplain(): import('../query/ast').ExplainStatement { this.expect(TokenType.EXPLAIN); if (this.curTokenIs(TokenType.EXPLAIN)) { throw this.error('Nested EXPLAIN is not allowed'); } const query = this.parseStatement(); return { type: 'EXPLAIN', query }; } /** ANALYZE [TABLE] name — 收集表统计信息 */ private parseAnalyze(): import('../query/ast').AnalyzeStatement { this.expect(TokenType.ANALYZE); if (this._isKeywordAsIdent() && this.curToken.value.toUpperCase() === 'TABLE') { this.nextToken(); } return { type: 'ANALYZE', table: this.expectIdentifier('table name') }; } /** REINDEX [TABLE] name — 重建表二级索引 */ private parseReindex(): import('../query/ast').ReindexStatement { this.expect(TokenType.REINDEX); if (this._isKeywordAsIdent() && this.curToken.value.toUpperCase() === 'TABLE') { this.nextToken(); } return { type: 'REINDEX', table: this.expectIdentifier('table name') }; } /** VACUUM — 压缩 LSM + 清理碎片 */ private parseVacuum(): import('../query/ast').VacuumStatement { this.expect(TokenType.VACUUM); return { type: 'VACUUM' }; } /** SAVEPOINT name | RELEASE [SAVEPOINT] name */ private parseSavepoint(): import('../query/ast').SavepointStatement { let action: 'SAVE' | 'ROLLBACK' | 'RELEASE'; if (this.curTokenIs(TokenType.RELEASE)) { action = 'RELEASE'; this.nextToken(); } else { action = 'SAVE'; this.expect(TokenType.SAVEPOINT); } // 可选 SAVEPOINT 关键字(RELEASE SAVEPOINT name) if (this.curTokenIs(TokenType.SAVEPOINT)) this.nextToken(); return { type: 'SAVEPOINT', name: this.expectIdentifier('savepoint name'), action }; } // ---- 事务语句 ---- private parseBegin(): BeginTransactionStatement { this.expect(TokenType.BEGIN); // 可选 TRANSACTION 关键字 if (this._isKeywordAsIdent() && this.curToken.value.toUpperCase() === 'TRANSACTION') { this.nextToken(); } return { type: 'BEGIN' }; } private parseCommit(): CommitTransactionStatement { this.expect(TokenType.COMMIT); if (this._isKeywordAsIdent() && this.curToken.value.toUpperCase() === 'TRANSACTION') { this.nextToken(); } return { type: 'COMMIT' }; } /** ROLLBACK [TRANSACTION] | ROLLBACK TO [SAVEPOINT] name(v0.5.1) */ private parseRollback(): Statement { this.expect(TokenType.ROLLBACK); if (this._isKeywordAsIdent() && this.curToken.value.toUpperCase() === 'TRANSACTION') { this.nextToken(); return { type: 'ROLLBACK' }; } // ROLLBACK TO [SAVEPOINT] name if (this.curTokenIs(TokenType.TO) || (this._isKeywordAsIdent() && this.curToken.value.toUpperCase() === 'TO')) { this.nextToken(); if (this.curTokenIs(TokenType.SAVEPOINT) || (this._isKeywordAsIdent() && this.curToken.value.toUpperCase() === 'SAVEPOINT')) { this.nextToken(); } return { type: 'SAVEPOINT', name: this.expectIdentifier('savepoint name'), action: 'ROLLBACK' }; } return { type: 'ROLLBACK' }; } // ---- CREATE TABLE / CREATE INDEX ---- private parseCreateStatement(): Statement { this.expect(TokenType.CREATE); if (this.curTokenIs(TokenType.TABLE)) { return this.parseCreateTable(); } if (this.curTokenIs(TokenType.INDEX) || (this._isKeywordAsIdent() && this.curToken.value.toUpperCase() === 'INDEX')) { return this.parseCreateIndex(); } if (this.curTokenIs(TokenType.UNIQUE)) { // CREATE UNIQUE INDEX this.nextToken(); if (this.curTokenIs(TokenType.INDEX) || (this._isKeywordAsIdent() && this.curToken.value.toUpperCase() === 'INDEX')) { const stmt = this.parseCreateIndex(); stmt.unique = true; return stmt; } } throw this.error(`Expected TABLE or INDEX after CREATE, got "${this.curToken.value}"`); } private parseCreateIndex(): CreateIndexStatement { this.expect(TokenType.INDEX); const name = this.expectIdentifier('index name'); this.expect(TokenType.ON); const table = this.expectIdentifier('table name'); this.expect(TokenType.LPAREN); const column = this.expectIdentifier('column name'); this.expect(TokenType.RPAREN); return { type: 'CREATE_INDEX', name, table, column }; } // ---- DROP TABLE / DROP INDEX ---- private parseDropStatement(): Statement { this.expect(TokenType.DROP); if (this.curTokenIs(TokenType.TABLE)) { return this.parseDropTable(); } if (this.curTokenIs(TokenType.INDEX) || (this._isKeywordAsIdent() && this.curToken.value.toUpperCase() === 'INDEX')) { return this.parseDropIndex(); } throw this.error(`Expected TABLE or INDEX after DROP, got "${this.curToken.value}"`); } private parseDropIndex(): DropIndexStatement { this.expect(TokenType.INDEX); const name = this.expectIdentifier('index name'); // SQLite 风格:DROP INDEX idx_name [ON table] let table = ''; let column = ''; if (this.curTokenIs(TokenType.ON)) { this.nextToken(); table = this.expectIdentifier('table name'); if (this.curTokenIs(TokenType.LPAREN)) { this.nextToken(); column = this.expectIdentifier('column name'); this.expect(TokenType.RPAREN); } } return { type: 'DROP_INDEX', name, table, column }; } // =================================================================== // SELECT // =================================================================== private parseSelect(): SelectStatement | SelectUnionStatement { 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(); // v0.7.3: `SELECT *, col [AS alias], ...` —— '*' 后可继续列列表 // (此前 '*' 独占分支,逗号后直接 PARSE_ERROR;executor 侧投影已支持混合) while (this.curTokenIs(TokenType.COMMA)) { this.nextToken(); columns.push(this.parseColumnWithAlias()); } } else { columns.push(...this.parseColumnList()); } // FROM(v0.4.0 可选:SELECT 1 / SELECT 'lit' 无表查询) let fromSubquery: SelectStatement | SelectUnionStatement | undefined; let tableName = ''; let alias: string | undefined; if (this.curTokenIs(TokenType.FROM)) { this.nextToken(); // v0.4.0: FROM (SELECT ...) AS alias 派生表 if (this.curTokenIs(TokenType.LPAREN)) { this.nextToken(); fromSubquery = this.parseSelect(); this.expect(TokenType.RPAREN); 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(); } } else { tableName = this.expectIdentifier('table name'); // 表别名(可选) 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: {}, }; if (fromSubquery) { stmt.fromSubquery = fromSubquery; } // 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'); } // UNION / UNION ALL(可选,v0.3.0) if (this.curTokenIs(TokenType.UNION)) { return this.parseUnion(stmt); } return stmt; } /** 解析 UNION / UNION ALL 组合(支持链式) */ private parseUnion(left: SelectStatement | SelectUnionStatement): SelectUnionStatement { this.expect(TokenType.UNION); let all = false; if (this.curTokenIs(TokenType.ALL)) { all = true; this.nextToken(); } const right = this.parseSelect(); const unionStmt: SelectUnionStatement = { type: 'SELECT_UNION', left, right, all: all || undefined }; this.adoptTrailingClauses(unionStmt, right); // 链式 UNION if (this.curTokenIs(TokenType.UNION)) { return this.parseUnionChain(unionStmt); } return unionStmt; } /** 链式 UNION:左侧是已组合的 UNION 语句 */ private parseUnionChain(left: SelectUnionStatement): SelectUnionStatement { this.expect(TokenType.UNION); let all = false; if (this.curTokenIs(TokenType.ALL)) { all = true; this.nextToken(); } const right = this.parseSelect(); const unionStmt: SelectUnionStatement = { type: 'SELECT_UNION', left, right, all: all || undefined }; this.adoptTrailingClauses(unionStmt, right); if (this.curTokenIs(TokenType.UNION)) { return this.parseUnionChain(unionStmt); } return unionStmt; } /** * v0.8.0(A26):把"最后一个 SELECT 上的 ORDER BY / LIMIT / OFFSET"上移到 * 复合查询节点,并把这些子句从该 SELECT 上**移除**。 * * 为什么必须"移动"而不是"复制": * - 语法上它们写在最后一个 SELECT 之后,但 SQL 语义作用于整个 UNION * (`A UNION B LIMIT 3` 是"合并去重后取前 3 行",不是"B 取前 3 行"); * - 若只复制不移除,LIMIT 会**应用两次** —— 正是 A5/A6 那类"两处都生效" * 缺陷的同一个坑(B 先被截断,再对合并结果截断,结果可能少行)。 * * 由于 `parseSelect` 无法预知后面有没有 UNION(它在返回后才知道), * 只能先让它照常解析、发现 UNION 时再回收 —— 这比"预读 UNION"简单且无回溯。 */ private adoptTrailingClauses( unionStmt: SelectUnionStatement, right: SelectStatement | SelectUnionStatement, ): void { // 链式 UNION 时右侧可能已是 UNION 节点,其尾部子句在创建时已上移 if (right.type !== 'SELECT') return; if (right.orderBy) { unionStmt.orderBy = right.orderBy; delete right.orderBy; } if (right.limit !== undefined) { unionStmt.limit = right.limit; delete right.limit; } if (right.offset !== undefined) { unionStmt.offset = right.offset; delete right.offset; } } /** 解析 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); } // INSERT INTO ... SELECT ...(v0.3.0) if (this.curTokenIs(TokenType.SELECT)) { return { type: 'INSERT', into: tableName, columns, select: this.parseSelect(), }; } // 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)); // v0.8.0 根治:显式列名时校验每行值的个数与列数一致。 // // 此前完全不校验 arity,实测: // INSERT INTO t (id, name) VALUES ('4','z',9) → 多余的 9 被**静默丢弃** // INSERT INTO t VALUES ('3') → 静默写入半行(其余列缺失) // SQLite / MySQL 都会报错。静默丢弃/截断属于"静默数据丢失", // 必须在解析期拦下(此时无需 schema,只要有显式列名即可判断)。 // // 未显式给列名时(INSERT INTO t VALUES (...))需要 schema 才能判断个数, // 由 executor 在拿到 schema 后校验(见 validateInsertArity)。 if (columns) { for (let i = 0; i < values.length; i++) { if (values[i].length !== columns.length) { throw this.error( `INSERT column/value count mismatch: ${columns.length} column(s) but row ${i + 1} has ${values[i].length} value(s)`, ); } } } return { type: 'INSERT', into: tableName, columns, values, }; } // =================================================================== // UPDATE // =================================================================== /** * v0.8.0: 创建**无原型**对象,用于以用户提供的列名为键的映射。 * * 背景:`obj['__proto__'] = v` 在普通对象上会触发原型 setter 而不是新增属性, * 于是 `UPDATE t SET __proto__ = 'x'` 的 sets 变成 `{}` —— 既没写进去、也不会被 * v0.7.4 新增的"未知列显式报错"预检看到,表现为"返回成功但什么都没发生"。 * 建表路径在 v0.7.1 已用 Object.create(null) 防护,此处补齐其余路径。 */ private newColumnMap(): Record { return Object.create(null) as Record; } private parseUpdate(): UpdateStatement { this.expect(TokenType.UPDATE); const tableName = this.expectIdentifier('table name'); this.expect(TokenType.SET); // SET col=val, ...(v0.8.0: 无原型对象,防 __proto__ 列名静默吞掉赋值) const sets: Record = this.newColumnMap(); 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 = this.newColumnMap() as 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 = this.newColumnMap() as 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.TABLE); // IF NOT EXISTS(可选) let ifNotExists = false; if (this.curTokenIs(TokenType.IF)) { this.nextToken(); this.expect(TokenType.NOT); this.expect(TokenType.EXISTS); ifNotExists = true; } 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, ifNotExists: ifNotExists || undefined }; } 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) || 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 */ private parseCascadeAction(): 'CASCADE' | 'SET NULL' | 'RESTRICT' { 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'; } // =================================================================== // ALTER TABLE // =================================================================== private parseAlterTable(): AlterTableStatement { this.expect(TokenType.ALTER); this.expect(TokenType.TABLE); const tableName = this.expectIdentifier('table name'); // ADD COLUMN / DROP COLUMN let action: 'ADD' | 'DROP'; if (this.curTokenIs(TokenType.ADD)) { action = 'ADD'; this.nextToken(); // Optional COLUMN keyword if (this.curToken.type === TokenType.IDENTIFIER && this.curToken.value.toUpperCase() === 'COLUMN') { this.nextToken(); } const col = this.parseColumnDef(); return { type: 'ALTER_TABLE', name: tableName, action, column: col }; } else if (this.curTokenIs(TokenType.DROP) || (this._isKeywordAsIdent() && this.curToken.value.toUpperCase() === 'DROP')) { action = 'DROP'; this.nextToken(); // Optional COLUMN keyword if (this.curToken.type === TokenType.IDENTIFIER && this.curToken.value.toUpperCase() === 'COLUMN') { this.nextToken(); } const colName = this.expectIdentifier('column name'); return { type: 'ALTER_TABLE', name: tableName, action, column: { name: colName, type: 'string' } }; } else { throw this.error('Expected ADD or DROP in ALTER TABLE'); } } // =================================================================== // TRUNCATE TABLE // =================================================================== private parseTruncateTable(): TruncateTableStatement { this.expect(TokenType.TRUNCATE); this.expect(TokenType.TABLE); const tableName = this.expectIdentifier('table name'); return { type: 'TRUNCATE_TABLE', name: tableName }; } // =================================================================== // DROP TABLE // =================================================================== private parseDropTable(): DropTableStatement { this.expect(TokenType.TABLE); // IF EXISTS(可选) let ifExists = false; if (this.curTokenIs(TokenType.IF)) { this.nextToken(); this.expect(TokenType.EXISTS); ifExists = true; } const tableName = this.expectIdentifier('table name'); return { type: 'DROP_TABLE', name: tableName, ifExists: ifExists || undefined }; } // =================================================================== // 条件表达式 // =================================================================== /** * condition → or_expr * * v0.8.0 根治:AND 的优先级必须高于 OR(SQL 标准)。 * * 此前实现是**纯左折叠**的单层循环: * `a = 1 OR a = 2 AND b = 3` → `(a = 1 OR a = 2) AND b = 3` ← 错 * 标准语义应为: * `a = 1 OR a = 2 AND b = 3` → `a = 1 OR (a = 2 AND b = 3)` ← 对 * * 影响面:任何"权限条件 OR 业务条件 AND 软删标记"的写法都会静默返回错误行集 * (审计实测:4 行表上返回 1 行而非 3 行)。这是本层影响面最大、改动最小的缺陷。 * * 现在按标准文法分层:or_expr → and_expr (OR and_expr)* * and_expr → unary (AND unary)* * unary → [NOT] primary * 并且只在**确实有多个操作数**时才包 $and/$or,避免生成 {$and:[x]} 这种冗余节点 * (否则 `WHERE a = 1` 的结构会从 `{a:{$eq:1}}` 变成 `{$and:[{a:{$eq:1}}]}`, * 破坏既有 AST 契约与下游引擎的索引下推识别)。 */ private parseCondition(): WhereCondition { return this.parseOrExpression(); } /** or_expr → and_expr (OR and_expr)* */ private parseOrExpression(): WhereCondition { const operands: WhereCondition[] = [this.parseAndExpression()]; while (this.curTokenIs(TokenType.OR)) { this.nextToken(); operands.push(this.parseAndExpression()); } return operands.length === 1 ? operands[0] : ({ $or: operands } as unknown as WhereCondition); } /** and_expr → simple_cond (AND simple_cond)* */ private parseAndExpression(): WhereCondition { const operands: WhereCondition[] = [this.parseSimpleCondition()]; while (this.curTokenIs(TokenType.AND)) { this.nextToken(); operands.push(this.parseSimpleCondition()); } return operands.length === 1 ? operands[0] : ({ $and: operands } as unknown as WhereCondition); } /** 公共 WHERE 条件入口(供 CASE WHEN 求值等外部场景,v0.3.1) */ parseWhere(): WhereCondition { return this.parseCondition(); } /** simple_cond → column op value | column IS [NOT] NULL | column [NOT] LIKE pattern * | column [NOT] IN (values) | NOT condition | (condition) * | [NOT] EXISTS (SELECT ...) ← v0.3.0 */ private parseSimpleCondition(): WhereCondition { // [NOT] EXISTS (SELECT ...) if (this.curTokenIs(TokenType.EXISTS) || (this._isKeywordAsIdent() && this.curToken.value.toUpperCase() === 'EXISTS')) { this.nextToken(); return this.parseExistsCondition(false); } if (this.curTokenIs(TokenType.NOT) && this._peekIsExists()) { this.nextToken(); // 跳过 NOT this.nextToken(); // 跳过 EXISTS return this.parseExistsCondition(true); } // NOT expr(注意 NOT IN / NOT LIKE 不作为通用 NOT) if (this.curTokenIs(TokenType.NOT) && !this._isNotInOrLike()) { 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 = this.newColumnMap() as WhereCondition; // v0.8.0 三值语义:IS NULL / IS NOT NULL 是**谓词**,不是等值比较。 // 此前生成为 { $eq: null } / { $ne: null } —— 在 SQL 标准里 // `x = NULL` 恒为 UNKNOWN(不保留任何行),而 IS NULL 要保留 NULL 行。 // 两者语义不同,必须用不同标记(见 query/sql-compare.ts 的谓词说明)。 result[column] = isNot ? { $isNotNull: true } : { $isNull: true }; return result; } // BETWEEN val1 AND val2 if (this.curTokenIs(TokenType.BETWEEN)) { this.nextToken(); const low = this.parseValue(); this.expect(TokenType.AND); const high = this.parseValue(); // v0.8.0 根治:BETWEEN 必须是**范围**条件。 // 此前把同一个对象同时当成"操作符对象"和"操作数"传给 `$eq` // (`{ $eq: { $gte, $lte } }`),matchOperator 的 `$eq` 收到一个对象再去比较, // 结果只对"值恰好等于该对象"的行成立 —— 实测 `WHERE n BETWEEN 1 AND 2` // 在 (1,2,NULL,3) 上只返回 1 行(应 2 行),静默错值。 const result: WhereCondition = this.newColumnMap() as WhereCondition; result[column] = { $gte: low, $lte: high }; return result; } // NOT BETWEEN val1 AND val2 if (this.curTokenIs(TokenType.NOT) && this.peekTokenIs(TokenType.BETWEEN)) { this.nextToken(); // skip NOT this.nextToken(); // skip BETWEEN const low = this.parseValue(); this.expect(TokenType.AND); const high = this.parseValue(); const result: WhereCondition = this.newColumnMap() as WhereCondition; // v0.8.0:NOT BETWEEN ≡ (x < low) OR (x > high),直接展开为字段级 `$or`。 // // 此前生成 `{ $not: { $gte, $lte } }`。这**曾经**恒为空集:字段级 `$not` // 把内层对象当"单个条件对象",key `$gte`/`$lte` 被当成列名去取 // `row['$gte']` → undefined → 整条恒 UNKNOWN → 取反仍 UNKNOWN → 全部排除。 // // 求值器已修正(内层按操作符对象解释),但这里仍选择展开为 `$or`: // - `$or` 的三值行为(含 NULL 时 UNKNOWN)是显式可读的; // - 避免依赖"$not 作用于比较"与"$not 作用于谓词"(如 `$isNull`)的差别。 // 两条路径都有测试锁定(tests/v080-sql-three-valued.test.ts 与 // tests/sql/where-matcher.test.ts 的 `$not` 用例)。 result[column] = { $or: [{ $lt: low } as never, { $gt: high } as never] as never }; return result; } // NOT LIKE / NOT IN(NOT 后紧跟 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: WhereCondition = this.newColumnMap() as WhereCondition; result[column] = { $nin: { $subquery: subquery } }; return result; } const values = this.parseValueList(); this.expect(TokenType.RPAREN); const result: WhereCondition = this.newColumnMap() as WhereCondition; 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: WhereCondition = this.newColumnMap() as WhereCondition; result[column] = { $not: { $like: pattern } }; return result; } } // LIKE if (this.curTokenIs(TokenType.LIKE)) { this.nextToken(); const pattern = this.parseValue(); const result: WhereCondition = this.newColumnMap() as WhereCondition; 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: WhereCondition = this.newColumnMap() as WhereCondition; result[column] = { $in: { $subquery: subquery } }; return result; } const values = this.parseValueList(); this.expect(TokenType.RPAREN); const result: WhereCondition = this.newColumnMap() as WhereCondition; result[column] = { $in: values }; return result; } // v0.4.1: 裸布尔列条件(WHERE done / CASE WHEN done THEN)— 列后直接是终止符时视为真值判断 if ( this.curTokenIs(TokenType.AND) || this.curTokenIs(TokenType.OR) || this.curTokenIs(TokenType.RPAREN) || this.curTokenIs(TokenType.EOF) || (this.curToken.type === TokenType.IDENTIFIER && ['THEN', 'END', 'ELSE', 'NULLS', 'LIMIT', 'OFFSET', 'ORDER', 'GROUP', 'HAVING', 'UNION', 'WHERE'].includes(this.curToken.value.toUpperCase())) ) { const result: WhereCondition = this.newColumnMap() as WhereCondition; result[column] = { $eq: true }; 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: WhereCondition = this.newColumnMap() as WhereCondition; result[column] = { [op]: { $subquery: subquery } }; return result; } // 尝试解析列引用(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 = this.newColumnMap() as WhereCondition; result[column] = { [op]: value }; return result; } /** 解析 EXISTS (SELECT ...) / NOT EXISTS (SELECT ...) */ private parseExistsCondition(negate: boolean): WhereCondition { this.expect(TokenType.LPAREN); const subquery = this.parseSelect(); this.expect(TokenType.RPAREN); // $exists 键由 Executor.resolveSubqueries 解析为 boolean,where-matcher 消费 return { $exists: { $subquery: subquery, $negate: negate || undefined } } as unknown as WhereCondition; } /** 判断当前 NOT 后是否紧跟 EXISTS */ private _peekIsExists(): boolean { return this.peekToken.type === TokenType.EXISTS || (this.peekToken.type === TokenType.IDENTIFIER && this.peekToken.value.toUpperCase() === 'EXISTS'); } /** 判断当前 NOT 是否为 NOT IN / NOT LIKE 的一部分(不应作为通用 NOT 处理) */ private _isNotInOrLike(): boolean { return this.peekTokenIs(TokenType.IN) || this.peekTokenIs(TokenType.LIKE); } 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.parseColumnWithAlias()); while (this.curTokenIs(TokenType.COMMA)) { this.nextToken(); cols.push(this.parseColumnWithAlias()); } return cols; } /** v0.3.3: 解析列(支持 `col AS alias` 显式别名与 `col alias` 隐式别名) */ private parseColumnWithAlias(): string { let col = this.parseColumnRef(); if (this.curTokenIs(TokenType.AS)) { this.nextToken(); const alias = this.expectIdentifier('alias'); col = `${col} AS ${alias}`; } else if (this.curToken.type === TokenType.IDENTIFIER && !this._isReservedAfterFrom() && !this._isJoinKeyword()) { const alias = this.curToken.value; this.nextToken(); col = `${col} AS ${alias}`; } return col; } /** 解析列引用:支持 'col'、'table.col'、'COUNT(*)'/'SUM(col)'、数字常量列(SELECT 1)、字符串常量列(SELECT 'x',v0.4.0)和 CASE WHEN 表达式(v0.3.1) */ private parseColumnRef(): string { // CASE WHEN 表达式(v0.3.1) if (this.curTokenIs(TokenType.CASE)) { return this.parseCaseExpressionText(); } // 数字常量列:SELECT 1 FROM t(常见于 EXISTS 子查询) // v0.8.0: 只返回常量文本,别名交给调用方 parseColumnWithAlias 处理 //(此前这里直接 return,导致 `SELECT 1 AS one` 的别名被丢弃, // 投影时 row['1'] → undefined → 整行变成 {})。 if (this.curTokenIs(TokenType.NUMBER)) { const value = this.curToken.value; this.nextToken(); return value; } // v0.4.0: 字符串常量列:SELECT 'value' FROM t // v0.8.0: 同样只返回字面量文本,别名由 parseColumnWithAlias 叠加 if (this.curTokenIs(TokenType.STRING)) { const value = this.curToken.value; this.nextToken(); return `'${value.replace(/'/g, "''")}'`; } // 聚合函数? 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; } /** * 解析 CASE WHEN 表达式,返回原文(含可选 AS 别名)。 * 例:CASE WHEN age > 30 THEN 'senior' ELSE 'junior' END AS status */ private parseCaseExpressionText(): string { const start = this.curToken.position; this.nextToken(); // 跳过 CASE let depth = 1; let end = start + 'CASE'.length; while (!this.curTokenIs(TokenType.EOF) && depth > 0) { if (this.curTokenIs(TokenType.CASE)) depth++; if (this.curTokenIs(TokenType.END)) { depth--; end = this.curToken.position + 'END'.length; this.nextToken(); if (depth === 0) break; } end = this.curToken.position + this.curToken.value.length; this.nextToken(); } let text = this.sql.slice(start, end); // 可选 AS 别名 if (this.curTokenIs(TokenType.AS)) { this.nextToken(); text += ` AS ${this.expectIdentifier('alias')}`; } else if (this.curToken.type === TokenType.IDENTIFIER && !this.curTokenIs(TokenType.COMMA) && !this._isReservedAfterFrom()) { text += ` AS ${this.curToken.value}`; this.nextToken(); } return text; } /** 解析聚合函数调用: COUNT(*), SUM(col), AVG(col), MIN(col), MAX(col),v0.4.0 支持 COUNT(DISTINCT col) */ private parseAggregateCall(): string { const func = this.curToken.value.toUpperCase(); this.nextToken(); this.expect(TokenType.LPAREN); // v0.4.0: COUNT(DISTINCT col) 等去重聚合 let distinct = false; if (this.curTokenIs(TokenType.DISTINCT)) { distinct = true; this.nextToken(); } 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(); } const inner = distinct ? `DISTINCT ${arg}` : arg; if (alias) { return `${func}(${inner}) AS ${alias}`; } return `${func}(${inner})`; } private _isAggregateAlias(): boolean { return !this._isReservedAfterFrom() && !this._isJoinKeyword(); } private parseIdentifierList(): string[] { const ids: string[] = []; ids.push(this.parseIdentifierWithDot()); while (this.curTokenIs(TokenType.COMMA)) { this.nextToken(); ids.push(this.parseIdentifierWithDot()); } 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.parseIdentifierWithDot(); let direction: SortDirection = 'asc'; if (this.curTokenIs(TokenType.ASC)) { this.nextToken(); } else if (this.curTokenIs(TokenType.DESC)) { direction = 'desc'; this.nextToken(); } // v0.4.0: NULLS FIRST / NULLS LAST let nulls: 'first' | 'last' | undefined; if (this.curTokenIs(TokenType.IDENTIFIER) && this.curToken.value.toUpperCase() === 'NULLS') { this.nextToken(); if (this.curTokenIs(TokenType.IDENTIFIER) && this.curToken.value.toUpperCase() === 'FIRST') { nulls = 'first'; this.nextToken(); } else if (this.curTokenIs(TokenType.IDENTIFIER) && this.curToken.value.toUpperCase() === 'LAST') { nulls = 'last'; this.nextToken(); } } return { column, direction, ...(nulls ? { nulls } : {}) }; } /** v0.4.0: 标识符(支持 'table.column' 带表前缀引用,用于 ORDER BY / GROUP BY) */ private parseIdentifierWithDot(): string { const first = this.expectIdentifier('identifier'); if (this.curTokenIs(TokenType.DOT)) { this.nextToken(); return `${first}.${this.expectIdentifier('identifier')}`; } return first; } /** 解析字面量值 */ 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 { // v0.8.0: 分隔标识符 "col" 与普通标识符等价(但不参与关键字识别, // 因此可以用它引用保留字列名,如 "order" / "select") if (this.curToken.type === TokenType.QUOTED_IDENTIFIER) { const val = this.curToken.value; this.nextToken(); return val; } 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 && // v0.8.0: 分隔标识符由 expectIdentifier 的显式分支处理,不走"关键字当标识符"兜底 this.curToken.type !== TokenType.QUOTED_IDENTIFIER && 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; } /** 解析 SQL 字符串为 AST Statement 数组(分号分隔的多语句支持,v0.3.0) */ export function parseAll(sql: string): Statement[] { const parser = new Parser(sql); return parser.parseAllStatements(); } /** 解析独立 WHERE 条件表达式(CASE WHEN 求值等场景,v0.3.1) */ export function parseWhereCondition(sql: string): WhereCondition { const parser = new Parser(sql); return parser.parseWhere(); }