001package gudusoft.gsqlparser.parser; 002 003import gudusoft.gsqlparser.EDbVendor; 004import gudusoft.gsqlparser.TBaseType; 005import gudusoft.gsqlparser.TCustomLexer; 006import gudusoft.gsqlparser.TCustomParser; 007import gudusoft.gsqlparser.TCustomSqlStatement; 008import gudusoft.gsqlparser.TLexerMdx; 009import gudusoft.gsqlparser.TParserMdx; 010import gudusoft.gsqlparser.TSourceToken; 011import gudusoft.gsqlparser.TSourceTokenList; 012import gudusoft.gsqlparser.TStatementList; 013import gudusoft.gsqlparser.TSyntaxError; 014import gudusoft.gsqlparser.EFindSqlStateType; 015import gudusoft.gsqlparser.ETokenType; 016import gudusoft.gsqlparser.ETokenStatus; 017import gudusoft.gsqlparser.ESqlStatementType; 018import gudusoft.gsqlparser.EErrorType; 019import gudusoft.gsqlparser.stmt.TUnknownSqlStatement; 020import gudusoft.gsqlparser.sqlcmds.ISqlCmds; 021import gudusoft.gsqlparser.sqlcmds.SqlCmdsFactory; 022import gudusoft.gsqlparser.compiler.TContext; 023import gudusoft.gsqlparser.sqlenv.TSQLEnv; 024import gudusoft.gsqlparser.compiler.TGlobalScope; 025import gudusoft.gsqlparser.compiler.TFrame; 026import gudusoft.gsqlparser.resolver.TSQLResolver; 027import gudusoft.gsqlparser.TLog; 028import gudusoft.gsqlparser.compiler.TASTEvaluator; 029 030import java.io.BufferedReader; 031import java.util.ArrayList; 032import java.util.List; 033import java.util.Stack; 034 035/** 036 * MDX (Multidimensional Expressions) SQL parser implementation. 037 * 038 * <p>This parser handles MDX-specific syntax including: 039 * <ul> 040 * <li>MDX SELECT statements with axes specifications</li> 041 * <li>SCOPE statements for calculated members</li> 042 * <li>IF/CASE/END block structures</li> 043 * <li>MDX-specific functions and operators</li> 044 * </ul> 045 * 046 * <p><b>Design Notes:</b> 047 * <ul> 048 * <li>Extends {@link AbstractSqlParser} using the template method pattern</li> 049 * <li>Uses {@link TLexerMdx} for tokenization</li> 050 * <li>Uses {@link TParserMdx} for parsing</li> 051 * <li>Delimiter character: ';' for MDX statements</li> 052 * </ul> 053 * 054 * <p><b>Usage Example:</b> 055 * <pre> 056 * // Get MDX parser from factory 057 * SqlParser parser = SqlParserFactory.get(EDbVendor.dbvmdx); 058 * 059 * // Build context 060 * ParserContext context = new ParserContext.Builder(EDbVendor.dbvmdx) 061 * .sqlText("SELECT [Measures].[Sales] ON COLUMNS FROM [Cube]") 062 * .build(); 063 * 064 * // Parse 065 * SqlParseResult result = parser.parse(context); 066 * 067 * // Access statements 068 * TStatementList statements = result.getSqlStatements(); 069 * </pre> 070 * 071 * @see SqlParser 072 * @see AbstractSqlParser 073 * @see TLexerMdx 074 * @see TParserMdx 075 * @since 3.2.0.0 076 */ 077public class MdxSqlParser extends AbstractSqlParser { 078 079 /** 080 * Construct MDX SQL parser. 081 * <p> 082 * Configures the parser for MDX with default delimiter (;). 083 * <p> 084 * Following the original TGSqlParser pattern, the lexer and parser are 085 * created once in the constructor and reused for all parsing operations. 086 */ 087 public MdxSqlParser() { 088 super(EDbVendor.dbvmdx); 089 this.delimiterChar = ';'; 090 this.defaultDelimiterStr = ";"; 091 092 // Create lexer once - will be reused for all parsing operations 093 this.flexer = new TLexerMdx(); 094 this.flexer.delimiterchar = this.delimiterChar; 095 this.flexer.defaultDelimiterStr = this.defaultDelimiterStr; 096 097 // Set parent's lexer reference for shared tokenization logic 098 this.lexer = this.flexer; 099 100 // Create parser once - will be reused for all parsing operations 101 this.fparser = new TParserMdx(null); 102 this.fparser.lexer = this.flexer; 103 } 104 105 // ========== Parser Components ========== 106 107 /** The MDX lexer used for tokenization */ 108 public TLexerMdx flexer; 109 110 /** MDX parser (for MDX statements) */ 111 private TParserMdx fparser; 112 113 /** Current statement being built during extraction */ 114 private TCustomSqlStatement gcurrentsqlstatement; 115 116 // Note: Global context and frame stack fields inherited from AbstractSqlParser: 117 // - protected TContext globalContext 118 // - protected TSQLEnv sqlEnv 119 // - protected Stack<TFrame> frameStack 120 // - protected TFrame globalFrame 121 122 // ========== AbstractSqlParser Abstract Methods Implementation ========== 123 124 /** 125 * Return the MDX lexer instance. 126 */ 127 @Override 128 protected TCustomLexer getLexer(ParserContext context) { 129 return this.flexer; 130 } 131 132 /** 133 * Return the MDX parser instance with updated token list. 134 */ 135 @Override 136 protected TCustomParser getParser(ParserContext context, TSourceTokenList tokens) { 137 this.fparser.sourcetokenlist = tokens; 138 return this.fparser; 139 } 140 141 /** 142 * MDX doesn't have a secondary parser. 143 * @return null 144 */ 145 @Override 146 protected TCustomParser getSecondaryParser(ParserContext context, TSourceTokenList tokens) { 147 return null; 148 } 149 150 /** 151 * Call MDX-specific tokenization logic. 152 * <p> 153 * Delegates to domdxtexttotokenlist which handles MDX's 154 * specific keyword recognition and token generation. 155 */ 156 @Override 157 protected void tokenizeVendorSql() { 158 domdxtexttotokenlist(); 159 } 160 161 /** 162 * Setup MDX parser for raw statement extraction. 163 * <p> 164 * MDX uses a single parser, so we inject sqlcmds and update 165 * the token list for the main parser only. 166 */ 167 @Override 168 protected void setupVendorParsersForExtraction() { 169 // Inject sqlcmds into parser (required for make_stmt) 170 this.fparser.sqlcmds = this.sqlcmds; 171 172 // Update token list for parser 173 this.fparser.sourcetokenlist = this.sourcetokenlist; 174 } 175 176 /** 177 * Call MDX-specific raw statement extraction logic. 178 * <p> 179 * Delegates to domdxgetrawsqlstatements which handles MDX's 180 * statement delimiters and SCOPE/END blocks. 181 */ 182 @Override 183 protected void extractVendorRawStatements(SqlParseResult.Builder builder) { 184 domdxgetrawsqlstatements(builder); 185 186 // Set the extracted statements in the builder 187 builder.sqlStatements(this.sqlstatements); 188 } 189 190 /** 191 * Perform full parsing of statements with syntax checking. 192 * <p> 193 * This method orchestrates the parsing of all statements. 194 */ 195 @Override 196 protected TStatementList performParsing(ParserContext context, 197 TCustomParser parser, 198 TCustomParser secondaryParser, 199 TSourceTokenList tokens, 200 TStatementList rawStatements) { 201 // Store references 202 this.fparser = (TParserMdx) parser; 203 this.sourcetokenlist = tokens; 204 this.parserContext = context; 205 206 // Use the raw statements passed from AbstractSqlParser.parse() 207 this.sqlstatements = rawStatements; 208 209 // Initialize sqlcmds 210 this.sqlcmds = SqlCmdsFactory.get(this.vendor); 211 this.fparser.sqlcmds = this.sqlcmds; 212 213 // Initialize global context (from AbstractSqlParser) 214 initializeGlobalContext(); 215 216 // Parse each statement 217 for (int i = 0; i < sqlstatements.size(); i++) { 218 TCustomSqlStatement stmt = sqlstatements.getRawSql(i); 219 220 try { 221 // Set frame stack 222 stmt.setFrameStack(frameStack); 223 224 // Parse the statement 225 int parseResult = stmt.parsestatement(null, false, context.isOnlyNeedRawParseTree()); 226 227 // Error recovery for CREATE TABLE statements 228 boolean doRecover = TBaseType.ENABLE_ERROR_RECOVER_IN_CREATE_TABLE; 229 if (doRecover && ((parseResult != 0) || (stmt.getErrorCount() > 0))) { 230 handleCreateTableErrorRecovery(stmt); 231 } 232 233 // Collect errors 234 if ((parseResult != 0) || (stmt.getErrorCount() > 0)) { 235 copyErrorsFromStatement(stmt); 236 } 237 238 } catch (Exception ex) { 239 // Use inherited exception handler 240 handleStatementParsingException(stmt, i, ex); 241 continue; 242 } 243 } 244 245 // Clean up frame stack 246 if (globalFrame != null) { 247 globalFrame.popMeFromStack(frameStack); 248 } 249 250 return sqlstatements; 251 } 252 253 /** 254 * Perform MDX-specific semantic analysis using TSQLResolver. 255 */ 256 @Override 257 protected void performSemanticAnalysis(ParserContext context, TStatementList statements) { 258 if (TBaseType.isEnableResolver() && getSyntaxErrors().isEmpty()) { 259 TSQLResolver resolver = new TSQLResolver(globalContext, statements); 260 resolver.resolve(); 261 } 262 } 263 264 /** 265 * Perform interpretation/evaluation on parsed statements. 266 */ 267 @Override 268 protected void performInterpreter(ParserContext context, TStatementList statements) { 269 if (TBaseType.ENABLE_INTERPRETER && getSyntaxErrors().isEmpty()) { 270 TLog.clearLogs(); 271 TGlobalScope interpreterScope = new TGlobalScope(sqlEnv); 272 TLog.enableInterpreterLogOnly(); 273 TASTEvaluator astEvaluator = new TASTEvaluator(statements, interpreterScope); 274 astEvaluator.eval(); 275 } 276 } 277 278 // ========== MDX-Specific Tokenization ========== 279 280 /** 281 * Tokenize MDX input into source tokens. 282 * <p> 283 * Migrated from TGSqlParser.doMdxtexttotokenlist() (lines 4824-4841) 284 * <p> 285 * MDX uses simple token-by-token processing without special handling. 286 */ 287 private void domdxtexttotokenlist() { 288 TSourceToken asourcetoken; 289 int yychar; 290 291 asourcetoken = getanewsourcetoken(); 292 if (asourcetoken == null) return; 293 yychar = asourcetoken.tokencode; 294 295 while (yychar > 0) { 296 sourcetokenlist.add(asourcetoken); 297 asourcetoken = getanewsourcetoken(); 298 if (asourcetoken == null) break; 299 yychar = asourcetoken.tokencode; 300 } 301 } 302 303 // ========== MDX-Specific Raw Statement Extraction ========== 304 305 /** 306 * Extract raw MDX statements from token list. 307 * <p> 308 * Migrated from TGSqlParser.domdxgetrawsqlstatements() (lines 10861-11011) 309 * <p> 310 * Handles: 311 * <ul> 312 * <li>Semicolon statement terminators</li> 313 * <li>SCOPE...END blocks with nested IF/CASE/END support</li> 314 * <li>Continuous semicolons treated as comments</li> 315 * </ul> 316 */ 317 private void domdxgetrawsqlstatements(SqlParseResult.Builder builder) { 318 int waitingEnd = 0; 319 boolean foundEnd = false; 320 321 if (TBaseType.assigned(sqlstatements)) sqlstatements.clear(); 322 if (!TBaseType.assigned(sourcetokenlist)) return; 323 324 gcurrentsqlstatement = null; 325 EFindSqlStateType gst = EFindSqlStateType.stnormal; 326 TSourceToken lcprevsolidtoken = null, ast = null; 327 328 for (int i = 0; i < sourcetokenlist.size(); i++) { 329 330 if ((ast != null) && (ast.issolidtoken())) 331 lcprevsolidtoken = ast; 332 333 ast = sourcetokenlist.get(i); 334 sourcetokenlist.curpos = i; 335 336 switch (gst) { 337 case sterror: { 338 if (ast.tokentype == ETokenType.ttsemicolon) { 339 gcurrentsqlstatement.sourcetokenlist.add(ast); 340 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 341 gst = EFindSqlStateType.stnormal; 342 } else { 343 gcurrentsqlstatement.sourcetokenlist.add(ast); 344 } 345 break; 346 } //sterror 347 348 case stnormal: { 349 if ((ast.tokencode == TBaseType.cmtdoublehyphen) 350 || (ast.tokencode == TBaseType.cmtslashstar) 351 || (ast.tokencode == TBaseType.lexspace) 352 || (ast.tokencode == TBaseType.lexnewline) 353 || (ast.tokentype == ETokenType.ttsemicolon)) { 354 if (gcurrentsqlstatement != null) { 355 gcurrentsqlstatement.sourcetokenlist.add(ast); 356 } 357 358 if ((lcprevsolidtoken != null) && (ast.tokentype == ETokenType.ttsemicolon)) { 359 if (lcprevsolidtoken.tokentype == ETokenType.ttsemicolon) { 360 // ;;;; continuous semicolon, treat it as comment 361 ast.tokentype = ETokenType.ttsimplecomment; 362 ast.tokencode = TBaseType.cmtdoublehyphen; 363 } 364 } 365 366 continue; 367 } 368 369 // find a tokentext to start sql or plsql mode 370 gcurrentsqlstatement = sqlcmds.issql(ast, gst, gcurrentsqlstatement); 371 372 if (gcurrentsqlstatement != null) { 373 if (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstmdxscope) { 374 gst = EFindSqlStateType.ststoredprocedure; 375 gcurrentsqlstatement.sourcetokenlist.add(ast); 376 foundEnd = false; 377 waitingEnd = 1; 378 } else { 379 gst = EFindSqlStateType.stsql; 380 gcurrentsqlstatement.sourcetokenlist.add(ast); 381 } 382 } else { 383 //error tokentext found 384 385 this.syntaxErrors.add(new TSyntaxError(ast.getAstext(), ast.lineNo, (ast.columnNo < 0 ? 0 : ast.columnNo) 386 , "Error when tokenlize", EErrorType.spwarning, TBaseType.MSG_WARNING_ERROR_WHEN_TOKENIZE, null, ast.posinlist)); 387 388 ast.tokentype = ETokenType.tttokenlizererrortoken; 389 gst = EFindSqlStateType.sterror; 390 391 gcurrentsqlstatement = new TUnknownSqlStatement(this.vendor); 392 gcurrentsqlstatement.sqlstatementtype = ESqlStatementType.sstinvalid; 393 gcurrentsqlstatement.sourcetokenlist.add(ast); 394 395 } 396 397 break; 398 } // stnormal 399 400 case stsql: { 401 if (ast.tokentype == ETokenType.ttsemicolon) { 402 gst = EFindSqlStateType.stnormal; 403 gcurrentsqlstatement.sourcetokenlist.add(ast); 404 gcurrentsqlstatement.semicolonended = ast; 405 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 406 continue; 407 } 408 409 gcurrentsqlstatement.sourcetokenlist.add(ast); 410 break; 411 }//case stsql 412 413 case ststoredprocedure: { 414 if (ast.tokencode == TBaseType.rrw_if) { 415 waitingEnd++; 416 } else if (ast.tokencode == TBaseType.rrw_case) { 417 waitingEnd++; 418 } else if (ast.tokencode == TBaseType.rrw_scope) { 419 // ignore scope after end keyword like this: 420 // end scope 421 if (lcprevsolidtoken.tokencode != TBaseType.rrw_end) { 422 waitingEnd++; 423 } 424 } else if (ast.tokencode == TBaseType.rrw_end) { 425 foundEnd = true; 426 waitingEnd--; 427 if (waitingEnd < 0) { 428 waitingEnd = 0; 429 } 430 } 431 432 gcurrentsqlstatement.sourcetokenlist.add(ast); 433 if ((ast.tokentype == ETokenType.ttsemicolon) && (waitingEnd == 0) && (foundEnd)) { 434 gst = EFindSqlStateType.stnormal; 435 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 436 } 437 438 break; 439 } //ststoredprocedure 440 } //switch 441 }//for 442 443 //last statement 444 if ((gcurrentsqlstatement != null) && 445 ((gst == EFindSqlStateType.stsql) || (gst == EFindSqlStateType.ststoredprocedure) || 446 (gst == EFindSqlStateType.sterror))) { 447 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, true, builder); 448 } 449 } 450 451 /** 452 * Handle CREATE TABLE error recovery. 453 * <p> 454 * Attempts to recover from parsing errors in CREATE TABLE statements 455 * by marking unparseable tokens as ignoreable. 456 */ 457 private void handleCreateTableErrorRecovery(TCustomSqlStatement stmt) { 458 // MDX doesn't typically have CREATE TABLE statements 459 // but we keep this method for consistency with the pattern 460 // No specific recovery logic needed for MDX 461 } 462}