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.TLexerGreenplum; 009import gudusoft.gsqlparser.TParserGreenplum; 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.stmt.greenplum.TSlashCommand; 021import gudusoft.gsqlparser.stmt.TRoutine; 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.TLog; 030import gudusoft.gsqlparser.compiler.TASTEvaluator; 031 032import java.io.BufferedReader; 033import java.util.ArrayList; 034import java.util.List; 035import java.util.Stack; 036 037/** 038 * Greenplum database SQL parser implementation. 039 * 040 * <p>This parser handles Greenplum-specific SQL syntax including: 041 * <ul> 042 * <li>PL/pgSQL blocks (procedures, functions, triggers)</li> 043 * <li>Dollar-quoted strings ($$ ... $$)</li> 044 * <li>Backslash meta commands (\d, \dt, etc.)</li> 045 * <li>PostgreSQL-compatible syntax extensions</li> 046 * <li>Special token handling (INNER, NOT DEFERRABLE, etc.)</li> 047 * </ul> 048 * 049 * <p><b>Design Notes:</b> 050 * <ul> 051 * <li>Extends {@link AbstractSqlParser} using the template method pattern</li> 052 * <li>Uses {@link TLexerGreenplum} for tokenization</li> 053 * <li>Uses {@link TParserGreenplum} for parsing</li> 054 * <li>Delimiter character: '/' for PL/pgSQL blocks</li> 055 * </ul> 056 * 057 * <p><b>Usage Example:</b> 058 * <pre> 059 * // Get Greenplum parser from factory 060 * SqlParser parser = SqlParserFactory.get(EDbVendor.dbvgreenplum); 061 * 062 * // Build context 063 * ParserContext context = new ParserContext.Builder(EDbVendor.dbvgreenplum) 064 * .sqlText("SELECT * FROM employees WHERE dept_id = 10") 065 * .build(); 066 * 067 * // Parse 068 * SqlParseResult result = parser.parse(context); 069 * 070 * // Access statements 071 * TStatementList statements = result.getSqlStatements(); 072 * </pre> 073 * 074 * @see SqlParser 075 * @see AbstractSqlParser 076 * @see TLexerGreenplum 077 * @see TParserGreenplum 078 * @since 3.3.1.0 079 */ 080public class GreenplumSqlParser extends AbstractSqlParser { 081 082 /** 083 * Construct Greenplum SQL parser. 084 * <p> 085 * Configures the parser for Greenplum database with default delimiter (/). 086 * <p> 087 * Following the original TGSqlParser pattern, the lexer and parser are 088 * created once in the constructor and reused for all parsing operations. 089 */ 090 public GreenplumSqlParser() { 091 super(EDbVendor.dbvgreenplum); 092 this.delimiterChar = '/'; 093 this.defaultDelimiterStr = "/"; 094 095 // Create lexer once - will be reused for all parsing operations 096 this.flexer = new TLexerGreenplum(); 097 this.flexer.delimiterchar = this.delimiterChar; 098 this.flexer.defaultDelimiterStr = this.defaultDelimiterStr; 099 100 // Set parent's lexer reference for shared tokenization logic 101 this.lexer = this.flexer; 102 103 // Create parser once - will be reused for all parsing operations 104 this.fparser = new TParserGreenplum(null); 105 this.fparser.lexer = this.flexer; 106 } 107 108 // ========== Parser Components ========== 109 110 /** The Greenplum lexer used for tokenization */ 111 public TLexerGreenplum flexer; 112 113 /** SQL parser (for Greenplum statements) */ 114 private TParserGreenplum fparser; 115 116 /** Current statement being built during extraction */ 117 private TCustomSqlStatement gcurrentsqlstatement; 118 119 // Note: Global context and frame stack fields inherited from AbstractSqlParser: 120 // - protected TContext globalContext 121 // - protected TSQLEnv sqlEnv 122 // - protected Stack<TFrame> frameStack 123 // - protected TFrame globalFrame 124 125 // ========== AbstractSqlParser Abstract Methods Implementation ========== 126 127 /** 128 * Return the Greenplum lexer instance. 129 */ 130 @Override 131 protected TCustomLexer getLexer(ParserContext context) { 132 return this.flexer; 133 } 134 135 /** 136 * Return the Greenplum SQL parser instance with updated token list. 137 */ 138 @Override 139 protected TCustomParser getParser(ParserContext context, TSourceTokenList tokens) { 140 this.fparser.sourcetokenlist = tokens; 141 return this.fparser; 142 } 143 144 /** 145 * Greenplum doesn't use secondary parser (unlike Oracle with PL/SQL parser). 146 */ 147 @Override 148 protected TCustomParser getSecondaryParser(ParserContext context, TSourceTokenList tokens) { 149 return null; 150 } 151 152 /** 153 * Call Greenplum-specific tokenization logic. 154 * <p> 155 * Delegates to dogreenplumtexttotokenlist which handles Greenplum's 156 * tokenization including backslash commands, dollar-quoted strings, and 157 * PostgreSQL-compatible syntax. 158 */ 159 @Override 160 protected void tokenizeVendorSql() { 161 dogreenplumtexttotokenlist(); 162 } 163 164 /** 165 * Setup vendor parsers before raw statement extraction. 166 * <p> 167 * Injects sqlcmds and sourcetokenlist into the Greenplum parser. 168 */ 169 @Override 170 protected void setupVendorParsersForExtraction() { 171 this.fparser.sqlcmds = this.sqlcmds; 172 this.fparser.sourcetokenlist = this.sourcetokenlist; 173 } 174 175 /** 176 * Call Greenplum-specific raw statement extraction. 177 * <p> 178 * Delegates to dogreenplumgetrawsqlstatements which handles: 179 * - PL/pgSQL block boundaries (dollar-quoted strings) 180 * - Backslash meta commands 181 * - Semicolon-separated statements 182 */ 183 @Override 184 protected void extractVendorRawStatements(SqlParseResult.Builder builder) { 185 dogreenplumgetrawsqlstatements(builder); 186 } 187 188 /** 189 * Parse all statements in the statement list. 190 * <p> 191 * This is the main parsing loop that processes each raw statement 192 * and converts it into a fully parsed AST. 193 */ 194 @Override 195 protected TStatementList performParsing(ParserContext context, 196 TCustomParser mainParser, 197 TCustomParser secondaryParser, 198 TSourceTokenList tokens, 199 TStatementList rawStatements) { 200 // Store references 201 this.fparser = (TParserGreenplum) mainParser; 202 this.sourcetokenlist = tokens; 203 this.parserContext = context; 204 205 // Initialize sqlcmds for this parsing session 206 this.sqlcmds = SqlCmdsFactory.get(vendor); 207 this.fparser.sqlcmds = this.sqlcmds; 208 209 // Initialize global context and frame stack 210 initializeGlobalContext(); 211 212 // Parse each statement 213 for (int i = 0; i < rawStatements.size(); i++) { 214 TCustomSqlStatement stmt = rawStatements.getRawSql(i); 215 try { 216 stmt.setFrameStack(frameStack); 217 int parseResult = stmt.parsestatement(null, false, context.isOnlyNeedRawParseTree()); 218 219 // Vendor-specific post-processing (if needed) 220 afterStatementParsed(stmt); 221 222 // Error recovery for CREATE TABLE statements 223 boolean doRecover = TBaseType.ENABLE_ERROR_RECOVER_IN_CREATE_TABLE; 224 if (doRecover && ((parseResult != 0) || (stmt.getErrorCount() > 0))) { 225 handleCreateTableErrorRecovery(stmt); 226 } 227 228 // Collect syntax errors from the statement 229 if ((parseResult != 0) || (stmt.getErrorCount() > 0)) { 230 copyErrorsFromStatement(stmt); 231 } 232 } catch (Exception ex) { 233 // Use inherited exception handler 234 handleStatementParsingException(stmt, i, ex); 235 continue; 236 } 237 } 238 239 // Clean up frame stack 240 if (globalFrame != null) { 241 globalFrame.popMeFromStack(frameStack); 242 } 243 244 return rawStatements; 245 } 246 247 /** 248 * Perform semantic analysis on parsed statements. 249 * <p> 250 * This runs the TSQLResolver to resolve column-to-table relationships, 251 * data flow analysis, and other semantic checks. 252 */ 253 @Override 254 protected void performSemanticAnalysis(ParserContext context, TStatementList statements) { 255 if (TBaseType.isEnableResolver() && getSyntaxErrors().isEmpty()) { 256 TSQLResolver resolver = new TSQLResolver(globalContext, statements); 257 resolver.resolve(); 258 } 259 } 260 261 262 // ========== Greenplum-Specific Tokenization ========== 263 264 /** 265 * Tokenize Greenplum SQL text into tokens. 266 * <p> 267 * This method handles Greenplum-specific tokenization including: 268 * - Backslash meta commands (\d, \dt, etc.) 269 * - Forward slash as SQL*Plus-like command delimiter 270 * - INNER keyword disambiguation 271 * - NOT DEFERRABLE keyword combination 272 * - Special operators (%, ROWTYPE, etc.) 273 */ 274 private void dogreenplumtexttotokenlist() { 275 boolean insqlpluscmd = false; 276 boolean isvalidplace = true; 277 boolean waitingreturnforfloatdiv = false; 278 boolean waitingreturnforsemicolon = false; 279 boolean continuesqlplusatnewline = false; 280 281 TSourceToken lct = null, prevst = null; 282 283 TSourceToken asourcetoken, lcprevst; 284 int yychar; 285 286 asourcetoken = getanewsourcetoken(); 287 if (asourcetoken == null) return; 288 yychar = asourcetoken.tokencode; 289 290 while (yychar > 0) { 291 sourcetokenlist.add(asourcetoken); 292 switch (yychar) { 293 case TBaseType.cmtdoublehyphen: 294 case TBaseType.cmtslashstar: 295 case TBaseType.lexspace: { 296 if (insqlpluscmd) { 297 asourcetoken.insqlpluscmd = true; 298 } 299 break; 300 } 301 case TBaseType.lexnewline: { 302 if (insqlpluscmd) { 303 insqlpluscmd = false; 304 isvalidplace = true; 305 306 if (continuesqlplusatnewline) { 307 insqlpluscmd = true; 308 isvalidplace = false; 309 asourcetoken.insqlpluscmd = true; 310 } 311 } 312 313 if (waitingreturnforsemicolon) { 314 isvalidplace = true; 315 } 316 if (waitingreturnforfloatdiv) { 317 isvalidplace = true; 318 lct.tokencode = TBaseType.sqlpluscmd; 319 if (lct.tokentype != ETokenType.ttslash) { 320 lct.tokentype = ETokenType.ttsqlpluscmd; 321 } 322 } 323 flexer.insqlpluscmd = insqlpluscmd; 324 break; 325 } //case newline 326 default: { 327 //solid token 328 continuesqlplusatnewline = false; 329 waitingreturnforsemicolon = false; 330 waitingreturnforfloatdiv = false; 331 if (insqlpluscmd) { 332 asourcetoken.insqlpluscmd = true; 333 if (asourcetoken.toString().equalsIgnoreCase("-")) { 334 continuesqlplusatnewline = true; 335 } 336 } else { 337 if (asourcetoken.tokentype == ETokenType.ttsemicolon) { 338 waitingreturnforsemicolon = true; 339 } 340 if ((asourcetoken.tokentype == ETokenType.ttslash) 341 && (isvalidplace || (IsValidPlaceForDivToSqlplusCmd(sourcetokenlist, asourcetoken.posinlist)))) { 342 lct = asourcetoken; 343 waitingreturnforfloatdiv = true; 344 } 345 if ((isvalidplace) && isvalidsqlpluscmdInPostgresql(asourcetoken.toString())) { 346 asourcetoken.tokencode = TBaseType.sqlpluscmd; 347 if (asourcetoken.tokentype != ETokenType.ttslash) { 348 asourcetoken.tokentype = ETokenType.ttsqlpluscmd; 349 } 350 insqlpluscmd = true; 351 flexer.insqlpluscmd = insqlpluscmd; 352 } 353 } 354 isvalidplace = false; 355 356 // the inner keyword token should be convert to ident when 357 // next solid token is not join 358 359 if (prevst != null) { 360 if (prevst.tokencode == TBaseType.rrw_inner) { 361 if (asourcetoken.tokencode != flexer.getkeywordvalue("JOIN")) { 362 prevst.tokencode = TBaseType.ident; 363 } 364 } 365 366 if ((prevst.tokencode == TBaseType.rrw_not) 367 && (asourcetoken.tokencode == flexer.getkeywordvalue("DEFERRABLE"))) { 368 prevst.tokencode = flexer.getkeywordvalue("NOT_DEFERRABLE"); 369 } 370 } 371 372 if (asourcetoken.tokencode == TBaseType.rrw_inner) { 373 prevst = asourcetoken; 374 } else if (asourcetoken.tokencode == TBaseType.rrw_not) { 375 prevst = asourcetoken; 376 } else { 377 prevst = null; 378 } 379 380 if ((asourcetoken.tokencode == flexer.getkeywordvalue("DIRECT_LOAD")) 381 || (asourcetoken.tokencode == flexer.getkeywordvalue("ALL"))) { 382 // RW_COMPRESS RW_FOR RW_ALL RW_OPERATIONS 383 // RW_COMPRESS RW_FOR RW_DIRECT_LOAD RW_OPERATIONS 384 // change rw_for to rw_for1, it conflicts with compress for update in create materialized view 385 386 lcprevst = getprevsolidtoken(asourcetoken); 387 if (lcprevst != null) { 388 if (lcprevst.tokencode == TBaseType.rrw_for) 389 lcprevst.tokencode = TBaseType.rw_for1; 390 } 391 } 392 393 if (asourcetoken.tokencode == TBaseType.rrw_dense_rank) { 394 //keep keyword can be column alias, make keep in keep_denserankclause as a different token code 395 TSourceToken stKeep = asourcetoken.searchToken(TBaseType.rrw_keep, -2); 396 if (stKeep != null) { 397 stKeep.tokencode = TBaseType.rrw_keep_before_dense_rank; 398 } 399 } 400 401 if (asourcetoken.tokencode == TBaseType.rrw_greenplum_rowtype) { 402 TSourceToken stPercent = asourcetoken.searchToken('%', -1); 403 if (stPercent != null) { 404 stPercent.tokencode = TBaseType.rowtype_operator; 405 } 406 } 407 408 } 409 } 410 411 //flexer.yylexwrap(asourcetoken); 412 asourcetoken = getanewsourcetoken(); 413 if (asourcetoken != null) { 414 yychar = asourcetoken.tokencode; 415 } else { 416 yychar = 0; 417 418 if (waitingreturnforfloatdiv) { // / at the end of line treat as sqlplus command 419 //isvalidplace = true; 420 lct.tokencode = TBaseType.sqlpluscmd; 421 if (lct.tokentype != ETokenType.ttslash) { 422 lct.tokentype = ETokenType.ttsqlpluscmd; 423 } 424 } 425 426 } 427 428 if ((yychar == 0) && (prevst != null)) { 429 if (prevst.tokencode == TBaseType.rrw_inner) { 430 prevst.tokencode = TBaseType.ident; 431 } 432 } 433 434 } 435 } 436 437 // ========== Greenplum-Specific Raw Statement Extraction ========== 438 439 /** 440 * Extract raw SQL statements from token list. 441 * <p> 442 * This method handles Greenplum-specific statement boundaries: 443 * - Semicolon (;) for regular SQL statements 444 * - Dollar-quoted strings ($$ ... $$) for PL/pgSQL function bodies 445 * - Backslash commands (\d, \dt, etc.) 446 * - BEGIN/END blocks for stored procedures 447 */ 448 private int dogreenplumgetrawsqlstatements(SqlParseResult.Builder builder) { 449 int waitingEnd = 0; 450 boolean foundEnd = false, enterDeclare = false; 451 452 if (TBaseType.assigned(sqlstatements)) sqlstatements.clear(); 453 if (!TBaseType.assigned(sourcetokenlist)) return -1; 454 455 gcurrentsqlstatement = null; 456 EFindSqlStateType gst = EFindSqlStateType.stnormal; 457 TSourceToken lcprevsolidtoken = null, ast = null; 458 TSourceToken dollarStringToken = null; 459 460 for (int i = 0; i < sourcetokenlist.size(); i++) { 461 462 if ((ast != null) && (ast.issolidtoken())) 463 lcprevsolidtoken = ast; 464 465 ast = sourcetokenlist.get(i); 466 sourcetokenlist.curpos = i; 467 468 // Special token adjustments 469 if (ast.tokencode == TBaseType.rrw_date) { 470 TSourceToken st1 = ast.nextSolidToken(); 471 if (st1 != null) { 472 if (st1.tokencode == '(') { 473 ast.tokencode = TBaseType.rrw_greenplum_DATE_FUNCTION; 474 } 475 } 476 } else if (ast.tokencode == TBaseType.rrw_greenplum_POSITION) { 477 TSourceToken st1 = ast.nextSolidToken(); 478 if (st1 != null) { 479 if (st1.tokencode == '(') { 480 ast.tokencode = TBaseType.rrw_greenplum_POSITION_FUNCTION; 481 } 482 } 483 } else if (ast.tokencode == TBaseType.rrw_greenplum_filter) { 484 TSourceToken st1 = ast.nextSolidToken(); 485 if (st1 != null) { 486 if (st1.tokencode == '(') { 487 488 } else { 489 ast.tokencode = TBaseType.ident; 490 } 491 } 492 } else if (ast.tokencode == TBaseType.rrw_values) { 493 TSourceToken stParen = ast.searchToken('(', 1); 494 if (stParen != null) { 495 TSourceToken stInsert = ast.searchToken(TBaseType.rrw_insert, -ast.posinlist); 496 if (stInsert != null) { 497 TSourceToken stSemiColon = ast.searchToken(';', -ast.posinlist); 498 if ((stSemiColon != null) && (stSemiColon.posinlist > stInsert.posinlist)) { 499// INSERT INTO test values (16,1), (8,2), (4,4), (2,0), (97, 16); 500// VALUES (1); 501 // don't treat values(1) as insert values 502 503 } else { 504 TSourceToken stFrom = ast.searchToken(TBaseType.rrw_from, -ast.posinlist); 505 if (stFrom != null) { 506 // don't treat values after from keyword as a insert values 507 // insert into inserttest values(10, 20, '40'), (-1, 2, DEFAULT), ((select 2), (select i from (values(3) ) as foo (i)), 'values are fun!'); 508 509 } else { 510 ast.tokencode = TBaseType.rrw_greenplum_values_insert; 511 } 512 513 } 514 515 } 516 } 517 } 518 519 switch (gst) { 520 case sterror: { 521 if (ast.tokentype == ETokenType.ttsemicolon) { 522 gcurrentsqlstatement.sourcetokenlist.add(ast); 523 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 524 gst = EFindSqlStateType.stnormal; 525 } else { 526 gcurrentsqlstatement.sourcetokenlist.add(ast); 527 } 528 break; 529 } //sterror 530 531 case stnormal: { 532 if ((ast.tokencode == TBaseType.cmtdoublehyphen) 533 || (ast.tokencode == TBaseType.cmtslashstar) 534 || (ast.tokencode == TBaseType.lexspace) 535 || (ast.tokencode == TBaseType.lexnewline) 536 || (ast.tokentype == ETokenType.ttsemicolon)) { 537 if (gcurrentsqlstatement != null) { 538 gcurrentsqlstatement.sourcetokenlist.add(ast); 539 } 540 541 if ((lcprevsolidtoken != null) && (ast.tokentype == ETokenType.ttsemicolon)) { 542 if (lcprevsolidtoken.tokentype == ETokenType.ttsemicolon) { 543 // ;;;; continuous semicolon,treat it as comment 544 ast.tokentype = ETokenType.ttsimplecomment; 545 ast.tokencode = TBaseType.cmtdoublehyphen; 546 } 547 } 548 549 continue; 550 } 551 552 if (ast.tokencode == '\\') { 553 gst = EFindSqlStateType.stsqlplus; 554 gcurrentsqlstatement = new TSlashCommand(vendor); 555 gcurrentsqlstatement.sourcetokenlist.add(ast); 556 continue; 557 } 558 559 // find a token to start sql or plsql mode 560 gcurrentsqlstatement = sqlcmds.issql(ast, gst, gcurrentsqlstatement); 561 562 if (gcurrentsqlstatement != null) { 563 if (gcurrentsqlstatement.isgreeplumplsql()) { 564 gst = EFindSqlStateType.ststoredprocedure; 565 gcurrentsqlstatement.sourcetokenlist.add(ast); 566 foundEnd = false; 567 if ((ast.tokencode == TBaseType.rrw_begin) 568 || (ast.tokencode == TBaseType.rrw_package) 569 || (ast.searchToken(TBaseType.rrw_package, 4) != null)) { 570 waitingEnd = 1; 571 } else if (ast.tokencode == TBaseType.rrw_declare) { 572 enterDeclare = true; 573 } 574 } else { 575 gst = EFindSqlStateType.stsql; 576 gcurrentsqlstatement.sourcetokenlist.add(ast); 577 } 578 } else { 579 //error token found 580 581 this.syntaxErrors.add(new TSyntaxError(ast.getAstext(), ast.lineNo, (ast.columnNo < 0 ? 0 : ast.columnNo) 582 , "Error when tokenlize", EErrorType.spwarning, TBaseType.MSG_WARNING_ERROR_WHEN_TOKENIZE, null, ast.posinlist)); 583 584 ast.tokentype = ETokenType.tttokenlizererrortoken; 585 gst = EFindSqlStateType.sterror; 586 587 gcurrentsqlstatement = new TUnknownSqlStatement(vendor); 588 gcurrentsqlstatement.sqlstatementtype = ESqlStatementType.sstinvalid; 589 gcurrentsqlstatement.sourcetokenlist.add(ast); 590 591 } 592 593 break; 594 } // stnormal 595 596 case stsqlplus: { 597 if (ast.tokencode == TBaseType.lexnewline) { 598 gst = EFindSqlStateType.stnormal; //this token must be newline, 599 gcurrentsqlstatement.sourcetokenlist.add(ast); // so add it here 600 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 601 } 602 if (ast.tokencode == '\\') { 603 TSourceToken nextst = ast.searchToken('\\', 1); 604 if (nextst != null) { 605 gst = EFindSqlStateType.stnormal; 606 gcurrentsqlstatement.sourcetokenlist.add(ast); 607 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 608 } else { 609 gst = EFindSqlStateType.stsqlplus; 610 gcurrentsqlstatement = new TSlashCommand(vendor); 611 gcurrentsqlstatement.sourcetokenlist.add(ast); 612 continue; 613 } 614 } else { 615 { 616 gcurrentsqlstatement.sourcetokenlist.add(ast); 617 } 618 } 619 620 break; 621 }//case greenplum meta command 622 623 case stsql: { 624 if (ast.tokentype == ETokenType.ttsemicolon) { 625 gst = EFindSqlStateType.stnormal; 626 gcurrentsqlstatement.sourcetokenlist.add(ast); 627 gcurrentsqlstatement.semicolonended = ast; 628 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 629 continue; 630 } 631 632 if (sourcetokenlist.sqlplusaftercurtoken()) //most probably is / cmd 633 { 634 gst = EFindSqlStateType.stnormal; 635 gcurrentsqlstatement.sourcetokenlist.add(ast); 636 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 637 continue; 638 } 639 gcurrentsqlstatement.sourcetokenlist.add(ast); 640 break; 641 }//case stsql 642 643 case ststoredprocedure: { 644 if (ast.tokencode == TBaseType.rrw_greenplum_function_delimiter) { 645 gcurrentsqlstatement.sourcetokenlist.add(ast); 646 gst = EFindSqlStateType.ststoredprocedurePgStartBody; 647 dollarStringToken = ast; 648 continue; 649 } 650 651 if (ast.tokencode == TBaseType.rrw_greenplum_language) { 652 // check next token which is the language used by this stored procedure 653 TSourceToken nextSt = ast.nextSolidToken(); 654 if (nextSt != null) { 655 if (gcurrentsqlstatement instanceof TRoutine) { // can be TCreateProcedureStmt or TCreateFunctionStmt 656 TRoutine p = (TRoutine) gcurrentsqlstatement; 657 p.setRoutineLanguage(nextSt.toString()); 658 } 659 } 660 } 661 662 if ((ast.tokentype == ETokenType.ttsemicolon) && (waitingEnd == 0) && (!enterDeclare)) { 663 gst = EFindSqlStateType.stnormal; 664 gcurrentsqlstatement.sourcetokenlist.add(ast); 665 gcurrentsqlstatement.semicolonended = ast; 666 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 667 continue; 668 } 669 670 671 if ((ast.tokencode == TBaseType.rrw_begin)) { 672 waitingEnd++; 673 enterDeclare = false; 674 } else if ((ast.tokencode == TBaseType.rrw_declare)) { 675 enterDeclare = true; 676 } else if ((ast.tokencode == TBaseType.rrw_if)) { 677 if (ast.searchToken(TBaseType.rrw_end, -1) == null) { 678 //this is not if after END 679 if (!((ast.searchToken(TBaseType.rrw_greenplum_exits, 1) != null) 680 || (ast.searchToken(TBaseType.rrw_greenplum_exits, 2) != null))) // if not exists, if exists is not a real if statement, so skip those 681 { 682 waitingEnd++; 683 } 684 } 685 } else if ((ast.tokencode == TBaseType.rrw_case)) { 686 if (ast.searchToken(TBaseType.rrw_end, -1) == null) { 687 //this is not case after END 688 waitingEnd++; 689 } 690 } else if ((ast.tokencode == TBaseType.rrw_loop)) { 691 if (ast.searchToken(TBaseType.rrw_end, -1) == null) { 692 //this is not loop after END 693 waitingEnd++; 694 } 695 } else if (ast.tokencode == TBaseType.rrw_end) { 696 foundEnd = true; 697 waitingEnd--; 698 if (waitingEnd < 0) { 699 waitingEnd = 0; 700 } 701 } 702 703 if ((ast.tokentype == ETokenType.ttslash) && (ast.tokencode == TBaseType.sqlpluscmd)) //and (prevst.NewlineIsLastTokenInTailerToken)) then 704 { 705 // TPlsqlStatementParse(asqlstatement).TerminatorToken := ast; 706 ast.tokenstatus = ETokenStatus.tsignorebyyacc; 707 gst = EFindSqlStateType.stnormal; 708 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 709 710 //make / a sqlplus cmd 711 gcurrentsqlstatement = new gudusoft.gsqlparser.stmt.oracle.TSqlplusCmdStatement(vendor); 712 gcurrentsqlstatement.sourcetokenlist.add(ast); 713 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 714 } else if ((ast.tokentype == ETokenType.ttperiod) && (sourcetokenlist.returnaftercurtoken(false)) && (sourcetokenlist.returnbeforecurtoken(false))) { // single dot at a separate line 715 ast.tokenstatus = ETokenStatus.tsignorebyyacc; 716 gst = EFindSqlStateType.stnormal; 717 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 718 719 //make ttperiod a sqlplus cmd 720 gcurrentsqlstatement = new gudusoft.gsqlparser.stmt.oracle.TSqlplusCmdStatement(vendor); 721 gcurrentsqlstatement.sourcetokenlist.add(ast); 722 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 723 } else if ((ast.tokencode == TBaseType.rrw_declare) && (waitingEnd == 0)) { 724 i--; 725 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 726 gst = EFindSqlStateType.stnormal; 727 } else { 728 gcurrentsqlstatement.sourcetokenlist.add(ast); 729 if ((ast.tokentype == ETokenType.ttsemicolon) && (waitingEnd == 0) && (foundEnd) && (gcurrentsqlstatement.OracleStatementCanBeSeparatedByBeginEndPair())) { 730 gst = EFindSqlStateType.stnormal; 731 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 732 } 733 } 734 735 if (ast.tokencode == TBaseType.sqlpluscmd) { 736 //change tokencode back to keyword or ident, because sqlplus cmd 737 //in a sql statement(almost is plsql block) is not really a sqlplus cmd 738 int m = flexer.getkeywordvalue(ast.getAstext()); 739 if (m != 0) { 740 ast.tokencode = m; 741 } else { 742 ast.tokencode = TBaseType.ident; 743 } 744 } 745 746 break; 747 } //ststoredprocedure 748 749 case ststoredprocedurePgStartBody: { 750 gcurrentsqlstatement.sourcetokenlist.add(ast); 751 752 if (ast.tokencode == TBaseType.rrw_greenplum_function_delimiter) { 753 if (dollarStringToken.toString().equalsIgnoreCase(ast.toString())) { 754 // must match the $$ token 755 gst = EFindSqlStateType.ststoredprocedurePgEndBody; 756 continue; 757 } 758 } 759 760 break; 761 } 762 763 case ststoredprocedurePgEndBody: { 764 765 if (ast.tokentype == ETokenType.ttsemicolon) { 766 gst = EFindSqlStateType.stnormal; 767 gcurrentsqlstatement.sourcetokenlist.add(ast); 768 gcurrentsqlstatement.semicolonended = ast; 769 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 770 continue; 771 } else if (ast.tokencode == TBaseType.cmtdoublehyphen) { 772 if (ast.toString().trim().endsWith(TBaseType.sqlflow_stmt_delimiter_str)) { // -- sqlflow-delimiter 773 gst = EFindSqlStateType.stnormal; 774 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 775 continue; 776 } 777 } 778 779 gcurrentsqlstatement.sourcetokenlist.add(ast); 780 781 if (ast.tokencode == TBaseType.rrw_greenplum_language) { 782 // check next token which is the language used by this stored procedure 783 TSourceToken nextSt = ast.nextSolidToken(); 784 if (nextSt != null) { 785 if (gcurrentsqlstatement instanceof TRoutine) { // can be TCreateProcedureStmt or TCreateFunctionStmt 786 TRoutine p = (TRoutine) gcurrentsqlstatement; 787 p.setRoutineLanguage(nextSt.toString()); 788 } 789 } 790 } 791 792 break; 793 } 794 795 } //switch 796 }//for 797 798 //last statement 799 if ((gcurrentsqlstatement != null) && 800 ((gst == EFindSqlStateType.stsqlplus) || (gst == EFindSqlStateType.stsql) 801 || (gst == EFindSqlStateType.ststoredprocedure) || (gst == EFindSqlStateType.ststoredprocedurePgEndBody) 802 || (gst == EFindSqlStateType.sterror))) { 803 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, true, builder); 804 } 805 806 // Set results in builder 807 builder.sqlStatements(this.sqlstatements); 808 builder.errorCode(0); 809 builder.errorMessage(""); 810 811 return 0; 812 } 813 814 // ========== Helper Methods ========== 815 816 /** 817 * Get previous solid token (non-whitespace, non-comment). 818 */ 819 private TSourceToken getprevsolidtoken(TSourceToken asourcetoken) { 820 int i = asourcetoken.posinlist; 821 TSourceToken st; 822 for (i = i - 1; i >= 0; i--) { 823 st = sourcetokenlist.get(i); 824 if (st.issolidtoken()) return st; 825 } 826 return null; 827 } 828 829 /** 830 * Placeholder function for PostgreSQL-style meta command validation. 831 * Always returns false - Greenplum doesn't use SQL*Plus commands. 832 */ 833 private boolean isvalidsqlpluscmdInPostgresql(String astr) { 834 return false; 835 } 836 837 /** 838 * Check if forward slash (/) is in a valid position to be treated as a SQL*Plus command. 839 */ 840 private boolean IsValidPlaceForDivToSqlplusCmd(TSourceTokenList tokenList, int pos) { 841 // Implementation similar to TSourceTokenList.TokenBeforeCurToken 842 // This checks if there's a newline before the current token 843 if (pos <= 0) return false; 844 for (int i = pos - 1; i >= 0; i--) { 845 TSourceToken st = tokenList.get(i); 846 if (st.tokencode == TBaseType.lexnewline) { 847 return true; 848 } 849 if (st.issolidtoken()) { 850 return false; 851 } 852 } 853 return false; 854 } 855 856 /** 857 * Hook method called after each statement is parsed. 858 * <p> 859 * Greenplum doesn't need special post-processing, so this is a no-op. 860 */ 861 @Override 862 protected void afterStatementParsed(TCustomSqlStatement stmt) { 863 // No special post-processing needed for Greenplum 864 } 865 866 /** 867 * Handle error recovery for CREATE TABLE statements. 868 * <p> 869 * This attempts to recover from syntax errors in table properties 870 * by marking unparseable tokens as SQL*Plus commands and retrying. 871 * <p> 872 * Migrated from TGSqlParser.doparse() lines 16914-16971 873 */ 874 private void handleCreateTableErrorRecovery(TCustomSqlStatement stmt) { 875 // Only handle CREATE TABLE and CREATE INDEX (but not for Couchbase) 876 if (!((stmt.sqlstatementtype == ESqlStatementType.sstcreatetable) 877 || ((stmt.sqlstatementtype == ESqlStatementType.sstcreateindex) && (vendor != EDbVendor.dbvcouchbase)))) { 878 return; 879 } 880 881 // Don't recover if strict parsing is enabled 882 if (TBaseType.c_createTableStrictParsing) { 883 return; 884 } 885 886 // only parse main body of create table, 887 // ignore vendor-specific table properties after closing parenthesis 888 TCustomSqlStatement errorSqlStatement = stmt; 889 890 int nested = 0; 891 boolean isIgnore = false, isFoundIgnoreToken = false; 892 TSourceToken firstIgnoreToken = null; 893 894 for (int k = 0; k < errorSqlStatement.sourcetokenlist.size(); k++) { 895 TSourceToken st = errorSqlStatement.sourcetokenlist.get(k); 896 897 if (isIgnore) { 898 if (st.issolidtoken() && (st.tokencode != ';')) { 899 isFoundIgnoreToken = true; 900 if (firstIgnoreToken == null) { 901 firstIgnoreToken = st; 902 } 903 } 904 if (st.tokencode != ';') { 905 st.tokencode = TBaseType.sqlpluscmd; 906 } 907 continue; 908 } 909 910 if (st.tokencode == (int) ')') { 911 nested--; 912 if (nested == 0) { 913 //let's check if next token is: AS ( SELECT 914 boolean isSelect = false; 915 TSourceToken st1 = st.searchToken(TBaseType.rrw_as, 1); 916 if (st1 != null) { 917 TSourceToken st2 = st.searchToken((int) '(', 2); 918 if (st2 != null) { 919 TSourceToken st3 = st.searchToken(TBaseType.rrw_select, 3); 920 isSelect = (st3 != null); 921 } 922 } 923 if (!isSelect) isIgnore = true; 924 } 925 } 926 927 if ((st.tokencode == (int) '(') || (st.tokencode == TBaseType.left_parenthesis_2)) { 928 nested++; 929 } 930 } 931 932 // For Oracle, check if the first ignored token is a valid table property 933 if ((vendor == EDbVendor.dbvoracle) && ((firstIgnoreToken != null) && (!TBaseType.searchOracleTablePros(firstIgnoreToken.toString())))) { 934 // if it is not a valid Oracle table properties option, let raise the error. 935 isFoundIgnoreToken = false; 936 } 937 938 if (isFoundIgnoreToken) { 939 errorSqlStatement.clearError(); 940 stmt.parsestatement(null, false, parserContext.isOnlyNeedRawParseTree()); 941 } 942 } 943}