rawsql-ts
Version:
[beta]High-performance SQL parser and AST analyzer written in TypeScript. Provides fast parsing and advanced transformation capabilities.
213 lines • 11.1 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.ValueParser = void 0;
const Lexeme_1 = require("../models/Lexeme");
const ValueComponent_1 = require("../models/ValueComponent");
const SqlTokenizer_1 = require("./SqlTokenizer");
const IdentifierParser_1 = require("./IdentifierParser");
const LiteralParser_1 = require("./LiteralParser");
const ParenExpressionParser_1 = require("./ParenExpressionParser");
const UnaryExpressionParser_1 = require("./UnaryExpressionParser");
const ParameterExpressionParser_1 = require("./ParameterExpressionParser");
const StringSpecifierExpressionParser_1 = require("./StringSpecifierExpressionParser");
const CommandExpressionParser_1 = require("./CommandExpressionParser");
const FunctionExpressionParser_1 = require("./FunctionExpressionParser");
const FullNameParser_1 = require("./FullNameParser");
const ParseError_1 = require("./ParseError");
const OperatorPrecedence_1 = require("../utils/OperatorPrecedence");
class ValueParser {
// Parse SQL string to AST (was: parse)
static parse(query) {
const tokenizer = new SqlTokenizer_1.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 ParseError_1.ParseError.fromUnparsedLexemes(lexemes, result.newIndex, `[ValueParser]`);
}
return result.value;
}
/**
* Parse from lexeme array with logical operator controls
*/
static parseFromLexeme(lexemes, index, allowAndOperator = true, allowOrOperator = true) {
return this.parseExpressionWithPrecedence(lexemes, index, 0, allowAndOperator, allowOrOperator);
}
/**
* Parse expressions with operator precedence handling
* Uses precedence climbing algorithm
*/
static parseExpressionWithPrecedence(lexemes, index, minPrecedence, allowAndOperator = true, allowOrOperator = true) {
let idx = index;
// Parse the primary expression (left side)
const comment = lexemes[idx].comments;
const left = this.parseItem(lexemes, idx);
left.value.comments = comment;
idx = left.newIndex;
let result = left.value;
// Process operators with precedence
while (idx < lexemes.length && (lexemes[idx].type & Lexeme_1.TokenType.Operator)) {
const operatorToken = lexemes[idx];
const operator = operatorToken.value;
// Check if this operator is allowed
if (!allowAndOperator && operator.toLowerCase() === "and") {
break;
}
if (!allowOrOperator && operator.toLowerCase() === "or") {
break;
}
// Get operator precedence
const precedence = OperatorPrecedence_1.OperatorPrecedence.getPrecedence(operator);
// If this operator has lower precedence than minimum, stop
if (precedence < minPrecedence) {
break;
}
idx++; // consume operator // Handle BETWEEN specially as it has different syntax
if (OperatorPrecedence_1.OperatorPrecedence.isBetweenOperator(operator)) {
const betweenResult = FunctionExpressionParser_1.FunctionExpressionParser.parseBetweenExpression(lexemes, idx, result, operator.toLowerCase().includes('not'));
result = betweenResult.value;
idx = betweenResult.newIndex;
continue;
}
// Handle :: (cast) operator specially
if (operator === "::") {
const typeValue = FunctionExpressionParser_1.FunctionExpressionParser.parseTypeValue(lexemes, idx);
result = new ValueComponent_1.CastExpression(result, typeValue.value);
idx = typeValue.newIndex;
continue;
}
// For left-associative operators, use precedence + 1
const nextMinPrecedence = precedence + 1;
// Parse the right-hand side with higher precedence
const rightResult = this.parseExpressionWithPrecedence(lexemes, idx, nextMinPrecedence, allowAndOperator, allowOrOperator);
idx = rightResult.newIndex;
// Create binary expression directly
result = new ValueComponent_1.BinaryExpression(result, operator, rightResult.value);
}
return { value: result, newIndex: idx };
}
static parseItem(lexemes, index) {
let idx = index;
// Range check
if (idx >= lexemes.length) {
throw new Error(`Unexpected end of lexemes at index ${index}`);
}
const current = lexemes[idx];
if (current.type & Lexeme_1.TokenType.Identifier && current.type & Lexeme_1.TokenType.Operator && current.type & Lexeme_1.TokenType.Type) {
// Typed literal format pattern
// e.g., `interval '2 days'`
const first = IdentifierParser_1.IdentifierParser.parseFromLexeme(lexemes, idx);
if (first.newIndex >= lexemes.length) {
return first;
}
const next = lexemes[first.newIndex];
if (next.type & Lexeme_1.TokenType.Literal) {
// Typed literal format
const second = LiteralParser_1.LiteralParser.parseFromLexeme(lexemes, first.newIndex);
const result = new ValueComponent_1.UnaryExpression(lexemes[idx].value, second.value);
return { value: result, newIndex: second.newIndex };
}
return first;
}
else if (current.type & Lexeme_1.TokenType.Identifier) {
const { namespaces, name, newIndex } = FullNameParser_1.FullNameParser.parseFromLexeme(lexemes, idx);
// Namespace is also recognized as Identifier.
// Since functions and types, as well as columns (tables), can have namespaces,
// it is necessary to determine by the last element of the identifier.
if (lexemes[newIndex - 1].type & (Lexeme_1.TokenType.Function | Lexeme_1.TokenType.Type)) {
return FunctionExpressionParser_1.FunctionExpressionParser.parseFromLexeme(lexemes, idx);
}
const value = new ValueComponent_1.ColumnReference(namespaces, name);
return { value, newIndex };
}
else if (current.type & Lexeme_1.TokenType.Literal) {
return LiteralParser_1.LiteralParser.parseFromLexeme(lexemes, idx);
}
else if (current.type & Lexeme_1.TokenType.OpenParen) {
return ParenExpressionParser_1.ParenExpressionParser.parseFromLexeme(lexemes, idx);
}
else if (current.type & Lexeme_1.TokenType.Function) {
return FunctionExpressionParser_1.FunctionExpressionParser.parseFromLexeme(lexemes, idx);
}
else if (current.type & Lexeme_1.TokenType.Operator) {
return UnaryExpressionParser_1.UnaryExpressionParser.parseFromLexeme(lexemes, idx);
}
else if (current.type & Lexeme_1.TokenType.Parameter) {
return ParameterExpressionParser_1.ParameterExpressionParser.parseFromLexeme(lexemes, idx);
}
else if (current.type & Lexeme_1.TokenType.StringSpecifier) {
return StringSpecifierExpressionParser_1.StringSpecifierExpressionParser.parseFromLexeme(lexemes, idx);
}
else if (current.type & Lexeme_1.TokenType.Command) {
return CommandExpressionParser_1.CommandExpressionParser.parseFromLexeme(lexemes, idx);
}
else if (current.type & Lexeme_1.TokenType.OpenBracket) {
// SQLServer escape identifier format. e.g. [dbo] or [dbo].[table]
const { namespaces, name, newIndex } = FullNameParser_1.FullNameParser.parseFromLexeme(lexemes, idx);
const value = new ValueComponent_1.ColumnReference(namespaces, name);
return { value, newIndex };
}
else if (current.type & Lexeme_1.TokenType.Type) {
// Handle standalone type tokens
const { namespaces, name, newIndex } = FullNameParser_1.FullNameParser.parseFromLexeme(lexemes, idx);
const value = new ValueComponent_1.TypeValue(namespaces, name);
return { value, newIndex };
}
throw new Error(`[ValueParser] Invalid lexeme. index: ${idx}, type: ${lexemes[idx].type}, value: ${lexemes[idx].value}`);
}
static parseArgument(openToken, closeToken, lexemes, index) {
let idx = index;
const args = [];
// Check for opening parenthesis
if (idx < lexemes.length && lexemes[idx].type === openToken) {
idx++;
if (idx < lexemes.length && lexemes[idx].type === closeToken) {
// If there are no arguments, return an empty ValueList
idx++;
return { value: new ValueComponent_1.ValueList([]), newIndex: idx };
}
// If the next element is `*`, treat `*` as an Identifier
if (idx < lexemes.length && lexemes[idx].value === "*") {
const wildcard = new ValueComponent_1.ColumnReference(null, "*");
idx++;
// The next element must be closeToken
if (idx < lexemes.length && lexemes[idx].type === closeToken) {
idx++;
return { value: wildcard, newIndex: idx };
}
else {
throw new Error(`Expected closing parenthesis at index ${idx}`);
}
}
// Parse the value inside
const result = this.parseFromLexeme(lexemes, idx);
idx = result.newIndex;
args.push(result.value);
// Continue reading if the next element is a comma
while (idx < lexemes.length && (lexemes[idx].type & Lexeme_1.TokenType.Comma)) {
idx++;
const argResult = this.parseFromLexeme(lexemes, idx);
idx = argResult.newIndex;
args.push(argResult.value);
}
// Check for closing parenthesis
if (idx < lexemes.length && lexemes[idx].type === closeToken) {
idx++;
if (args.length === 1) {
// Return as is if there is only one argument
return { value: args[0], newIndex: idx };
}
// Create ValueCollection if there are multiple arguments
const value = new ValueComponent_1.ValueList(args);
return { value, newIndex: idx };
}
else {
throw new Error(`Missing closing parenthesis at index ${idx}`);
}
}
throw new Error(`Expected opening parenthesis at index ${index}`);
}
}
exports.ValueParser = ValueParser;
//# sourceMappingURL=ValueParser.js.map