UNPKG

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
"use strict"; 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