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.TLexerDb2; 009import gudusoft.gsqlparser.TParserDb2Sql; 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.ESqlStatementType; 017import gudusoft.gsqlparser.EErrorType; 018import gudusoft.gsqlparser.stmt.oracle.TSqlplusCmdStatement; 019import gudusoft.gsqlparser.stmt.TUnknownSqlStatement; 020import gudusoft.gsqlparser.sqlcmds.ISqlCmds; 021import gudusoft.gsqlparser.sqlcmds.SqlCmdsFactory; 022 023import java.util.ArrayList; 024 025/** 026 * IBM DB2 SQL parser implementation. 027 * 028 * <p>This parser handles DB2-specific SQL syntax including: 029 * <ul> 030 * <li>DB2 SQL procedures and functions</li> 031 * <li>DB2-specific triggers</li> 032 * <li>DECLARE CURSOR and compound statements</li> 033 * <li>DB2 echo commands treated as SQL*Plus commands</li> 034 * <li>JDBC escape sequences</li> 035 * <li>WITH isolation clauses</li> 036 * </ul> 037 * 038 * <p><b>Design Notes:</b> 039 * <ul> 040 * <li>Extends {@link AbstractSqlParser}</li> 041 * <li>Can directly instantiate: {@link TLexerDb2}, {@link TParserDb2Sql}</li> 042 * <li>Uses single parser (no secondary parser)</li> 043 * <li>Delimiter character: '@' for stored procedures (not ';')</li> 044 * </ul> 045 * 046 * <p><b>Migrated from TGSqlParser:</b> 047 * <ul> 048 * <li>dodb2sqltexttotokenlist() → tokenizeVendorSql()</li> 049 * <li>dodb2getrawsqlstatements() → extractVendorRawStatements()</li> 050 * </ul> 051 * 052 * @see SqlParser 053 * @see AbstractSqlParser 054 * @see TLexerDb2 055 * @see TParserDb2Sql 056 * @since 3.2.0.0 057 */ 058public class Db2SqlParser extends AbstractSqlParser { 059 060 // ========== Lexer and Parser Instances ========== 061 // Created once in constructor, reused for all parsing operations 062 063 public TLexerDb2 flexer; 064 private TParserDb2Sql fparser; 065 066 // ========== State Variables ========== 067 // NOTE: The following fields are inherited from AbstractSqlParser: 068 // - sourcetokenlist (TSourceTokenList) 069 // - sqlstatements (TStatementList) 070 // - parserContext (ParserContext) 071 // - sqlcmds (ISqlCmds) 072 // - globalContext (TContext) 073 // - sqlEnv (TSQLEnv) 074 // - frameStack (Stack<TFrame>) 075 // - globalFrame (TFrame) 076 // - lexer (TCustomLexer) 077 078 // ========== Constructor ========== 079 080 /** 081 * Construct DB2 SQL parser. 082 * <p> 083 * Configures the parser for IBM DB2 database with default delimiter: at-sign (@) 084 * <p> 085 * Following the original TGSqlParser pattern, the lexer and parser are 086 * created once in the constructor and reused for all parsing operations. 087 * <p> 088 * <b>Note:</b> DB2 uses '@' as the statement delimiter (not ';' like most databases). 089 * This is consistent with DB2 stored procedure conventions where @ marks the end of a procedure. 090 */ 091 public Db2SqlParser() { 092 super(EDbVendor.dbvdb2); 093 094 // Set delimiter character - DB2 uses '@' not ';' 095 this.delimiterChar = '@'; 096 this.defaultDelimiterStr = "@"; 097 098 // Create lexer once - will be reused for all parsing operations 099 this.flexer = new TLexerDb2(); 100 this.flexer.delimiterchar = this.delimiterChar; 101 this.flexer.defaultDelimiterStr = this.defaultDelimiterStr; 102 103 // CRITICAL: Set lexer for inherited getanewsourcetoken() method 104 this.lexer = this.flexer; 105 106 // Create parser once - will be reused for all parsing operations 107 this.fparser = new TParserDb2Sql(null); 108 this.fparser.lexer = this.flexer; 109 110 // NOTE: sourcetokenlist and sqlstatements are initialized in AbstractSqlParser constructor 111 } 112 113 // ========== AbstractSqlParser Abstract Methods Implementation ========== 114 115 /** 116 * Return the DB2 lexer instance. 117 * <p> 118 * The lexer is created once in the constructor and reused for all 119 * parsing operations. This method simply returns the existing instance, 120 * matching the original TGSqlParser pattern where the lexer is created 121 * once and reset before each use. 122 * 123 * @param context parser context (not used, lexer already created) 124 * @return the DB2 lexer instance created in constructor 125 */ 126 @Override 127 protected TCustomLexer getLexer(ParserContext context) { 128 // Return existing lexer instance (created in constructor) 129 return this.flexer; 130 } 131 132 /** 133 * Return the DB2 SQL parser instance with updated token list. 134 * <p> 135 * The parser is created once in the constructor and reused for all 136 * parsing operations. This method updates the token list and returns 137 * the existing instance, matching the original TGSqlParser pattern. 138 * 139 * @param context parser context (not used, parser already created) 140 * @param tokens source token list to parse 141 * @return the DB2 SQL parser instance created in constructor 142 */ 143 @Override 144 protected TCustomParser getParser(ParserContext context, TSourceTokenList tokens) { 145 // Update token list for reused parser instance 146 this.fparser.sourcetokenlist = tokens; 147 return this.fparser; 148 } 149 150 /** 151 * Call DB2-specific tokenization logic. 152 * <p> 153 * Delegates to dodb2sqltexttotokenlist which handles DB2's 154 * specific keyword recognition, JDBC escapes, and token generation. 155 */ 156 @Override 157 protected void tokenizeVendorSql() { 158 dodb2sqltexttotokenlist(); 159 } 160 161 /** 162 * Setup DB2 parser for raw statement extraction. 163 * <p> 164 * DB2 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 this.fparser.sqlcmds = this.sqlcmds; 170 this.fparser.sourcetokenlist = this.sourcetokenlist; 171 } 172 173 /** 174 * Call DB2-specific raw statement extraction logic. 175 * <p> 176 * Delegates to dodb2getrawsqlstatements which handles DB2's 177 * statement boundaries, stored procedures, and compound blocks. 178 * 179 * @param builder result builder to populate 180 */ 181 @Override 182 protected void extractVendorRawStatements(SqlParseResult.Builder builder) { 183 dodb2getrawsqlstatements(builder); 184 } 185 186 /** 187 * Perform full parsing on raw DB2 SQL statements. 188 * <p> 189 * This method parses each raw statement into a complete parse tree, 190 * handling stored procedures, functions, triggers, and nested blocks. 191 * 192 * @param context parser context 193 * @param parser main parser instance 194 * @param secondaryParser secondary parser (null for DB2) 195 * @param tokens source token list 196 * @param rawStatements raw statements to parse 197 * @return list of parsed statements 198 */ 199 @Override 200 protected TStatementList performParsing(ParserContext context, 201 TCustomParser parser, 202 TCustomParser secondaryParser, 203 TSourceTokenList tokens, 204 TStatementList rawStatements) { 205 // Store references 206 this.fparser = (TParserDb2Sql) parser; 207 this.sourcetokenlist = tokens; 208 this.parserContext = context; 209 210 // Use rawStatements if provided, otherwise use our own sqlstatements field 211 if (rawStatements != null) { 212 this.sqlstatements = rawStatements; 213 } 214 215 // Safety check - if sqlstatements is still null, return empty list 216 if (this.sqlstatements == null) { 217 return new TStatementList(); 218 } 219 220 // Initialize sqlcmds if not already done 221 if (this.sqlcmds == null) { 222 this.sqlcmds = SqlCmdsFactory.get(vendor); 223 } 224 this.fparser.sqlcmds = this.sqlcmds; 225 226 // Use inherited method to initialize global context 227 initializeGlobalContext(); 228 229 // Parse each statement with exception handling 230 for (int i = 0; i < sqlstatements.size(); i++) { 231 TCustomSqlStatement stmt = sqlstatements.getRawSql(i); 232 try { 233 stmt.setFrameStack(frameStack); 234 int parseResult = stmt.parsestatement(null, false, context.isOnlyNeedRawParseTree()); 235 236 // Perform any DB2-specific post-processing if needed 237 afterStatementParsed(stmt); 238 239 // Attempt error recovery for CREATE TABLE/INDEX with unsupported options 240 // This matches legacy TGSqlParser behavior that ignores vendor-specific 241 // table/index options not in the grammar 242 if ((parseResult != 0) || (stmt.getErrorCount() > 0)) { 243 parseResult = attemptErrorRecovery(stmt, parseResult, context.isOnlyNeedRawParseTree()); 244 } 245 246 // Collect errors from statement (after recovery attempt) 247 if ((parseResult != 0) || (stmt.getErrorCount() > 0)) { 248 copyErrorsFromStatement(stmt); 249 } 250 } catch (Exception ex) { 251 // Use inherited exception handler 252 handleStatementParsingException(stmt, i, ex); 253 continue; 254 } 255 } 256 257 // Cleanup 258 if (globalFrame != null) { 259 globalFrame.popMeFromStack(frameStack); 260 } 261 262 return sqlstatements; 263 } 264 265 // ========== DB2-Specific Tokenization ========== 266 // Migrated from TGSqlParser.dodb2sqltexttotokenlist() 267 268 /** 269 * Tokenize DB2 SQL text into source tokens. 270 * <p> 271 * Handles DB2-specific features: 272 * <ul> 273 * <li>Echo commands at line start treated as SQL*Plus commands</li> 274 * <li>JDBC escape sequences {fn} and }</li> 275 * <li>WITH isolation clauses (RR, RS, CS, UR)</li> 276 * <li>ScriptOptions comment directive</li> 277 * </ul> 278 * <p> 279 * Migrated from TGSqlParser.dodb2sqltexttotokenlist (lines 4300-4408) 280 */ 281 private void dodb2sqltexttotokenlist() { 282 // treat echo, ! of db2 at the begin of each line as oracle sqlplus command 283 284 boolean insqlpluscmd = false; 285 boolean isvalidplace = true; 286 int jdbc_escape_nest = 0; 287 288 TSourceToken asourcetoken, lcprevst; 289 int yychar; 290 291 asourcetoken = getanewsourcetoken(); 292 293 if (asourcetoken == null) return; 294 yychar = asourcetoken.tokencode; 295 296 while (yychar > 0) { 297 298 switch (yychar) { 299 case TBaseType.lexnewline: { 300 insqlpluscmd = false; 301 isvalidplace = true; 302 break; 303 } 304 case TBaseType.cmtdoublehyphen: { 305 if (asourcetoken.toString().toLowerCase().indexOf("scriptoptions") >= 0) { 306 asourcetoken.tokencode = TBaseType.scriptoptions; 307 asourcetoken.tokentype = ETokenType.ttidentifier; 308 } 309 310 if (insqlpluscmd) { 311 asourcetoken.insqlpluscmd = true; 312 } 313 break; 314 } 315 case TBaseType.rrw_jdbc_escape_fn: { 316 jdbc_escape_nest++; 317 asourcetoken.tokencode = TBaseType.lexspace; 318 break; 319 } 320 case TBaseType.rrw_jdbc_escape_end: { 321 jdbc_escape_nest--; 322 asourcetoken.tokencode = TBaseType.lexspace; 323 break; 324 } 325 default: // solid tokentext 326 { 327 if ((asourcetoken.tokencode == TBaseType.rrw_rr) 328 || (asourcetoken.tokencode == TBaseType.rrw_rs) 329 || (asourcetoken.tokencode == TBaseType.rrw_cs) 330 || (asourcetoken.tokencode == TBaseType.rrw_ur)) { 331 // change with keyword in isolation clause to rrw_with_isolation 332 // so opt_restriction_clause in create view worked correctly 333 lcprevst = getprevsolidtoken(asourcetoken); 334 if (lcprevst != null) { 335 if (lcprevst.tokencode == TBaseType.rrw_with) 336 lcprevst.tokencode = TBaseType.rrw_with_isolation; 337 } 338 } 339 340 if (insqlpluscmd) { 341 asourcetoken.insqlpluscmd = true; 342 } else { 343 if (isvalidplace) { 344 if (TBaseType.mycomparetext(asourcetoken.toString().toLowerCase(), "echo") == 0) { 345 asourcetoken.tokencode = TBaseType.sqlpluscmd; 346 if (asourcetoken.tokentype != ETokenType.ttslash) { 347 asourcetoken.tokentype = ETokenType.ttsqlpluscmd; 348 } 349 asourcetoken.insqlpluscmd = true; 350 insqlpluscmd = true; 351 } 352 } 353 } 354 isvalidplace = false; 355 356 break; 357 } // end of solid tokentext 358 } // switch 359 360 sourcetokenlist.add(asourcetoken); 361 362 asourcetoken = getanewsourcetoken(); 363 if (asourcetoken == null) break; 364 yychar = asourcetoken.tokencode; 365 366 } // while 367 } 368 369 // ========== DB2-Specific Raw Statement Extraction ========== 370 // Migrated from TGSqlParser.dodb2getrawsqlstatements() 371 372 /** 373 * Extract raw DB2 SQL statements from token list. 374 * <p> 375 * Handles DB2-specific statement boundaries: 376 * <ul> 377 * <li>Stored procedures, functions, triggers with BEGIN/END blocks</li> 378 * <li>DECLARE CURSOR statements</li> 379 * <li>Compound statements with nested blocks</li> 380 * <li>Echo commands as SQL*Plus commands</li> 381 * <li>Token transformations (DECLARE GLOBAL, YEAR as alias, TRIM L/R)</li> 382 * </ul> 383 * <p> 384 * Migrated from TGSqlParser.dodb2getrawsqlstatements (lines 15346-15622) 385 * 386 * @param builder result builder to populate with extracted statements 387 */ 388 private void dodb2getrawsqlstatements(SqlParseResult.Builder builder) { 389 // Clear and initialize sqlstatements 390 if (TBaseType.assigned(sqlstatements)) { 391 sqlstatements.clear(); 392 } 393 394 // Check if sourcetokenlist is available 395 if (!TBaseType.assigned(sourcetokenlist)) { 396 // No tokens available - populate builder with empty results and return 397 builder.sqlStatements(this.sqlstatements); 398 builder.errorCode(1); 399 builder.errorMessage("No source token list available"); 400 return; 401 } 402 403 TCustomSqlStatement gcurrentsqlstatement = null; 404 EFindSqlStateType gst = EFindSqlStateType.stnormal; 405 int i, c; 406 TSourceToken ast; 407 int errorcount = 0; 408 char ch; 409 String lcstr; 410 int waitingEnds = 0; 411 boolean waitingForFirstBegin = false; 412 char curDelimiterChar = delimiterChar; 413 414 for (i = 0; i < sourcetokenlist.size(); i++) { 415 ast = sourcetokenlist.get(i); 416 sourcetokenlist.curpos = i; 417 418 // Token transformations 419 if (ast.tokencode == TBaseType.rrw_declare) { 420 TSourceToken st1 = ast.searchToken("global", 1); 421 if (st1 != null) { 422 ast.tokencode = TBaseType.rrw_declare_global; 423 } 424 } else if ((ast.tokencode == TBaseType.rrw_year) || (ast.tokencode == TBaseType.rrw_db2_second) 425 || (ast.tokencode == TBaseType.rrw_db2_current)) { 426 TSourceToken st1 = ast.searchToken('.', 1); 427 if (st1 != null) { 428 // year.columnA, year is a table alias 429 ast.tokencode = TBaseType.ident; 430 } 431 } else if ((ast.tokencode == TBaseType.rrw_db2_trim_l) || (ast.tokencode == TBaseType.rrw_db2_trim_r)) { 432 TSourceToken st1 = ast.searchToken(TBaseType.rrw_db2_trim, -2); 433 if (st1 == null) { 434 // not L,R in TRIM (R 'Mr.' FROM name) 435 ast.tokencode = TBaseType.ident; 436 } 437 } 438 439 switch (gst) { 440 case sterror: { 441 if (ast.tokentype == ETokenType.ttsemicolon) { 442 gcurrentsqlstatement.sourcetokenlist.add(ast); 443 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 444 gst = EFindSqlStateType.stnormal; 445 } else { 446 gcurrentsqlstatement.sourcetokenlist.add(ast); 447 } 448 break; 449 } 450 case stnormal: { 451 if ((ast.tokencode == TBaseType.cmtdoublehyphen) 452 || (ast.tokencode == TBaseType.cmtslashstar) 453 || (ast.tokencode == TBaseType.lexspace) 454 || (ast.tokencode == TBaseType.lexnewline) 455 || (ast.tokentype == ETokenType.ttsemicolon)) { 456 if (TBaseType.assigned(gcurrentsqlstatement)) { 457 gcurrentsqlstatement.sourcetokenlist.add(ast); 458 } 459 continue; 460 } 461 462 if (ast.tokencode == TBaseType.sqlpluscmd) { 463 // echo in db2 is treated as sqlplus cmd 464 gst = EFindSqlStateType.stsqlplus; 465 gcurrentsqlstatement = new TSqlplusCmdStatement(vendor); 466 gcurrentsqlstatement.sourcetokenlist.add(ast); 467 continue; 468 } 469 470 // find a tokentext to start sql or plsql mode 471 gcurrentsqlstatement = sqlcmds.issql(ast, gst, gcurrentsqlstatement); 472 473 if (TBaseType.assigned(gcurrentsqlstatement)) { 474 ESqlStatementType[] ses = {ESqlStatementType.sstdb2declarecursor, ESqlStatementType.sstcreateprocedure, 475 ESqlStatementType.sstcreatefunction, ESqlStatementType.sstcreatetrigger, ESqlStatementType.sst_plsql_block}; 476 if (includesqlstatementtype(gcurrentsqlstatement.sqlstatementtype, ses)) { 477 gst = EFindSqlStateType.ststoredprocedure; 478 waitingEnds = 1; // need a END keyword to end this procedure 479 waitingForFirstBegin = true; 480 gcurrentsqlstatement.sourcetokenlist.add(ast); 481 if ((gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstcreateprocedure) 482 || (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstcreatefunction) 483 || (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstcreatetrigger)) { 484 curDelimiterChar = ';'; 485 } 486 } else if (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstdb2scriptOption) { 487 gst = EFindSqlStateType.stnormal; 488 gcurrentsqlstatement.sourcetokenlist.add(ast); 489 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 490 } else { 491 gst = EFindSqlStateType.stsql; 492 gcurrentsqlstatement.sourcetokenlist.add(ast); 493 } 494 495 } 496 497 if (!TBaseType.assigned(gcurrentsqlstatement)) // error tokentext found 498 { 499 this.syntaxErrors.add(new TSyntaxError(ast.getAstext(), ast.lineNo, (ast.columnNo < 0 ? 0 : ast.columnNo) 500 , "Error when tokenlize", EErrorType.spwarning, TBaseType.MSG_WARNING_ERROR_WHEN_TOKENIZE, null, ast.posinlist)); 501 502 ast.tokentype = ETokenType.tttokenlizererrortoken; 503 gst = EFindSqlStateType.sterror; 504 505 gcurrentsqlstatement = new TUnknownSqlStatement(vendor); 506 gcurrentsqlstatement.sqlstatementtype = ESqlStatementType.sstinvalid; 507 gcurrentsqlstatement.sourcetokenlist.add(ast); 508 509 } 510 break; 511 } 512 case stsqlplus: // treat echo, ! of db2 at the begin of each line as oracle sqlplus command 513 { 514 if (ast.insqlpluscmd) { 515 gcurrentsqlstatement.sourcetokenlist.add(ast); 516 } else { 517 gst = EFindSqlStateType.stnormal; // this tokentext must be newline, 518 gcurrentsqlstatement.sourcetokenlist.add(ast); // so add it here 519 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 520 } 521 break; 522 } 523 case stsql: { 524 if (ast.tokentype == ETokenType.ttsemicolon) { 525 gst = EFindSqlStateType.stnormal; 526 gcurrentsqlstatement.sourcetokenlist.add(ast); 527 gcurrentsqlstatement.semicolonended = ast; 528 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 529 continue; 530 } 531 gcurrentsqlstatement.sourcetokenlist.add(ast); 532 533 break; 534 } 535 case ststoredprocedure: { 536 TCustomSqlStatement nextStmt = sqlcmds.issql(ast, gst, gcurrentsqlstatement); 537 if ((nextStmt != null) 538 && ((nextStmt.sqlstatementtype == ESqlStatementType.sstcreateprocedure) 539 || (nextStmt.sqlstatementtype == ESqlStatementType.sstcreatefunction) 540 || (nextStmt.sqlstatementtype == ESqlStatementType.sstcreatetrigger))) { 541 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 542 gcurrentsqlstatement = nextStmt; 543 gst = EFindSqlStateType.ststoredprocedure; 544 waitingEnds = 1; // need a END keyword to end this procedure 545 waitingForFirstBegin = true; 546 gcurrentsqlstatement.sourcetokenlist.add(ast); 547 curDelimiterChar = ';'; 548 continue; 549 } 550 551 // single stmt in function/procedure/trigger may use ; as terminate char 552 // so default terminate char is ;, if begin is found, then 553 // set terminate char to DelimiterChar 554 if (curDelimiterChar != delimiterChar) { 555 if ((ast.tokencode == TBaseType.rrw_begin) || (ast.tokencode == TBaseType.rrw_declare)) { 556 curDelimiterChar = delimiterChar; 557 } 558 } 559 560 if (curDelimiterChar == ';') { 561 gcurrentsqlstatement.sourcetokenlist.add(ast); 562 if (ast.tokentype == ETokenType.ttsemicolon) { 563 gst = EFindSqlStateType.stnormal; 564 gcurrentsqlstatement.semicolonended = ast; 565 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 566 continue; 567 } 568 } else { 569 if (ast.getAstext().length() == 1) { 570 ch = ast.getAstext().charAt(0); 571 } else if ((ast.getAstext().length() > 1) && (ast.issolidtoken())) { 572 ch = ast.getAstext().charAt(ast.getAstext().length() - 1); 573 } else { 574 ch = ' '; 575 } 576 577 if (ch == curDelimiterChar) { 578 if (ast.getAstext().length() > 1) { 579 lcstr = ast.getAstext().substring(0, ast.getAstext().length() - 1); 580 if ((c = flexer.getkeywordvalue(lcstr)) > 0) 581 ast.tokencode = c; 582 } else { 583 gcurrentsqlstatement.semicolonended = ast; 584 } 585 gcurrentsqlstatement.sourcetokenlist.add(ast); 586 gst = EFindSqlStateType.stnormal; 587 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 588 } else { 589 gcurrentsqlstatement.sourcetokenlist.add(ast); 590 591 if ((ast.tokencode == TBaseType.rrw_case) 592 || (ast.tokencode == TBaseType.rrw_for) 593 || (ast.tokencode == TBaseType.rrw_if) 594 || (ast.tokencode == TBaseType.rrw_while) 595 || (ast.tokencode == TBaseType.rrw_repeat) 596 || (ast.tokencode == TBaseType.rrw_loop)) { 597 TSourceToken nextst = ast.nextSolidToken(); 598 if (nextst != null) { 599 if (nextst.tokencode != ';') { 600 waitingEnds++; 601 } 602 } 603 604 } else if (ast.tokencode == TBaseType.rrw_begin) { 605 if (waitingForFirstBegin) { 606 waitingForFirstBegin = false; 607 } else { 608 waitingEnds++; 609 } 610 } else if (ast.tokencode == TBaseType.rrw_end) { 611 waitingEnds--; 612 } 613 614 if ((ast.tokentype == ETokenType.ttsemicolon) && (waitingEnds == 0)) { 615 gst = EFindSqlStateType.stnormal; 616 gcurrentsqlstatement.semicolonended = ast; 617 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 618 continue; 619 } 620 621 } 622 } // end of DelimiterChar 623 624 break; 625 } // db2 sp 626 } // case 627 } // for 628 629 // last statement 630 if (TBaseType.assigned(gcurrentsqlstatement) && ((gst == EFindSqlStateType.stsqlplus) || (gst == EFindSqlStateType.stsql) || (gst == EFindSqlStateType.ststoredprocedure) || (gst == EFindSqlStateType.sterror))) { 631 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, true, builder); 632 } 633 634 // Populate builder with extracted statements and errors 635 builder.sqlStatements(this.sqlstatements); 636 builder.syntaxErrors(syntaxErrors instanceof ArrayList ? 637 (ArrayList<TSyntaxError>) syntaxErrors : new ArrayList<>(syntaxErrors)); 638 builder.errorCode(syntaxErrors.isEmpty() ? 0 : syntaxErrors.size()); 639 } 640 641 /** 642 * Get previous non-whitespace/comment token. 643 * <p> 644 * Helper method for tokenization that finds the previous "solid" token 645 * (skipping whitespace, newlines, and comments). 646 * 647 * @param ptoken current token 648 * @return previous solid token, or null if not found 649 */ 650 private TSourceToken getprevsolidtoken(TSourceToken ptoken) { 651 TSourceToken ret = null; 652 TSourceTokenList lctokenlist = ptoken.container; 653 654 if (lctokenlist != null) { 655 if ((ptoken.posinlist > 0) && (lctokenlist.size() > ptoken.posinlist - 1)) { 656 if (!( 657 (lctokenlist.get(ptoken.posinlist - 1).tokentype == ETokenType.ttwhitespace) 658 || (lctokenlist.get(ptoken.posinlist - 1).tokentype == ETokenType.ttreturn) 659 || (lctokenlist.get(ptoken.posinlist - 1).tokentype == ETokenType.ttsimplecomment) 660 || (lctokenlist.get(ptoken.posinlist - 1).tokentype == ETokenType.ttbracketedcomment) 661 )) { 662 ret = lctokenlist.get(ptoken.posinlist - 1); 663 } else { 664 ret = lctokenlist.nextsolidtoken(ptoken.posinlist - 1, -1, false); 665 } 666 } 667 } 668 return ret; 669 } 670 671 /** 672 * Helper method to check if a statement type is in the given array. 673 * Migrated from TGSqlParser. 674 * 675 * @param type statement type to check 676 * @param types array of statement types 677 * @return true if type is in array 678 */ 679 private boolean includesqlstatementtype(ESqlStatementType type, ESqlStatementType[] types) { 680 for (ESqlStatementType t : types) { 681 if (type == t) return true; 682 } 683 return false; 684 } 685 686 @Override 687 public String toString() { 688 return "Db2SqlParser{vendor=" + vendor + "}"; 689 } 690}