rawsql-ts
Version:
High-performance SQL parser and AST analyzer written in TypeScript. Provides fast parsing and advanced transformation capabilities.
427 lines • 23.1 kB
JavaScript
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';
/**
* 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);
// 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':
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 sequence':
case 'create temporary sequence':
case 'create temp sequence':
return this.parseCreateSequenceStatement(segment, statementIndex);
case 'drop table':
return this.parseDropTableStatement(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 'analyze':
return this.parseAnalyzeStatement(segment, statementIndex);
case 'explain':
return this.parseExplainStatement(segment, statementIndex);
default:
throw new Error(`[SqlParser] Statement ${statementIndex} starts with unsupported token "${segment.lexemes[0].value}".`);
}
}
static parseSelectStatement(segment, statementIndex) {
var _a, _b;
try {
const result = SelectQueryParser.parseFromLexeme(segment.lexemes, 0);
if (result.newIndex < segment.lexemes.length) {
const unexpected = segment.lexemes[result.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 statement ${statementIndex} at character ${position}.`);
}
return result.value;
}
catch (error) {
const message = error instanceof Error ? error.message : String(error);
throw new Error(`[SqlParser] Failed to parse SELECT statement ${statementIndex}: ${message}`);
}
}
static parseExplainStatement(segment, statementIndex) {
var _a, _b;
try {
const result = 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 };
});
if (result.newIndex < segment.lexemes.length) {
const unexpected = segment.lexemes[result.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 EXPLAIN statement ${statementIndex} at character ${position}.`);
}
return result.value;
}
catch (error) {
const message = error instanceof Error ? error.message : String(error);
throw new Error(`[SqlParser] Failed to parse EXPLAIN statement ${statementIndex}: ${message}`);
}
}
static parseInsertStatement(segment, statementIndex) {
var _a, _b;
try {
const result = InsertQueryParser.parseFromLexeme(segment.lexemes, 0);
if (result.newIndex < segment.lexemes.length) {
const unexpected = segment.lexemes[result.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 statement ${statementIndex} at character ${position}.`);
}
return result.value;
}
catch (error) {
const message = error instanceof Error ? error.message : String(error);
throw new Error(`[SqlParser] Failed to parse INSERT statement ${statementIndex}: ${message}`);
}
}
static parseUpdateStatement(segment, statementIndex) {
var _a, _b;
try {
const result = UpdateQueryParser.parseFromLexeme(segment.lexemes, 0);
if (result.newIndex < segment.lexemes.length) {
const unexpected = segment.lexemes[result.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 statement ${statementIndex} at character ${position}.`);
}
return result.value;
}
catch (error) {
const message = error instanceof Error ? error.message : String(error);
throw new Error(`[SqlParser] Failed to parse UPDATE statement ${statementIndex}: ${message}`);
}
}
static parseDeleteStatement(segment, statementIndex) {
var _a, _b;
try {
const result = DeleteQueryParser.parseFromLexeme(segment.lexemes, 0);
// Guard against trailing tokens that would indicate multiple statements in DELETE parsing.
if (result.newIndex < segment.lexemes.length) {
const unexpected = segment.lexemes[result.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 statement ${statementIndex} at character ${position}.`);
}
return result.value;
}
catch (error) {
const message = error instanceof Error ? error.message : String(error);
throw new Error(`[SqlParser] Failed to parse DELETE statement ${statementIndex}: ${message}`);
}
}
static parseCreateTableStatement(segment, statementIndex) {
var _a, _b;
try {
const result = CreateTableParser.parseFromLexeme(segment.lexemes, 0);
if (result.newIndex < segment.lexemes.length) {
const unexpected = segment.lexemes[result.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 statement ${statementIndex} at character ${position}.`);
}
return result.value;
}
catch (error) {
const message = error instanceof Error ? error.message : String(error);
throw new Error(`[SqlParser] Failed to parse CREATE TABLE statement ${statementIndex}: ${message}`);
}
}
static parseDropTableStatement(segment, statementIndex) {
var _a, _b;
try {
const result = DropTableParser.parseFromLexeme(segment.lexemes, 0);
if (result.newIndex < segment.lexemes.length) {
const unexpected = segment.lexemes[result.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 statement ${statementIndex} at character ${position}.`);
}
return result.value;
}
catch (error) {
const message = error instanceof Error ? error.message : String(error);
throw new Error(`[SqlParser] Failed to parse DROP TABLE statement ${statementIndex}: ${message}`);
}
}
static parseDropIndexStatement(segment, statementIndex) {
var _a, _b;
try {
const result = DropIndexParser.parseFromLexeme(segment.lexemes, 0);
if (result.newIndex < segment.lexemes.length) {
const unexpected = segment.lexemes[result.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 statement ${statementIndex} at character ${position}.`);
}
return result.value;
}
catch (error) {
const message = error instanceof Error ? error.message : String(error);
throw new Error(`[SqlParser] Failed to parse DROP INDEX statement ${statementIndex}: ${message}`);
}
}
static parseCreateIndexStatement(segment, statementIndex) {
var _a, _b;
try {
const result = CreateIndexParser.parseFromLexeme(segment.lexemes, 0);
if (result.newIndex < segment.lexemes.length) {
const unexpected = segment.lexemes[result.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 statement ${statementIndex} at character ${position}.`);
}
return result.value;
}
catch (error) {
const message = error instanceof Error ? error.message : String(error);
throw new Error(`[SqlParser] Failed to parse CREATE INDEX statement ${statementIndex}: ${message}`);
}
}
static parseCreateSequenceStatement(segment, statementIndex) {
var _a, _b;
try {
const result = CreateSequenceParser.parseFromLexeme(segment.lexemes, 0);
// Ensure no trailing lexemes remain after the CREATE SEQUENCE clause.
if (result.newIndex < segment.lexemes.length) {
const unexpected = segment.lexemes[result.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 statement ${statementIndex} at character ${position}.`);
}
return result.value;
}
catch (error) {
const message = error instanceof Error ? error.message : String(error);
throw new Error(`[SqlParser] Failed to parse CREATE SEQUENCE statement ${statementIndex}: ${message}`);
}
}
static parseAlterSequenceStatement(segment, statementIndex) {
var _a, _b;
try {
const result = AlterSequenceParser.parseFromLexeme(segment.lexemes, 0);
// Validate that the ALTER SEQUENCE statement consumed all available tokens.
if (result.newIndex < segment.lexemes.length) {
const unexpected = segment.lexemes[result.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 statement ${statementIndex} at character ${position}.`);
}
return result.value;
}
catch (error) {
const message = error instanceof Error ? error.message : String(error);
throw new Error(`[SqlParser] Failed to parse ALTER SEQUENCE statement ${statementIndex}: ${message}`);
}
}
static parseAlterTableStatement(segment, statementIndex) {
var _a, _b;
try {
const result = AlterTableParser.parseFromLexeme(segment.lexemes, 0);
if (result.newIndex < segment.lexemes.length) {
const unexpected = segment.lexemes[result.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 statement ${statementIndex} at character ${position}.`);
}
return result.value;
}
catch (error) {
const message = error instanceof Error ? error.message : String(error);
throw new Error(`[SqlParser] Failed to parse ALTER TABLE statement ${statementIndex}: ${message}`);
}
}
static parseDropConstraintStatement(segment, statementIndex) {
var _a, _b;
try {
const result = DropConstraintParser.parseFromLexeme(segment.lexemes, 0);
if (result.newIndex < segment.lexemes.length) {
const unexpected = segment.lexemes[result.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 statement ${statementIndex} at character ${position}.`);
}
return result.value;
}
catch (error) {
const message = error instanceof Error ? error.message : String(error);
throw new Error(`[SqlParser] Failed to parse DROP CONSTRAINT statement ${statementIndex}: ${message}`);
}
}
static parseAnalyzeStatement(segment, statementIndex) {
var _a, _b;
try {
// Delegate lexeme interpretation to the ANALYZE-specific parser.
const result = AnalyzeStatementParser.parseFromLexeme(segment.lexemes, 0);
// Ensure parsing consumed every lexeme belonging to this statement.
if (result.newIndex < segment.lexemes.length) {
const unexpected = segment.lexemes[result.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 statement ${statementIndex} at character ${position}.`);
}
return result.value;
}
catch (error) {
const message = error instanceof Error ? error.message : String(error);
throw new Error(`[SqlParser] Failed to parse ANALYZE statement ${statementIndex}: ${message}`);
}
}
static parseMergeStatement(segment, statementIndex) {
var _a, _b;
try {
const result = MergeQueryParser.parseFromLexeme(segment.lexemes, 0);
if (result.newIndex < segment.lexemes.length) {
// Guard against trailing tokens that would indicate parsing stopped prematurely.
const unexpected = segment.lexemes[result.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 statement ${statementIndex} at character ${position}.`);
}
return result.value;
}
catch (error) {
const message = error instanceof Error ? error.message : String(error);
throw new Error(`[SqlParser] Failed to parse MERGE statement ${statementIndex}: ${message}`);
}
}
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 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