rawsql-ts
Version:
High-performance SQL parser and AST analyzer written in TypeScript. Provides fast parsing and advanced transformation capabilities.
335 lines • 19 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.SqlParser = void 0;
const SqlTokenizer_1 = require("./SqlTokenizer");
const SelectQueryParser_1 = require("./SelectQueryParser");
const InsertQueryParser_1 = require("./InsertQueryParser");
const UpdateQueryParser_1 = require("./UpdateQueryParser");
const DeleteQueryParser_1 = require("./DeleteQueryParser");
const CreateTableParser_1 = require("./CreateTableParser");
const MergeQueryParser_1 = require("./MergeQueryParser");
const WithClauseParser_1 = require("./WithClauseParser");
const DropTableParser_1 = require("./DropTableParser");
const DropIndexParser_1 = require("./DropIndexParser");
const CreateIndexParser_1 = require("./CreateIndexParser");
const AlterTableParser_1 = require("./AlterTableParser");
const DropConstraintParser_1 = require("./DropConstraintParser");
const AnalyzeStatementParser_1 = require("./AnalyzeStatementParser");
const ExplainStatementParser_1 = require("./ExplainStatementParser");
const SequenceParser_1 = require("./SequenceParser");
const CreateSchemaParser_1 = require("./CreateSchemaParser");
const DropSchemaParser_1 = require("./DropSchemaParser");
const VacuumStatementParser_1 = require("./VacuumStatementParser");
const ReindexStatementParser_1 = require("./ReindexStatementParser");
const ClusterStatementParser_1 = require("./ClusterStatementParser");
const CheckpointStatementParser_1 = require("./CheckpointStatementParser");
const CommentOnParser_1 = require("./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.
*/
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_1.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_1.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_1.SelectQueryParser.parseFromLexeme(lexemes, startIndex));
}
static parseExplainStatement(segment, statementIndex) {
return this.parseStatementWithCallback(segment, statementIndex, 'EXPLAIN', () => ExplainStatementParser_1.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_1.VacuumStatementParser.parseFromLexeme(lexemes, startIndex), `VACUUM statement ${statementIndex}`);
}
static parseReindexStatement(segment, statementIndex) {
return this.parseStatementWithParser(segment, statementIndex, 'REINDEX', (lexemes, startIndex) => ReindexStatementParser_1.ReindexStatementParser.parseFromLexeme(lexemes, startIndex), `REINDEX statement ${statementIndex}`);
}
static parseClusterStatement(segment, statementIndex) {
return this.parseStatementWithParser(segment, statementIndex, 'CLUSTER', (lexemes, startIndex) => ClusterStatementParser_1.ClusterStatementParser.parseFromLexeme(lexemes, startIndex), `CLUSTER statement ${statementIndex}`);
}
static parseCheckpointStatement(segment, statementIndex) {
return this.parseStatementWithParser(segment, statementIndex, 'CHECKPOINT', (lexemes, startIndex) => CheckpointStatementParser_1.CheckpointStatementParser.parseFromLexeme(lexemes, startIndex), `CHECKPOINT statement ${statementIndex}`);
}
static parseInsertStatement(segment, statementIndex) {
return this.parseStatementWithParser(segment, statementIndex, 'INSERT', (lexemes, startIndex) => InsertQueryParser_1.InsertQueryParser.parseFromLexeme(lexemes, startIndex));
}
static parseUpdateStatement(segment, statementIndex) {
return this.parseStatementWithParser(segment, statementIndex, 'UPDATE', (lexemes, startIndex) => UpdateQueryParser_1.UpdateQueryParser.parseFromLexeme(lexemes, startIndex));
}
static parseDeleteStatement(segment, statementIndex) {
return this.parseStatementWithParser(segment, statementIndex, 'DELETE', (lexemes, startIndex) => DeleteQueryParser_1.DeleteQueryParser.parseFromLexeme(lexemes, startIndex));
}
static parseCreateTableStatement(segment, statementIndex) {
return this.parseStatementWithParser(segment, statementIndex, 'CREATE TABLE', (lexemes, startIndex) => CreateTableParser_1.CreateTableParser.parseFromLexeme(lexemes, startIndex));
}
static parseDropTableStatement(segment, statementIndex) {
return this.parseStatementWithParser(segment, statementIndex, 'DROP TABLE', (lexemes, startIndex) => DropTableParser_1.DropTableParser.parseFromLexeme(lexemes, startIndex));
}
static parseDropSchemaStatement(segment, statementIndex) {
return this.parseStatementWithParser(segment, statementIndex, 'DROP SCHEMA', (lexemes, startIndex) => DropSchemaParser_1.DropSchemaParser.parseFromLexeme(lexemes, startIndex));
}
static parseDropIndexStatement(segment, statementIndex) {
return this.parseStatementWithParser(segment, statementIndex, 'DROP INDEX', (lexemes, startIndex) => DropIndexParser_1.DropIndexParser.parseFromLexeme(lexemes, startIndex));
}
static parseCreateIndexStatement(segment, statementIndex) {
return this.parseStatementWithParser(segment, statementIndex, 'CREATE INDEX', (lexemes, startIndex) => CreateIndexParser_1.CreateIndexParser.parseFromLexeme(lexemes, startIndex));
}
static parseCreateSchemaStatement(segment, statementIndex) {
return this.parseStatementWithParser(segment, statementIndex, 'CREATE SCHEMA', (lexemes, startIndex) => CreateSchemaParser_1.CreateSchemaParser.parseFromLexeme(lexemes, startIndex));
}
static parseCreateSequenceStatement(segment, statementIndex) {
return this.parseStatementWithParser(segment, statementIndex, 'CREATE SEQUENCE', (lexemes, startIndex) => SequenceParser_1.CreateSequenceParser.parseFromLexeme(lexemes, startIndex));
}
static parseAlterSequenceStatement(segment, statementIndex) {
return this.parseStatementWithParser(segment, statementIndex, 'ALTER SEQUENCE', (lexemes, startIndex) => SequenceParser_1.AlterSequenceParser.parseFromLexeme(lexemes, startIndex));
}
static parseAlterTableStatement(segment, statementIndex) {
return this.parseStatementWithParser(segment, statementIndex, 'ALTER TABLE', (lexemes, startIndex) => AlterTableParser_1.AlterTableParser.parseFromLexeme(lexemes, startIndex));
}
static parseDropConstraintStatement(segment, statementIndex) {
return this.parseStatementWithParser(segment, statementIndex, 'DROP CONSTRAINT', (lexemes, startIndex) => DropConstraintParser_1.DropConstraintParser.parseFromLexeme(lexemes, startIndex));
}
static parseCommentOnStatement(segment, statementIndex) {
return this.parseStatementWithParser(segment, statementIndex, 'COMMENT ON', (lexemes, startIndex) => CommentOnParser_1.CommentOnParser.parseFromLexeme(lexemes, startIndex));
}
static parseAnalyzeStatement(segment, statementIndex) {
return this.parseStatementWithParser(segment, statementIndex, 'ANALYZE', (lexemes, startIndex) => AnalyzeStatementParser_1.AnalyzeStatementParser.parseFromLexeme(lexemes, startIndex));
}
static parseMergeStatement(segment, statementIndex) {
return this.parseStatementWithParser(segment, statementIndex, 'MERGE', (lexemes, startIndex) => MergeQueryParser_1.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_1.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 {
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 };
}
}
}
exports.SqlParser = SqlParser;
//# sourceMappingURL=SqlParser.js.map