rawsql-ts
Version:
[beta]High-performance SQL parser and AST analyzer written in TypeScript. Provides fast parsing and advanced transformation capabilities.
109 lines • 5.66 kB
JavaScript
import { FullNameParser } from "./FullNameParser";
import { FunctionSource, SubQuerySource, TableSource } from "../models/Clause";
import { TokenType } from "../models/Lexeme";
import { SelectQueryParser } from "./SelectQueryParser";
import { SqlTokenizer } from "./SqlTokenizer";
import { ValueParser } from "./ValueParser";
export class SourceParser {
// Parse SQL string to AST (was: parse)
static parse(query) {
const tokenizer = new SqlTokenizer(query); // Initialize tokenizer
const lexemes = tokenizer.readLexmes(); // Get tokens
// Parse
const result = this.parseFromLexeme(lexemes, 0);
// Error if there are remaining tokens
if (result.newIndex < lexemes.length) {
throw new Error(`Syntax error: Unexpected token "${lexemes[result.newIndex].value}" at position ${result.newIndex}. The source component is complete but there are additional tokens.`);
}
return result.value;
}
/**
* Parses only a TableSource from the given lexemes, regardless of the presence of parentheses after the identifier.
* This method is specifically used for cases like INSERT queries (e.g., "insert into table_name (col1, col2)")
* where a parenthesis immediately following the table name could otherwise be misinterpreted as a function call.
* By using this method, the parser forcibly treats the source as a TableSource.
*
* @param lexemes The array of lexemes to parse.
* @param index The starting index in the lexeme array.
* @returns An object containing the parsed TableSource and the new index.
*/
static parseTableSourceFromLexemes(lexemes, index) {
const fullNameResult = FullNameParser.parseFromLexeme(lexemes, index);
return this.parseTableSource(fullNameResult);
}
// Parse from lexeme array (was: parse)
static parseFromLexeme(lexemes, index) {
let idx = index;
// Handle subquery
if (idx < lexemes.length && (lexemes[idx].type & TokenType.OpenParen)) {
return this.parseParenSource(lexemes, idx);
}
// Retrieve the full name only once and reuse the result
const fullNameResult = FullNameParser.parseFromLexeme(lexemes, idx);
// Handle function-based source (determine by lastTokenType)
if (fullNameResult.lastTokenType & TokenType.Function) {
// Also use fullNameResult as argument for parseFunctionSource
return SourceParser.parseFunctionSource(lexemes, fullNameResult);
}
// Handle table source (regular table, potentially schema-qualified)
return SourceParser.parseTableSource(fullNameResult);
}
static parseTableSource(fullNameResult) {
const { namespaces, name, newIndex } = fullNameResult;
const value = new TableSource(namespaces, name.name);
return { value, newIndex };
}
static parseFunctionSource(lexemes, fullNameResult) {
let idx = fullNameResult.newIndex;
const { namespaces, name } = fullNameResult;
const argument = ValueParser.parseArgument(TokenType.OpenParen, TokenType.CloseParen, lexemes, idx);
idx = argument.newIndex;
const functionName = name.name;
const result = new FunctionSource({ namespaces: namespaces, name: functionName }, argument.value);
return { value: result, newIndex: idx };
}
static parseParenSource(lexemes, index) {
let idx = index;
// skip the open parenthesis
idx++;
if (idx >= lexemes.length) {
throw new Error(`Syntax error: Unexpected end of input at position ${idx}. Expected a subquery or nested expression after opening parenthesis.`);
}
// Support both SELECT and VALUES in subqueries
const keyword = lexemes[idx].value;
if (keyword === "select" || keyword === "values" || keyword === "with") {
const result = this.parseSubQuerySource(lexemes, idx);
idx = result.newIndex;
if (idx < lexemes.length && lexemes[idx].type == TokenType.CloseParen) {
// skip the closing parenthesis
idx++;
}
else {
throw new Error(`Syntax error at position ${idx}: Missing closing parenthesis. Each opening parenthesis must have a matching closing parenthesis.`);
}
return { value: result.value, newIndex: idx };
}
else if (lexemes[idx].type == TokenType.OpenParen) {
const result = this.parseParenSource(lexemes, idx);
idx = result.newIndex;
if (idx < lexemes.length && lexemes[idx].type == TokenType.CloseParen) {
// skip the closing parenthesis
idx++;
}
else {
throw new Error(`Syntax error at position ${idx}: Missing closing parenthesis. Each opening parenthesis must have a matching closing parenthesis.`);
}
return { value: result.value, newIndex: idx };
}
throw new Error(`Syntax error at position ${idx}: Expected 'SELECT' keyword, 'VALUES' keyword, or opening parenthesis '(' but found "${lexemes[idx].value}".`);
}
static parseSubQuerySource(lexemes, index) {
let idx = index;
// Use the new parseFromLexeme method and destructure the result
const { value: selectQuery, newIndex } = SelectQueryParser.parseFromLexeme(lexemes, idx);
idx = newIndex;
const subQuerySource = new SubQuerySource(selectQuery);
return { value: subQuerySource, newIndex: idx };
}
}
//# sourceMappingURL=SourceParser.js.map