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