UNPKG

rawsql-ts

Version:

[beta]High-performance SQL parser and AST analyzer written in TypeScript. Provides fast parsing and advanced transformation capabilities.

163 lines 7.95 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.WindowExpressionParser = void 0; const Lexeme_1 = require("../models/Lexeme"); const ValueComponent_1 = require("../models/ValueComponent"); const OrderByClauseParser_1 = require("./OrderByClauseParser"); const PartitionByParser_1 = require("./PartitionByParser"); const SqlTokenizer_1 = require("./SqlTokenizer"); const ValueParser_1 = require("./ValueParser"); class WindowExpressionParser { // 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 new Error(`Syntax error: Unexpected token "${lexemes[result.newIndex].value}" at position ${result.newIndex}. The window frame expression is complete but there are additional tokens.`); } return result.value; } // Parse from lexeme array (was: parse) static parseFromLexeme(lexemes, index) { let idx = index; if (lexemes[idx].type !== Lexeme_1.TokenType.OpenParen) { throw new Error(`Syntax error at position ${idx}: Expected opening parenthesis '(' but found "${lexemes[idx].value}".`); } idx++; let partition = null; let order = null; let frameSpec = null; if (idx < lexemes.length && lexemes[idx].value === 'partition by') { const partitionResult = PartitionByParser_1.PartitionByParser.parseFromLexeme(lexemes, idx); partition = partitionResult.value; idx = partitionResult.newIndex; } if (idx < lexemes.length && lexemes[idx].value === 'order by') { const orderResult = OrderByClauseParser_1.OrderByClauseParser.parseFromLexeme(lexemes, idx); order = orderResult.value; idx = orderResult.newIndex; } // Parse frame clause (ROWS/RANGE/GROUPS) if (idx < lexemes.length && this.isFrameTypeKeyword(lexemes[idx].value)) { const frameSpecResult = this.parseFrameSpec(lexemes, idx); frameSpec = frameSpecResult.value; idx = frameSpecResult.newIndex; } if (idx >= lexemes.length || lexemes[idx].type !== Lexeme_1.TokenType.CloseParen) { throw new Error(`Syntax error at position ${idx}: Missing closing parenthesis ')' for window frame. Each opening parenthesis must have a matching closing parenthesis.`); } // Read close paren idx++; return { value: new ValueComponent_1.WindowFrameExpression(partition, order, frameSpec), newIndex: idx }; } static isFrameTypeKeyword(value) { const lowerValue = value; return lowerValue === 'rows' || lowerValue === 'range' || lowerValue === 'groups'; } static parseFrameSpec(lexemes, index) { let idx = index; // Determine frame type (ROWS/RANGE/GROUPS) const frameTypeStr = lexemes[idx].value; let frameType; switch (frameTypeStr) { case 'rows': frameType = ValueComponent_1.WindowFrameType.Rows; break; case 'range': frameType = ValueComponent_1.WindowFrameType.Range; break; case 'groups': frameType = ValueComponent_1.WindowFrameType.Groups; break; default: throw new Error(`Syntax error at position ${idx}: Invalid frame type "${lexemes[idx].value}". Expected one of: ROWS, RANGE, GROUPS.`); } idx++; // Check for BETWEEN ... AND ... syntax if (idx < lexemes.length && lexemes[idx].value === 'between') { // BETWEEN ... AND ... syntax idx++; // Parse start boundary const startBoundResult = this.parseFrameBoundary(lexemes, idx); const startBound = startBoundResult.value; idx = startBoundResult.newIndex; // Check for AND keyword - may be recognized as a separate token or part of a compound token if (idx >= lexemes.length || (lexemes[idx].value !== 'and')) { throw new Error(`Syntax error at position ${idx}: Expected 'AND' keyword in BETWEEN clause.`); } idx++; // Skip AND // Parse end boundary const endBoundResult = this.parseFrameBoundary(lexemes, idx); const endBound = endBoundResult.value; idx = endBoundResult.newIndex; return { value: new ValueComponent_1.WindowFrameSpec(frameType, startBound, endBound), newIndex: idx }; } else { // Single boundary specification const boundaryResult = this.parseFrameBoundary(lexemes, idx); const startBound = boundaryResult.value; idx = boundaryResult.newIndex; return { value: new ValueComponent_1.WindowFrameSpec(frameType, startBound, null), newIndex: idx }; } } static parseFrameBoundary(lexemes, index) { let idx = index; // Check for predefined boundaries if (idx < lexemes.length && (lexemes[idx].type & Lexeme_1.TokenType.Command)) { const currentValue = lexemes[idx].value; let frameBound; switch (currentValue) { case 'current row': frameBound = ValueComponent_1.WindowFrameBound.CurrentRow; break; case 'unbounded preceding': frameBound = ValueComponent_1.WindowFrameBound.UnboundedPreceding; break; case 'unbounded following': frameBound = ValueComponent_1.WindowFrameBound.UnboundedFollowing; break; default: throw new Error(`Syntax error at position ${idx}: Invalid frame type "${lexemes[idx].value}". Expected one of: ROWS, RANGE, GROUPS.`); } const bound = new ValueComponent_1.WindowFrameBoundStatic(frameBound); return { value: bound, newIndex: idx + 1 }; } else if (idx < lexemes.length && (lexemes[idx].type & Lexeme_1.TokenType.Literal)) { // Parse the numeric/literal value const valueResult = ValueParser_1.ValueParser.parseFromLexeme(lexemes, idx); idx = valueResult.newIndex; // Next token must be 'preceding' or 'following' if (idx < lexemes.length && (lexemes[idx].type & Lexeme_1.TokenType.Command)) { const direction = lexemes[idx].value; let isFollowing; if (direction === 'preceding') { isFollowing = false; } else if (direction === 'following') { isFollowing = true; } else { throw new Error(`Syntax error at position ${idx}: Expected 'preceding' or 'following' after numeric value in window frame boundary.`); } idx++; const bound = new ValueComponent_1.WindowFrameBoundaryValue(valueResult.value, isFollowing); return { value: bound, newIndex: idx }; } else { throw new Error(`Syntax error at position ${idx}: Expected 'preceding' or 'following' after numeric value in window frame boundary.`); } } throw new Error(`Syntax error at position ${idx}: Expected a valid frame boundary component.`); } } exports.WindowExpressionParser = WindowExpressionParser; //# sourceMappingURL=WindowExpressionParser.js.map