001package gudusoft.gsqlparser.parser; 002 003import gudusoft.gsqlparser.EDbVendor; 004import gudusoft.gsqlparser.TBaseType; 005import gudusoft.gsqlparser.TGSqlParser; 006import gudusoft.gsqlparser.TCustomLexer; 007import gudusoft.gsqlparser.TCustomParser; 008import gudusoft.gsqlparser.TCustomSqlStatement; 009import gudusoft.gsqlparser.TLexerSparksql; 010import gudusoft.gsqlparser.TParserSparksql; 011import gudusoft.gsqlparser.TSourceToken; 012import gudusoft.gsqlparser.TSourceTokenList; 013import gudusoft.gsqlparser.TStatementList; 014import gudusoft.gsqlparser.TSyntaxError; 015import gudusoft.gsqlparser.EFindSqlStateType; 016import gudusoft.gsqlparser.ETokenType; 017import gudusoft.gsqlparser.ETokenStatus; 018import gudusoft.gsqlparser.ESqlStatementType; 019import gudusoft.gsqlparser.EErrorType; 020import gudusoft.gsqlparser.stmt.TUnknownSqlStatement; 021import gudusoft.gsqlparser.stmt.mysql.TMySQLSource; 022import gudusoft.gsqlparser.sqlcmds.ISqlCmds; 023import gudusoft.gsqlparser.sqlcmds.SqlCmdsFactory; 024import gudusoft.gsqlparser.compiler.TContext; 025import gudusoft.gsqlparser.sqlenv.TSQLEnv; 026import gudusoft.gsqlparser.compiler.TGlobalScope; 027import gudusoft.gsqlparser.compiler.TFrame; 028import gudusoft.gsqlparser.resolver.TSQLResolver; 029import gudusoft.gsqlparser.resolver2.TSQLResolver2; 030import gudusoft.gsqlparser.resolver2.TSQLResolverConfig; 031import gudusoft.gsqlparser.TLog; 032import gudusoft.gsqlparser.compiler.TASTEvaluator; 033 034import java.io.BufferedReader; 035import java.util.ArrayList; 036import java.util.List; 037import java.util.Stack; 038 039import static gudusoft.gsqlparser.ESqlStatementType.*; 040 041/** 042 * Apache Spark SQL parser implementation. 043 * 044 * <p>This parser handles SparkSQL-specific SQL syntax including: 045 * <ul> 046 * <li>SparkSQL SELECT statements</li> 047 * <li>SparkSQL-specific functions (DATE, TIME, TIMESTAMP, INTERVAL)</li> 048 * <li>SparkSQL stored procedures and triggers</li> 049 * <li>SparkSQL delimiter handling</li> 050 * </ul> 051 * 052 * <p><b>Design Notes:</b> 053 * <ul> 054 * <li>Extends {@link AbstractSqlParser} using the template method pattern</li> 055 * <li>Uses {@link TLexerSparksql} for tokenization</li> 056 * <li>Uses {@link TParserSparksql} for parsing</li> 057 * <li>Tokenization is simple, similar to MySQL</li> 058 * <li>Delimiter character: ';' for SparkSQL statements</li> 059 * </ul> 060 * 061 * @see SqlParser 062 * @see AbstractSqlParser 063 * @see TLexerSparksql 064 * @see TParserSparksql 065 * @since 3.2.0.0 066 */ 067public class SparksqlSqlParser extends AbstractSqlParser { 068 069 /** 070 * Construct SparkSQL parser. 071 * <p> 072 * Configures the parser for Apache Spark SQL with default delimiter (;). 073 */ 074 public SparksqlSqlParser() { 075 super(EDbVendor.dbvsparksql); 076 this.delimiterChar = ';'; 077 this.defaultDelimiterStr = ";"; 078 079 // Create lexer once - will be reused for all parsing operations 080 this.flexer = new TLexerSparksql(); 081 this.flexer.delimiterchar = this.delimiterChar; 082 this.flexer.defaultDelimiterStr = this.defaultDelimiterStr; 083 084 // Set parent's lexer reference for shared tokenization logic 085 this.lexer = this.flexer; 086 087 // Create parser once - will be reused for all parsing operations 088 this.fparser = new TParserSparksql(null); 089 this.fparser.lexer = this.flexer; 090 } 091 092 // ========== Parser Components ========== 093 094 /** The SparkSQL lexer used for tokenization */ 095 public TLexerSparksql flexer; 096 097 /** SparkSQL parser (for SparkSQL statements) */ 098 private TParserSparksql fparser; 099 100 /** Current statement being built during extraction */ 101 private TCustomSqlStatement gcurrentsqlstatement; 102 103 /** User-defined delimiter string */ 104 private String userDelimiterStr = ";"; 105 106 /** Current delimiter character */ 107 private char curdelimiterchar = ';'; 108 109 // ========== AbstractSqlParser Abstract Methods Implementation ========== 110 111 /** 112 * Return the SparkSQL lexer instance. 113 */ 114 @Override 115 protected TCustomLexer getLexer(ParserContext context) { 116 return this.flexer; 117 } 118 119 /** 120 * Return the SparkSQL parser instance with updated token list. 121 */ 122 @Override 123 protected TCustomParser getParser(ParserContext context, TSourceTokenList tokens) { 124 this.fparser.sourcetokenlist = tokens; 125 return this.fparser; 126 } 127 128 /** 129 * Call SparkSQL-specific tokenization logic. 130 */ 131 @Override 132 protected void tokenizeVendorSql() { 133 dosparksqltexttotokenlist(); 134 } 135 136 /** 137 * Setup SparkSQL parser for raw statement extraction. 138 */ 139 @Override 140 protected void setupVendorParsersForExtraction() { 141 this.fparser.sqlcmds = this.sqlcmds; 142 this.fparser.sourcetokenlist = this.sourcetokenlist; 143 } 144 145 /** 146 * Call SparkSQL-specific raw statement extraction logic. 147 */ 148 @Override 149 protected void extractVendorRawStatements(SqlParseResult.Builder builder) { 150 dosparksqlgetrawsqlstatements(builder); 151 } 152 153 /** 154 * Perform full parsing of statements with syntax checking. 155 */ 156 @Override 157 protected TStatementList performParsing(ParserContext context, 158 TCustomParser parser, 159 TCustomParser secondaryParser, 160 TSourceTokenList tokens, 161 TStatementList rawStatements) { 162 this.fparser = (TParserSparksql) parser; 163 this.sourcetokenlist = tokens; 164 this.parserContext = context; 165 this.sqlstatements = rawStatements; 166 167 this.sqlcmds = SqlCmdsFactory.get(vendor); 168 this.fparser.sqlcmds = this.sqlcmds; 169 170 if (context != null && context.getGsqlparser() != null) { 171 TGSqlParser gsqlparser = (TGSqlParser) context.getGsqlparser(); 172 this.frameStack = gsqlparser.getFrameStack(); 173 this.fparser.getNf().setGsqlParser(gsqlparser); 174 this.globalContext = new TContext(); 175 this.sqlEnv = new TSQLEnv(this.vendor) { 176 @Override 177 public void initSQLEnv() { 178 } 179 }; 180 this.globalContext.setSqlEnv(this.sqlEnv, this.sqlstatements); 181 } else { 182 initializeGlobalContext(); 183 } 184 185 for (int i = 0; i < sqlstatements.size(); i++) { 186 TCustomSqlStatement stmt = sqlstatements.getRawSql(i); 187 188 try { 189 stmt.setFrameStack(frameStack); 190 int parseResult = stmt.parsestatement(null, false, context.isOnlyNeedRawParseTree()); 191 192 boolean doRecover = TBaseType.ENABLE_ERROR_RECOVER_IN_CREATE_TABLE; 193 if (doRecover && ((parseResult != 0) || (stmt.getErrorCount() > 0))) { 194 handleCreateTableErrorRecovery(stmt); 195 } 196 197 if ((parseResult != 0) || (stmt.getErrorCount() > 0)) { 198 copyErrorsFromStatement(stmt); 199 } 200 201 } catch (Exception ex) { 202 handleStatementParsingException(stmt, i, ex); 203 continue; 204 } 205 } 206 207 if (globalFrame != null) { 208 globalFrame.popMeFromStack(frameStack); 209 } 210 211 return this.sqlstatements; 212 } 213 214 private void handleCreateTableErrorRecovery(TCustomSqlStatement stmt) { 215 if (((stmt.sqlstatementtype == ESqlStatementType.sstcreatetable) 216 || (stmt.sqlstatementtype == ESqlStatementType.sstcreateindex)) 217 && (!TBaseType.c_createTableStrictParsing)) { 218 219 int nested = 0; 220 boolean isIgnore = false, isFoundIgnoreToken = false; 221 TSourceToken firstIgnoreToken = null; 222 223 for (int k = 0; k < stmt.sourcetokenlist.size(); k++) { 224 TSourceToken st = stmt.sourcetokenlist.get(k); 225 if (isIgnore) { 226 if (st.issolidtoken() && (st.tokencode != ';')) { 227 isFoundIgnoreToken = true; 228 if (firstIgnoreToken == null) { 229 firstIgnoreToken = st; 230 } 231 } 232 if (st.tokencode != ';') { 233 st.tokencode = TBaseType.sqlpluscmd; 234 } 235 continue; 236 } 237 if (st.tokencode == (int) ')') { 238 nested--; 239 if (nested == 0) { 240 boolean isSelect = false; 241 TSourceToken st1 = st.searchToken(TBaseType.rrw_as, 1); 242 if (st1 != null) { 243 TSourceToken st2 = st.searchToken((int) '(', 2); 244 if (st2 != null) { 245 TSourceToken st3 = st.searchToken(TBaseType.rrw_select, 3); 246 isSelect = (st3 != null); 247 } 248 } 249 if (!isSelect) isIgnore = true; 250 } 251 } else if (st.tokencode == (int) '(') { 252 nested++; 253 } 254 } 255 256 if (isFoundIgnoreToken) { 257 stmt.clearError(); 258 stmt.parsestatement(null, false); 259 } 260 } 261 } 262 263 @Override 264 protected void performSemanticAnalysis(ParserContext context, TStatementList statements) { 265 if (context != null && context.getGsqlparser() != null) { 266 return; 267 } 268 269 if (getSyntaxErrors().isEmpty()) { 270 if (TBaseType.isEnableResolver2()) { 271 TSQLResolverConfig config = new TSQLResolverConfig(); 272 config.setVendor(vendor); 273 TSQLResolver2 resolver2 = new TSQLResolver2(null, statements, config); 274 if (this.sqlEnv != null) { 275 resolver2.setSqlEnv(this.sqlEnv); 276 } 277 resolver2.resolve(); 278 } else if (TBaseType.isEnableResolver()) { 279 TSQLResolver resolver = new TSQLResolver(globalContext, statements); 280 resolver.resolve(); 281 } 282 } 283 } 284 285 @Override 286 protected void performInterpreter(ParserContext context, TStatementList statements) { 287 if (TBaseType.ENABLE_INTERPRETER && getSyntaxErrors().isEmpty()) { 288 TLog.clearLogs(); 289 TGlobalScope interpreterScope = new TGlobalScope(sqlEnv); 290 TLog.enableInterpreterLogOnly(); 291 TASTEvaluator astEvaluator = new TASTEvaluator(statements, interpreterScope); 292 astEvaluator.eval(); 293 } 294 } 295 296 // ========== Helper Methods ========== 297 298 /** 299 * Add token to current statement with proper statement linkage. 300 * This replicates TCustomSqlStatement.addtokentolist() behavior 301 * which sets st.stmt = this before adding. 302 */ 303 private void addTokenToStatement(TSourceToken st) { 304 st.stmt = gcurrentsqlstatement; 305 gcurrentsqlstatement.sourcetokenlist.add(st); 306 } 307 308 // ========== SparkSQL-Specific Tokenization ========== 309 310 /** 311 * SparkSQL-specific tokenization logic. 312 * <p> 313 * SparkSQL uses simple tokenization, similar to MySQL. 314 * Handles ROLLUP keyword specially. 315 * Migrated from TGSqlParser.dosparksqltexttotokenlist() 316 */ 317 private void dosparksqltexttotokenlist() { 318 TSourceToken asourcetoken, lcprevst; 319 int yychar; 320 boolean startDelimiter = false; 321 322 flexer.tmpDelimiter = ""; 323 324 asourcetoken = getanewsourcetoken(); 325 if (asourcetoken == null) return; 326 yychar = asourcetoken.tokencode; 327 328 while (yychar > 0) { 329 // Always split mysqllabel ("ident:") into IDENT + ':' tokens. 330 // The grammar handles labeled blocks via IDENT ':' BEGIN. 331 if (yychar == TBaseType.mysqllabel) { 332 String labelText = asourcetoken.toString(); 333 String identText = labelText.substring(0, labelText.length() - 1); 334 int line = (int) asourcetoken.lineNo; 335 int col = (int) asourcetoken.columnNo; 336 337 asourcetoken.setAstext(identText); 338 asourcetoken.tokencode = TBaseType.ident; 339 asourcetoken.tokentype = ETokenType.ttidentifier; 340 sourcetokenlist.add(asourcetoken); 341 342 TSourceToken colonToken = new TSourceToken(":"); 343 colonToken.tokencode = ':'; 344 colonToken.tokentype = ETokenType.ttcolon; 345 colonToken.tokenstatus = ETokenStatus.tsoriginal; 346 colonToken.lineNo = line; 347 colonToken.columnNo = col + identText.length(); 348 colonToken.container = sourcetokenlist; 349 sourcetokenlist.curpos = sourcetokenlist.curpos + 1; 350 colonToken.posinlist = sourcetokenlist.curpos; 351 sourcetokenlist.add(colonToken); 352 353 asourcetoken = getanewsourcetoken(); 354 if (asourcetoken == null) break; 355 yychar = asourcetoken.tokencode; 356 continue; 357 } 358 359 sourcetokenlist.add(asourcetoken); 360 asourcetoken = getanewsourcetoken(); 361 if (asourcetoken == null) break; 362 checkMySQLCommentToken(asourcetoken); 363 364 if ((asourcetoken.tokencode == TBaseType.lexnewline) && (startDelimiter)) { 365 startDelimiter = false; 366 flexer.tmpDelimiter = sourcetokenlist.get(sourcetokenlist.size() - 1).getAstext(); 367 } 368 369 if (asourcetoken.tokencode == TBaseType.rrw_rollup) { 370 // with rollup 371 lcprevst = getprevsolidtoken(asourcetoken); 372 if (lcprevst != null) { 373 if (lcprevst.tokencode == TBaseType.rrw_with) 374 lcprevst.tokencode = TBaseType.with_rollup; 375 } 376 } 377 378 yychar = asourcetoken.tokencode; 379 } 380 } 381 382 private TSourceToken getprevsolidtoken(TSourceToken ptoken) { 383 if (ptoken == null) return null; 384 TSourceTokenList lcstlist = ptoken.container; 385 if (TBaseType.assigned(lcstlist)) { 386 return lcstlist.nextsolidtoken(ptoken.posinlist - 1, -1, false); 387 } 388 return null; 389 } 390 391 /** 392 * Check and process MySQL-style comment tokens. 393 * <p> 394 * Migrated from TGSqlParser.checkMySQLCommentToken() 395 */ 396 private void checkMySQLCommentToken(TSourceToken asourcetoken) { 397 if (asourcetoken.tokencode == TBaseType.cmtslashstar) { 398 String cmtText = asourcetoken.toString(); 399 if (cmtText.startsWith("/*+") || cmtText.startsWith("/*-") || cmtText.startsWith("/*!") 400 || cmtText.startsWith("/*%")) { 401 // Optimizer hint comment, leave as is 402 } 403 } 404 } 405 406 // ========== SparkSQL-Specific Raw Statement Extraction ========== 407 408 /** 409 * SparkSQL-specific raw statement extraction logic. 410 * <p> 411 * SparkSQL uses MySQL-like raw statement extraction with special handling 412 * for DATE, TIME, TIMESTAMP, and INTERVAL keywords. 413 * Migrated from TGSqlParser.dosparksqlgetrawsqlstatements() 414 */ 415 private void dosparksqlgetrawsqlstatements(SqlParseResult.Builder builder) { 416 int errorcount = 0; 417 gcurrentsqlstatement = null; 418 EFindSqlStateType gst = EFindSqlStateType.stnormal; 419 int i; 420 TSourceToken ast; 421 boolean waitingDelimiter = false; 422 int compoundBlockNesting = 0; 423 424 // reset delimiter 425 userDelimiterStr = defaultDelimiterStr; 426 427 for (i = 0; i < sourcetokenlist.size(); i++) { 428 ast = sourcetokenlist.get(i); 429 sourcetokenlist.curpos = i; 430 431 // Handle SparkSQL-specific keywords 432 if (ast.tokencode == TBaseType.rrw_date) { 433 TSourceToken st1 = ast.nextSolidToken(); 434 if (st1 != null) { 435 if (st1.tokencode == '(') { 436 ast.tokencode = TBaseType.rrw_spark_date_function; 437 } else if (st1.tokencode == TBaseType.sconst) { 438 ast.tokencode = TBaseType.rrw_spark_date_const; 439 } 440 } 441 } else if (ast.tokencode == TBaseType.rrw_time) { 442 TSourceToken st1 = ast.nextSolidToken(); 443 if (st1 != null) { 444 if (st1.tokencode == TBaseType.sconst) { 445 ast.tokencode = TBaseType.rrw_spark_time_const; 446 } 447 } 448 } else if (ast.tokencode == TBaseType.rrw_timestamp) { 449 TSourceToken st1 = ast.nextSolidToken(); 450 if (st1 != null) { 451 if (st1.tokencode == TBaseType.sconst) { 452 ast.tokencode = TBaseType.rrw_spark_timestamp_constant; 453 } else if (st1.tokencode == TBaseType.ident) { 454 if (st1.toString().startsWith("\"")) { 455 ast.tokencode = TBaseType.rrw_spark_timestamp_constant; 456 st1.tokencode = TBaseType.sconst; 457 } 458 } 459 } 460 } else if (ast.tokencode == TBaseType.rrw_interval) { 461 TSourceToken leftParen = ast.searchToken('(', 1); 462 if (leftParen != null) { 463 int k = leftParen.posinlist + 1; 464 boolean commaToken = false; 465 while (k < ast.container.size()) { 466 if (ast.container.get(k).tokencode == ')') break; 467 if (ast.container.get(k).tokencode == ',') { 468 commaToken = true; 469 break; 470 } 471 k++; 472 } 473 if (commaToken) { 474 ast.tokencode = TBaseType.rrw_mysql_interval_func; 475 } 476 } 477 } else if (ast.tokencode == TBaseType.rrw_spark_position) { 478 TSourceToken leftParen = ast.searchToken('(', 1); 479 if (leftParen == null) { 480 ast.tokencode = TBaseType.ident; // treat it as identifier 481 } 482 } 483 484 switch (gst) { 485 case sterror: { 486 if (ast.tokentype == ETokenType.ttsemicolon) { 487 addTokenToStatement(ast); 488 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 489 gst = EFindSqlStateType.stnormal; 490 } else { 491 addTokenToStatement(ast); 492 } 493 break; 494 } 495 case stnormal: { 496 if ((ast.tokencode == TBaseType.cmtdoublehyphen) 497 || (ast.tokencode == TBaseType.cmtslashstar) 498 || (ast.tokencode == TBaseType.lexspace) 499 || (ast.tokencode == TBaseType.lexnewline) 500 || (ast.tokentype == ETokenType.ttsemicolon)) { 501 if (TBaseType.assigned(gcurrentsqlstatement)) { 502 addTokenToStatement(ast); 503 } 504 505 continue; 506 } 507 508 if ((ast.isFirstTokenOfLine()) && ((ast.tokencode == TBaseType.rrw_mysql_source) || (ast.tokencode == TBaseType.slash_dot))) { 509 gst = EFindSqlStateType.stsqlplus; 510 gcurrentsqlstatement = new TMySQLSource(vendor); 511 addTokenToStatement(ast); 512 continue; 513 } 514 515 // find a tokentext to start sql or plsql mode 516 gcurrentsqlstatement = sqlcmds.issql(ast, gst, gcurrentsqlstatement); 517 518 if (TBaseType.assigned(gcurrentsqlstatement)) { 519 ESqlStatementType[] ses = {ESqlStatementType.sstmysqlcreateprocedure, ESqlStatementType.sstmysqlcreatefunction 520 , sstcreateprocedure, ESqlStatementType.sstcreatefunction 521 , ESqlStatementType.sstcreatetrigger}; 522 if (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sst_plsql_block) { 523 // Compound BEGIN...END block 524 gst = EFindSqlStateType.stsql; 525 compoundBlockNesting = (ast.tokencode == TBaseType.rrw_begin) ? 1 : 0; 526 addTokenToStatement(ast); 527 } else if (includesqlstatementtype(gcurrentsqlstatement.sqlstatementtype, ses)) { 528 gst = EFindSqlStateType.ststoredprocedure; 529 waitingDelimiter = false; 530 addTokenToStatement(ast); 531 curdelimiterchar = ';'; 532 } else { 533 gst = EFindSqlStateType.stsql; 534 addTokenToStatement(ast); 535 } 536 537 } 538 539 if (!TBaseType.assigned(gcurrentsqlstatement)) { // error tokentext found 540 541 this.syntaxErrors.add(new TSyntaxError(ast.getAstext(), ast.lineNo, (ast.columnNo < 0 ? 0 : ast.columnNo) 542 , "Error when tokenlize", EErrorType.spwarning, TBaseType.MSG_WARNING_ERROR_WHEN_TOKENIZE, null, ast.posinlist)); 543 544 ast.tokentype = ETokenType.tttokenlizererrortoken; 545 gst = EFindSqlStateType.sterror; 546 547 gcurrentsqlstatement = new TUnknownSqlStatement(vendor); 548 gcurrentsqlstatement.sqlstatementtype = ESqlStatementType.sstinvalid; 549 addTokenToStatement(ast); 550 551 } 552 break; 553 } 554 case stsqlplus: { 555 if (ast.tokencode == TBaseType.lexnewline) { 556 gst = EFindSqlStateType.stnormal; 557 addTokenToStatement(ast); // so add it here 558 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 559 } else { 560 addTokenToStatement(ast); 561 } 562 563 break; 564 } 565 case stsql: { 566 // Track BEGIN/END nesting for compound blocks 567 if (gcurrentsqlstatement != null 568 && gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sst_plsql_block) { 569 if (ast.tokencode == TBaseType.rrw_begin) { 570 compoundBlockNesting++; 571 } else if (ast.tokencode == TBaseType.rrw_end && compoundBlockNesting > 0) { 572 // Only decrement for END that closes a BEGIN block. 573 // END IF, END WHILE, END LOOP, END REPEAT, END CASE don't close BEGIN. 574 TSourceToken nextSolid = sourcetokenlist.nextsolidtoken(i, 1, false); 575 boolean isStructEnd = nextSolid != null && ( 576 nextSolid.tokencode == TBaseType.rrw_if || 577 nextSolid.tokencode == TBaseType.rrw_while || 578 nextSolid.tokencode == TBaseType.rrw_loop || 579 nextSolid.tokencode == TBaseType.rrw_repeat || 580 nextSolid.tokencode == TBaseType.rrw_case || 581 nextSolid.tokencode == TBaseType.rrw_for); 582 if (!isStructEnd) { 583 compoundBlockNesting--; 584 } 585 } 586 } 587 588 if ((ast.tokentype == ETokenType.ttsemicolon) && (gcurrentsqlstatement.sqlstatementtype != ESqlStatementType.sstmysqldelimiter)) { 589 if (compoundBlockNesting > 0) { 590 // Inside compound block - don't complete on semicolon 591 addTokenToStatement(ast); 592 continue; 593 } 594 gst = EFindSqlStateType.stnormal; 595 addTokenToStatement(ast); 596 gcurrentsqlstatement.semicolonended = ast; 597 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 598 compoundBlockNesting = 0; 599 continue; 600 } 601 if (ast.toString().equalsIgnoreCase(userDelimiterStr)) { 602 gst = EFindSqlStateType.stnormal; 603 ast.tokencode = ';'; // treat it as semicolon 604 addTokenToStatement(ast); 605 gcurrentsqlstatement.semicolonended = ast; 606 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 607 continue; 608 } 609 addTokenToStatement(ast); 610 611 if ((ast.tokencode == TBaseType.lexnewline) 612 && (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstmysqldelimiter)) { 613 gst = EFindSqlStateType.stnormal; 614 userDelimiterStr = ""; 615 for (int k = 0; k < gcurrentsqlstatement.sourcetokenlist.size(); k++) { 616 TSourceToken st = gcurrentsqlstatement.sourcetokenlist.get(k); 617 if ((st.tokencode == TBaseType.rrw_mysql_delimiter) 618 || (st.tokencode == TBaseType.lexnewline) 619 || (st.tokencode == TBaseType.lexspace) 620 || (st.tokencode == TBaseType.rrw_set)) { 621 continue; 622 } 623 624 userDelimiterStr += st.toString(); 625 } 626 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 627 628 continue; 629 } 630 631 break; 632 } 633 case ststoredprocedure: { 634 if (waitingDelimiter) { 635 if (userDelimiterStr.equalsIgnoreCase(ast.toString())) { 636 gst = EFindSqlStateType.stnormal; 637 gcurrentsqlstatement.semicolonended = ast; 638 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 639 continue; 640 } else if (userDelimiterStr.startsWith(ast.toString())) { 641 String lcstr = ast.toString(); 642 for (int k = ast.posinlist + 1; k < ast.container.size(); k++) { 643 TSourceToken st = ast.container.get(k); 644 if ((st.tokencode == TBaseType.rrw_mysql_delimiter) || (st.tokencode == TBaseType.lexnewline) || (st.tokencode == TBaseType.lexspace)) { 645 break; 646 } 647 lcstr = lcstr + st.toString(); 648 } 649 650 if (userDelimiterStr.equalsIgnoreCase(lcstr)) { 651 for (int k = ast.posinlist; k < ast.container.size(); k++) { 652 TSourceToken st = ast.container.get(k); 653 if ((st.tokencode == TBaseType.rrw_mysql_delimiter) || (st.tokencode == TBaseType.lexnewline) || (st.tokencode == TBaseType.lexspace)) { 654 break; 655 } 656 ast.tokenstatus = ETokenStatus.tsignorebyyacc; 657 } 658 gst = EFindSqlStateType.stnormal; 659 gcurrentsqlstatement.semicolonended = ast; 660 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 661 continue; 662 } 663 664 } 665 } 666 if (ast.tokencode == TBaseType.rrw_begin) 667 waitingDelimiter = true; 668 669 if (userDelimiterStr.equals(";") || (waitingDelimiter == false)) { 670 addTokenToStatement(ast); 671 if (ast.tokentype == ETokenType.ttsemicolon) { 672 gst = EFindSqlStateType.stnormal; 673 gcurrentsqlstatement.semicolonended = ast; 674 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 675 continue; 676 } 677 } else { 678 if (ast.toString().equals(userDelimiterStr)) { 679 ast.tokenstatus = ETokenStatus.tsignorebyyacc; 680 addTokenToStatement(ast); 681 gst = EFindSqlStateType.stnormal; 682 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 683 } else { 684 if ((ast.tokentype == ETokenType.ttsemicolon) && (userDelimiterStr.equals(";"))) { 685 TSourceToken lcprevtoken = ast.container.nextsolidtoken(ast, -1, false); 686 if (lcprevtoken != null) { 687 if (lcprevtoken.tokencode == TBaseType.rrw_end) { 688 gst = EFindSqlStateType.stnormal; 689 gcurrentsqlstatement.semicolonended = ast; 690 addTokenToStatement(ast); 691 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 692 continue; 693 } 694 } 695 } 696 697 addTokenToStatement(ast); 698 } 699 } 700 break; 701 } 702 } 703 } 704 705 // last statement 706 if (TBaseType.assigned(gcurrentsqlstatement) && ((gst == EFindSqlStateType.stsql) || (gst == EFindSqlStateType.ststoredprocedure) || (gst == EFindSqlStateType.sterror))) { 707 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, true, builder); 708 } 709 710 builder.sqlStatements(this.sqlstatements); 711 builder.errorCode(errorcount); 712 builder.errorMessage(errorcount == 0 ? "" : String.format("Extraction completed with %d error(s)", errorcount)); 713 } 714 715 /** 716 * Check if a SQL statement type is included in an array. 717 */ 718 private boolean includesqlstatementtype(ESqlStatementType type, ESqlStatementType[] types) { 719 for (ESqlStatementType t : types) { 720 if (t == type) return true; 721 } 722 return false; 723 } 724}