UNPKG

rawsql-ts

Version:

High-performance SQL parser and AST analyzer written in TypeScript. Provides fast parsing and advanced transformation capabilities.

331 lines 18.3 kB
import { SqlTokenizer } from './SqlTokenizer'; import { SelectQueryParser } from './SelectQueryParser'; import { InsertQueryParser } from './InsertQueryParser'; import { UpdateQueryParser } from './UpdateQueryParser'; import { DeleteQueryParser } from './DeleteQueryParser'; import { CreateTableParser } from './CreateTableParser'; import { MergeQueryParser } from './MergeQueryParser'; import { WithClauseParser } from './WithClauseParser'; import { DropTableParser } from './DropTableParser'; import { DropIndexParser } from './DropIndexParser'; import { CreateIndexParser } from './CreateIndexParser'; import { AlterTableParser } from './AlterTableParser'; import { DropConstraintParser } from './DropConstraintParser'; import { AnalyzeStatementParser } from './AnalyzeStatementParser'; import { ExplainStatementParser } from './ExplainStatementParser'; import { CreateSequenceParser, AlterSequenceParser } from './SequenceParser'; import { CreateSchemaParser } from './CreateSchemaParser'; import { DropSchemaParser } from './DropSchemaParser'; import { VacuumStatementParser } from './VacuumStatementParser'; import { ReindexStatementParser } from './ReindexStatementParser'; import { ClusterStatementParser } from './ClusterStatementParser'; import { CheckpointStatementParser } from './CheckpointStatementParser'; import { CommentOnParser } from './CommentOnParser'; /** * Canonical entry point for SQL parsing. * Delegates to dedicated parsers for SELECT, INSERT, UPDATE, and DELETE statements, and is designed to embrace additional statement types next. */ export class SqlParser { static parse(sql, options = {}) { var _a, _b; const skipEmpty = (_a = options.skipEmptyStatements) !== null && _a !== void 0 ? _a : true; const mode = (_b = options.mode) !== null && _b !== void 0 ? _b : 'single'; const tokenizer = new SqlTokenizer(sql); // Fast path for the common single-statement parse used by benchmarks and most callers. if (mode === 'single' && skipEmpty) { const first = this.readNextMeaningfulStatement(tokenizer, 0); if (!first) { throw new Error('[SqlParser] No SQL statements found in input.'); } const parsed = this.dispatchParse(first, 1); const remainder = this.readNextMeaningfulStatement(tokenizer, first.nextPosition); if (remainder) { throw new Error('[SqlParser] Unexpected additional statement detected at index 2. Use parseMany or set mode to "multiple" to allow multiple statements.'); } return parsed; } // Acquire the first meaningful statement so future dispatching can inspect its leading keyword. const first = this.consumeNextStatement(tokenizer, 0, skipEmpty); if (!first) { throw new Error('[SqlParser] No SQL statements found in input.'); } const parsed = this.dispatchParse(first.segment, 1); if (mode === 'single') { // Ensure callers opting into single-statement mode are protected against trailing statements. const remainder = this.consumeNextStatement(tokenizer, first.nextCursor, skipEmpty); if (remainder) { throw new Error('[SqlParser] Unexpected additional statement detected at index 2. Use parseMany or set mode to "multiple" to allow multiple statements.'); } } return parsed; } static parseMany(sql, options = {}) { var _a; const skipEmpty = (_a = options.skipEmptyStatements) !== null && _a !== void 0 ? _a : true; const tokenizer = new SqlTokenizer(sql); const statements = []; let cursor = 0; let carry = null; let index = 0; while (true) { // Collect the next logical statement segment, carrying forward detached comments when necessary. const segment = tokenizer.readNextStatement(cursor, carry); carry = null; if (!segment) { break; } cursor = segment.nextPosition; if (segment.lexemes.length === 0) { // Preserve dangling comments so they can attach to the next real statement. if (segment.leadingComments && segment.leadingComments.length > 0) { carry = segment.leadingComments; } if (skipEmpty || segment.rawText.trim().length === 0) { continue; } } index++; statements.push(this.dispatchParse(segment, index)); } return statements; } static dispatchParse(segment, statementIndex) { if (segment.lexemes.length === 0) { throw new Error(`[SqlParser] Statement ${statementIndex} does not contain any tokens.`); } const firstToken = segment.lexemes[0].value.toLowerCase(); switch (firstToken) { case 'select': case 'values': return this.parseSelectStatement(segment, statementIndex); case 'with': { const commandAfterWith = this.getCommandAfterWith(segment.lexemes); switch (commandAfterWith) { case 'insert into': return this.parseInsertStatement(segment, statementIndex); case 'update': return this.parseUpdateStatement(segment, statementIndex); case 'delete from': return this.parseDeleteStatement(segment, statementIndex); case 'merge into': return this.parseMergeStatement(segment, statementIndex); default: return this.parseSelectStatement(segment, statementIndex); } } case 'insert into': return this.parseInsertStatement(segment, statementIndex); case 'update': return this.parseUpdateStatement(segment, statementIndex); case 'delete from': return this.parseDeleteStatement(segment, statementIndex); case 'create table': case 'create temporary table': case 'create unlogged table': return this.parseCreateTableStatement(segment, statementIndex); case 'merge into': return this.parseMergeStatement(segment, statementIndex); case 'create index': case 'create unique index': return this.parseCreateIndexStatement(segment, statementIndex); case 'create schema': return this.parseCreateSchemaStatement(segment, statementIndex); case 'create sequence': case 'create temporary sequence': case 'create temp sequence': return this.parseCreateSequenceStatement(segment, statementIndex); case 'drop table': return this.parseDropTableStatement(segment, statementIndex); case 'drop schema': return this.parseDropSchemaStatement(segment, statementIndex); case 'drop index': return this.parseDropIndexStatement(segment, statementIndex); case 'alter table': return this.parseAlterTableStatement(segment, statementIndex); case 'alter sequence': return this.parseAlterSequenceStatement(segment, statementIndex); case 'drop constraint': return this.parseDropConstraintStatement(segment, statementIndex); case 'comment on table': case 'comment on column': return this.parseCommentOnStatement(segment, statementIndex); case 'analyze': return this.parseAnalyzeStatement(segment, statementIndex); case 'explain': return this.parseExplainStatement(segment, statementIndex); case 'vacuum': case 'vacuum full': return this.parseVacuumStatement(segment, statementIndex); case 'reindex': case 'reindex table': case 'reindex index': case 'reindex schema': return this.parseReindexStatement(segment, statementIndex); case 'cluster': return this.parseClusterStatement(segment, statementIndex); case 'checkpoint': return this.parseCheckpointStatement(segment, statementIndex); default: throw new Error(`[SqlParser] Statement ${statementIndex} starts with unsupported token "${segment.lexemes[0].value}".`); } } static parseSelectStatement(segment, statementIndex) { return this.parseStatementWithParser(segment, statementIndex, 'SELECT', (lexemes, startIndex) => SelectQueryParser.parseFromLexeme(lexemes, startIndex)); } static parseExplainStatement(segment, statementIndex) { return this.parseStatementWithCallback(segment, statementIndex, 'EXPLAIN', () => ExplainStatementParser.parseFromLexeme(segment.lexemes, 0, (lexemes, nestedStart) => { if (nestedStart >= lexemes.length) { throw new Error("[ExplainStatementParser] Missing statement after EXPLAIN options."); } const nestedSegment = { lexemes: lexemes.slice(nestedStart), statementStart: segment.statementStart, statementEnd: segment.statementEnd, nextPosition: segment.nextPosition, rawText: segment.rawText, leadingComments: segment.leadingComments, }; const statement = this.dispatchParse(nestedSegment, statementIndex); return { value: statement, newIndex: lexemes.length }; }), `EXPLAIN statement ${statementIndex}`); } static parseVacuumStatement(segment, statementIndex) { return this.parseStatementWithParser(segment, statementIndex, 'VACUUM', (lexemes, startIndex) => VacuumStatementParser.parseFromLexeme(lexemes, startIndex), `VACUUM statement ${statementIndex}`); } static parseReindexStatement(segment, statementIndex) { return this.parseStatementWithParser(segment, statementIndex, 'REINDEX', (lexemes, startIndex) => ReindexStatementParser.parseFromLexeme(lexemes, startIndex), `REINDEX statement ${statementIndex}`); } static parseClusterStatement(segment, statementIndex) { return this.parseStatementWithParser(segment, statementIndex, 'CLUSTER', (lexemes, startIndex) => ClusterStatementParser.parseFromLexeme(lexemes, startIndex), `CLUSTER statement ${statementIndex}`); } static parseCheckpointStatement(segment, statementIndex) { return this.parseStatementWithParser(segment, statementIndex, 'CHECKPOINT', (lexemes, startIndex) => CheckpointStatementParser.parseFromLexeme(lexemes, startIndex), `CHECKPOINT statement ${statementIndex}`); } static parseInsertStatement(segment, statementIndex) { return this.parseStatementWithParser(segment, statementIndex, 'INSERT', (lexemes, startIndex) => InsertQueryParser.parseFromLexeme(lexemes, startIndex)); } static parseUpdateStatement(segment, statementIndex) { return this.parseStatementWithParser(segment, statementIndex, 'UPDATE', (lexemes, startIndex) => UpdateQueryParser.parseFromLexeme(lexemes, startIndex)); } static parseDeleteStatement(segment, statementIndex) { return this.parseStatementWithParser(segment, statementIndex, 'DELETE', (lexemes, startIndex) => DeleteQueryParser.parseFromLexeme(lexemes, startIndex)); } static parseCreateTableStatement(segment, statementIndex) { return this.parseStatementWithParser(segment, statementIndex, 'CREATE TABLE', (lexemes, startIndex) => CreateTableParser.parseFromLexeme(lexemes, startIndex)); } static parseDropTableStatement(segment, statementIndex) { return this.parseStatementWithParser(segment, statementIndex, 'DROP TABLE', (lexemes, startIndex) => DropTableParser.parseFromLexeme(lexemes, startIndex)); } static parseDropSchemaStatement(segment, statementIndex) { return this.parseStatementWithParser(segment, statementIndex, 'DROP SCHEMA', (lexemes, startIndex) => DropSchemaParser.parseFromLexeme(lexemes, startIndex)); } static parseDropIndexStatement(segment, statementIndex) { return this.parseStatementWithParser(segment, statementIndex, 'DROP INDEX', (lexemes, startIndex) => DropIndexParser.parseFromLexeme(lexemes, startIndex)); } static parseCreateIndexStatement(segment, statementIndex) { return this.parseStatementWithParser(segment, statementIndex, 'CREATE INDEX', (lexemes, startIndex) => CreateIndexParser.parseFromLexeme(lexemes, startIndex)); } static parseCreateSchemaStatement(segment, statementIndex) { return this.parseStatementWithParser(segment, statementIndex, 'CREATE SCHEMA', (lexemes, startIndex) => CreateSchemaParser.parseFromLexeme(lexemes, startIndex)); } static parseCreateSequenceStatement(segment, statementIndex) { return this.parseStatementWithParser(segment, statementIndex, 'CREATE SEQUENCE', (lexemes, startIndex) => CreateSequenceParser.parseFromLexeme(lexemes, startIndex)); } static parseAlterSequenceStatement(segment, statementIndex) { return this.parseStatementWithParser(segment, statementIndex, 'ALTER SEQUENCE', (lexemes, startIndex) => AlterSequenceParser.parseFromLexeme(lexemes, startIndex)); } static parseAlterTableStatement(segment, statementIndex) { return this.parseStatementWithParser(segment, statementIndex, 'ALTER TABLE', (lexemes, startIndex) => AlterTableParser.parseFromLexeme(lexemes, startIndex)); } static parseDropConstraintStatement(segment, statementIndex) { return this.parseStatementWithParser(segment, statementIndex, 'DROP CONSTRAINT', (lexemes, startIndex) => DropConstraintParser.parseFromLexeme(lexemes, startIndex)); } static parseCommentOnStatement(segment, statementIndex) { return this.parseStatementWithParser(segment, statementIndex, 'COMMENT ON', (lexemes, startIndex) => CommentOnParser.parseFromLexeme(lexemes, startIndex)); } static parseAnalyzeStatement(segment, statementIndex) { return this.parseStatementWithParser(segment, statementIndex, 'ANALYZE', (lexemes, startIndex) => AnalyzeStatementParser.parseFromLexeme(lexemes, startIndex)); } static parseMergeStatement(segment, statementIndex) { return this.parseStatementWithParser(segment, statementIndex, 'MERGE', (lexemes, startIndex) => MergeQueryParser.parseFromLexeme(lexemes, startIndex)); } static parseStatementWithParser(segment, statementIndex, statementLabel, parser, trailingContext = `statement ${statementIndex}`) { return this.parseStatementWithCallback(segment, statementIndex, statementLabel, () => parser(segment.lexemes, 0), trailingContext); } static parseStatementWithCallback(segment, statementIndex, statementLabel, parse, trailingContext = `statement ${statementIndex}`) { try { const result = parse(); // Keep trailing-token validation centralized so every statement parser reports the same shape of error. this.assertFullyConsumed(segment, result.newIndex, trailingContext); return result.value; } catch (error) { throw new Error(`[SqlParser] Failed to parse ${statementLabel} statement ${statementIndex}: ${this.errorMessage(error)}`); } } static assertFullyConsumed(segment, newIndex, trailingContext) { var _a, _b; if (newIndex >= segment.lexemes.length) { return; } const unexpected = segment.lexemes[newIndex]; const position = (_b = (_a = unexpected.position) === null || _a === void 0 ? void 0 : _a.startPosition) !== null && _b !== void 0 ? _b : segment.statementStart; throw new Error(`[SqlParser] Unexpected token "${unexpected.value}" in ${trailingContext} at character ${position}.`); } static errorMessage(error) { return error instanceof Error ? error.message : String(error); } static getCommandAfterWith(lexemes) { var _a; try { const withResult = WithClauseParser.parseFromLexeme(lexemes, 0); const next = lexemes[withResult.newIndex]; return (_a = next === null || next === void 0 ? void 0 : next.value.toLowerCase()) !== null && _a !== void 0 ? _a : null; } catch (_b) { return null; } } static readNextMeaningfulStatement(tokenizer, cursor) { let localCursor = cursor; let carry = null; while (true) { const segment = tokenizer.readNextStatement(localCursor, carry); carry = null; if (!segment) { return null; } if (segment.lexemes.length > 0) { return segment; } localCursor = segment.nextPosition; if (segment.leadingComments && segment.leadingComments.length > 0) { carry = segment.leadingComments; } } } static consumeNextStatement(tokenizer, cursor, skipEmpty) { let localCursor = cursor; let carry = null; // Advance until a statement with tokens is found or the input ends. while (true) { const segment = tokenizer.readNextStatement(localCursor, carry); carry = null; if (!segment) { return null; } localCursor = segment.nextPosition; if (segment.lexemes.length === 0) { // Retain comments so the next statement can inherit them when appropriate. if (segment.leadingComments && segment.leadingComments.length > 0) { carry = segment.leadingComments; } if (skipEmpty || segment.rawText.trim().length === 0) { continue; } } return { segment, nextCursor: localCursor }; } } } //# sourceMappingURL=SqlParser.js.map