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 // Mantis 4497: a '/' on its own line is tentatively flagged as an Oracle 461 // SQL*Plus statement terminator (tokencode sqlpluscmd, but tokentype still 462 // ttslash) by dogreenplumsqltexttotokenlist(). Greenplum/psql has no '/' 463 // terminator -- '/' is the division operator. When such a slash sits between 464 // two operands (its next solid token can start a right-hand operand), revert 465 // it to a normal division operator so the surrounding expression is parsed as 466 // a single statement instead of being split. A genuine terminator is followed 467 // by end-of-input or a statement keyword, never a bare operand, so it keeps 468 // the sqlpluscmd flag and still splits. This pre-pass runs before the main 469 // extraction loop because sqlplusaftercurtoken() peeks ahead at the slash 470 // while processing the preceding token, so the revert must already be done. 471 for (int i = 0; i < sourcetokenlist.size(); i++) { 472 TSourceToken st = sourcetokenlist.get(i); 473 if ((st.tokencode == TBaseType.sqlpluscmd) 474 && (st.tokentype == ETokenType.ttslash) 475 && isDivisionOperatorContext(st)) { 476 st.tokencode = (int) '/'; 477 } 478 } 479 480 for (int i = 0; i < sourcetokenlist.size(); i++) { 481 482 if ((ast != null) && (ast.issolidtoken())) 483 lcprevsolidtoken = ast; 484 485 ast = sourcetokenlist.get(i); 486 sourcetokenlist.curpos = i; 487 488 // Special token adjustments 489 if (ast.tokencode == TBaseType.rrw_date) { 490 TSourceToken st1 = ast.nextSolidToken(); 491 if (st1 != null) { 492 if (st1.tokencode == '(') { 493 ast.tokencode = TBaseType.rrw_greenplum_DATE_FUNCTION; 494 } 495 } 496 } else if (ast.tokencode == TBaseType.rrw_greenplum_POSITION) { 497 TSourceToken st1 = ast.nextSolidToken(); 498 if (st1 != null) { 499 if (st1.tokencode == '(') { 500 ast.tokencode = TBaseType.rrw_greenplum_POSITION_FUNCTION; 501 } 502 } 503 } else if (ast.tokencode == TBaseType.rrw_greenplum_filter) { 504 TSourceToken st1 = ast.nextSolidToken(); 505 if (st1 != null) { 506 if (st1.tokencode == '(') { 507 508 } else { 509 ast.tokencode = TBaseType.ident; 510 } 511 } 512 } else if (ast.tokencode == TBaseType.rrw_values) { 513 TSourceToken stParen = ast.searchToken('(', 1); 514 if (stParen != null) { 515 TSourceToken stInsert = ast.searchToken(TBaseType.rrw_insert, -ast.posinlist); 516 if (stInsert != null) { 517 TSourceToken stSemiColon = ast.searchToken(';', -ast.posinlist); 518 if ((stSemiColon != null) && (stSemiColon.posinlist > stInsert.posinlist)) { 519// INSERT INTO test values (16,1), (8,2), (4,4), (2,0), (97, 16); 520// VALUES (1); 521 // don't treat values(1) as insert values 522 523 } else { 524 TSourceToken stFrom = ast.searchToken(TBaseType.rrw_from, -ast.posinlist); 525 if (stFrom != null) { 526 // don't treat values after from keyword as a insert values 527 // insert into inserttest values(10, 20, '40'), (-1, 2, DEFAULT), ((select 2), (select i from (values(3) ) as foo (i)), 'values are fun!'); 528 529 } else { 530 ast.tokencode = TBaseType.rrw_greenplum_values_insert; 531 } 532 533 } 534 535 } 536 } 537 } 538 539 switch (gst) { 540 case sterror: { 541 if (ast.tokentype == ETokenType.ttsemicolon) { 542 gcurrentsqlstatement.sourcetokenlist.add(ast); 543 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 544 gst = EFindSqlStateType.stnormal; 545 } else { 546 gcurrentsqlstatement.sourcetokenlist.add(ast); 547 } 548 break; 549 } //sterror 550 551 case stnormal: { 552 if ((ast.tokencode == TBaseType.cmtdoublehyphen) 553 || (ast.tokencode == TBaseType.cmtslashstar) 554 || (ast.tokencode == TBaseType.lexspace) 555 || (ast.tokencode == TBaseType.lexnewline) 556 || (ast.tokentype == ETokenType.ttsemicolon)) { 557 if (gcurrentsqlstatement != null) { 558 gcurrentsqlstatement.sourcetokenlist.add(ast); 559 } 560 561 if ((lcprevsolidtoken != null) && (ast.tokentype == ETokenType.ttsemicolon)) { 562 if (lcprevsolidtoken.tokentype == ETokenType.ttsemicolon) { 563 // ;;;; continuous semicolon,treat it as comment 564 ast.tokentype = ETokenType.ttsimplecomment; 565 ast.tokencode = TBaseType.cmtdoublehyphen; 566 } 567 } 568 569 continue; 570 } 571 572 if (ast.tokencode == '\\') { 573 gst = EFindSqlStateType.stsqlplus; 574 gcurrentsqlstatement = new TSlashCommand(vendor); 575 gcurrentsqlstatement.sourcetokenlist.add(ast); 576 continue; 577 } 578 579 // find a token to start sql or plsql mode 580 gcurrentsqlstatement = sqlcmds.issql(ast, gst, gcurrentsqlstatement); 581 582 if (gcurrentsqlstatement != null) { 583 if (gcurrentsqlstatement.isgreeplumplsql()) { 584 gst = EFindSqlStateType.ststoredprocedure; 585 gcurrentsqlstatement.sourcetokenlist.add(ast); 586 foundEnd = false; 587 if ((ast.tokencode == TBaseType.rrw_begin) 588 || (ast.tokencode == TBaseType.rrw_package) 589 || (ast.searchToken(TBaseType.rrw_package, 4) != null)) { 590 waitingEnd = 1; 591 } else if (ast.tokencode == TBaseType.rrw_declare) { 592 enterDeclare = true; 593 } 594 } else { 595 gst = EFindSqlStateType.stsql; 596 gcurrentsqlstatement.sourcetokenlist.add(ast); 597 } 598 } else { 599 //error token found 600 601 this.syntaxErrors.add(new TSyntaxError(ast.getAstext(), ast.lineNo, (ast.columnNo < 0 ? 0 : ast.columnNo) 602 , "Error when tokenlize", EErrorType.spwarning, TBaseType.MSG_WARNING_ERROR_WHEN_TOKENIZE, null, ast.posinlist)); 603 604 ast.tokentype = ETokenType.tttokenlizererrortoken; 605 gst = EFindSqlStateType.sterror; 606 607 gcurrentsqlstatement = new TUnknownSqlStatement(vendor); 608 gcurrentsqlstatement.sqlstatementtype = ESqlStatementType.sstinvalid; 609 gcurrentsqlstatement.sourcetokenlist.add(ast); 610 611 } 612 613 break; 614 } // stnormal 615 616 case stsqlplus: { 617 if (ast.tokencode == TBaseType.lexnewline) { 618 gst = EFindSqlStateType.stnormal; //this token must be newline, 619 gcurrentsqlstatement.sourcetokenlist.add(ast); // so add it here 620 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 621 } 622 if (ast.tokencode == '\\') { 623 TSourceToken nextst = ast.searchToken('\\', 1); 624 if (nextst != null) { 625 gst = EFindSqlStateType.stnormal; 626 gcurrentsqlstatement.sourcetokenlist.add(ast); 627 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 628 } else { 629 gst = EFindSqlStateType.stsqlplus; 630 gcurrentsqlstatement = new TSlashCommand(vendor); 631 gcurrentsqlstatement.sourcetokenlist.add(ast); 632 continue; 633 } 634 } else { 635 { 636 gcurrentsqlstatement.sourcetokenlist.add(ast); 637 } 638 } 639 640 break; 641 }//case greenplum meta command 642 643 case stsql: { 644 if (ast.tokentype == ETokenType.ttsemicolon) { 645 gst = EFindSqlStateType.stnormal; 646 gcurrentsqlstatement.sourcetokenlist.add(ast); 647 gcurrentsqlstatement.semicolonended = ast; 648 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 649 continue; 650 } 651 652 if (sourcetokenlist.sqlplusaftercurtoken()) //most probably is / cmd 653 { 654 gst = EFindSqlStateType.stnormal; 655 gcurrentsqlstatement.sourcetokenlist.add(ast); 656 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 657 continue; 658 } 659 gcurrentsqlstatement.sourcetokenlist.add(ast); 660 break; 661 }//case stsql 662 663 case ststoredprocedure: { 664 if (ast.tokencode == TBaseType.rrw_greenplum_function_delimiter) { 665 gcurrentsqlstatement.sourcetokenlist.add(ast); 666 gst = EFindSqlStateType.ststoredprocedurePgStartBody; 667 dollarStringToken = ast; 668 continue; 669 } 670 671 if (ast.tokencode == TBaseType.rrw_greenplum_language) { 672 // check next token which is the language used by this stored procedure 673 TSourceToken nextSt = ast.nextSolidToken(); 674 if (nextSt != null) { 675 if (gcurrentsqlstatement instanceof TRoutine) { // can be TCreateProcedureStmt or TCreateFunctionStmt 676 TRoutine p = (TRoutine) gcurrentsqlstatement; 677 p.setRoutineLanguage(nextSt.toString()); 678 } 679 } 680 } 681 682 if ((ast.tokentype == ETokenType.ttsemicolon) && (waitingEnd == 0) && (!enterDeclare)) { 683 gst = EFindSqlStateType.stnormal; 684 gcurrentsqlstatement.sourcetokenlist.add(ast); 685 gcurrentsqlstatement.semicolonended = ast; 686 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 687 continue; 688 } 689 690 691 if ((ast.tokencode == TBaseType.rrw_begin)) { 692 waitingEnd++; 693 enterDeclare = false; 694 } else if ((ast.tokencode == TBaseType.rrw_declare)) { 695 enterDeclare = true; 696 } else if ((ast.tokencode == TBaseType.rrw_if)) { 697 if (ast.searchToken(TBaseType.rrw_end, -1) == null) { 698 //this is not if after END 699 if (!((ast.searchToken(TBaseType.rrw_greenplum_exits, 1) != null) 700 || (ast.searchToken(TBaseType.rrw_greenplum_exits, 2) != null))) // if not exists, if exists is not a real if statement, so skip those 701 { 702 waitingEnd++; 703 } 704 } 705 } else if ((ast.tokencode == TBaseType.rrw_case)) { 706 if (ast.searchToken(TBaseType.rrw_end, -1) == null) { 707 //this is not case after END 708 waitingEnd++; 709 } 710 } else if ((ast.tokencode == TBaseType.rrw_loop)) { 711 if (ast.searchToken(TBaseType.rrw_end, -1) == null) { 712 //this is not loop after END 713 waitingEnd++; 714 } 715 } else if (ast.tokencode == TBaseType.rrw_end) { 716 foundEnd = true; 717 waitingEnd--; 718 if (waitingEnd < 0) { 719 waitingEnd = 0; 720 } 721 } 722 723 if ((ast.tokentype == ETokenType.ttslash) && (ast.tokencode == TBaseType.sqlpluscmd)) //and (prevst.NewlineIsLastTokenInTailerToken)) then 724 { 725 // TPlsqlStatementParse(asqlstatement).TerminatorToken := ast; 726 ast.tokenstatus = ETokenStatus.tsignorebyyacc; 727 gst = EFindSqlStateType.stnormal; 728 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 729 730 //make / a sqlplus cmd 731 gcurrentsqlstatement = new gudusoft.gsqlparser.stmt.oracle.TSqlplusCmdStatement(vendor); 732 gcurrentsqlstatement.sourcetokenlist.add(ast); 733 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 734 } else if ((ast.tokentype == ETokenType.ttperiod) && (sourcetokenlist.returnaftercurtoken(false)) && (sourcetokenlist.returnbeforecurtoken(false))) { // single dot at a separate line 735 ast.tokenstatus = ETokenStatus.tsignorebyyacc; 736 gst = EFindSqlStateType.stnormal; 737 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 738 739 //make ttperiod a sqlplus cmd 740 gcurrentsqlstatement = new gudusoft.gsqlparser.stmt.oracle.TSqlplusCmdStatement(vendor); 741 gcurrentsqlstatement.sourcetokenlist.add(ast); 742 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 743 } else if ((ast.tokencode == TBaseType.rrw_declare) && (waitingEnd == 0)) { 744 i--; 745 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 746 gst = EFindSqlStateType.stnormal; 747 } else { 748 gcurrentsqlstatement.sourcetokenlist.add(ast); 749 if ((ast.tokentype == ETokenType.ttsemicolon) && (waitingEnd == 0) && (foundEnd) && (gcurrentsqlstatement.OracleStatementCanBeSeparatedByBeginEndPair())) { 750 gst = EFindSqlStateType.stnormal; 751 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 752 } 753 } 754 755 if (ast.tokencode == TBaseType.sqlpluscmd) { 756 //change tokencode back to keyword or ident, because sqlplus cmd 757 //in a sql statement(almost is plsql block) is not really a sqlplus cmd 758 int m = flexer.getkeywordvalue(ast.getAstext()); 759 if (m != 0) { 760 ast.tokencode = m; 761 } else { 762 ast.tokencode = TBaseType.ident; 763 } 764 } 765 766 break; 767 } //ststoredprocedure 768 769 case ststoredprocedurePgStartBody: { 770 gcurrentsqlstatement.sourcetokenlist.add(ast); 771 772 if (ast.tokencode == TBaseType.rrw_greenplum_function_delimiter) { 773 if (dollarStringToken.toString().equalsIgnoreCase(ast.toString())) { 774 // must match the $$ token 775 gst = EFindSqlStateType.ststoredprocedurePgEndBody; 776 continue; 777 } 778 } 779 780 break; 781 } 782 783 case ststoredprocedurePgEndBody: { 784 785 if (ast.tokentype == ETokenType.ttsemicolon) { 786 gst = EFindSqlStateType.stnormal; 787 gcurrentsqlstatement.sourcetokenlist.add(ast); 788 gcurrentsqlstatement.semicolonended = ast; 789 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 790 continue; 791 } else if (ast.tokencode == TBaseType.cmtdoublehyphen) { 792 if (ast.toString().trim().endsWith(TBaseType.sqlflow_stmt_delimiter_str)) { // -- sqlflow-delimiter 793 gst = EFindSqlStateType.stnormal; 794 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 795 continue; 796 } 797 } 798 799 gcurrentsqlstatement.sourcetokenlist.add(ast); 800 801 if (ast.tokencode == TBaseType.rrw_greenplum_language) { 802 // check next token which is the language used by this stored procedure 803 TSourceToken nextSt = ast.nextSolidToken(); 804 if (nextSt != null) { 805 if (gcurrentsqlstatement instanceof TRoutine) { // can be TCreateProcedureStmt or TCreateFunctionStmt 806 TRoutine p = (TRoutine) gcurrentsqlstatement; 807 p.setRoutineLanguage(nextSt.toString()); 808 } 809 } 810 } 811 812 break; 813 } 814 815 } //switch 816 }//for 817 818 //last statement 819 if ((gcurrentsqlstatement != null) && 820 ((gst == EFindSqlStateType.stsqlplus) || (gst == EFindSqlStateType.stsql) 821 || (gst == EFindSqlStateType.ststoredprocedure) || (gst == EFindSqlStateType.ststoredprocedurePgEndBody) 822 || (gst == EFindSqlStateType.sterror))) { 823 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, true, builder); 824 } 825 826 // Set results in builder 827 builder.sqlStatements(this.sqlstatements); 828 builder.errorCode(0); 829 builder.errorMessage(""); 830 831 return 0; 832 } 833 834 // ========== Helper Methods ========== 835 836 /** 837 * Get previous solid token (non-whitespace, non-comment). 838 */ 839 private TSourceToken getprevsolidtoken(TSourceToken asourcetoken) { 840 int i = asourcetoken.posinlist; 841 TSourceToken st; 842 for (i = i - 1; i >= 0; i--) { 843 st = sourcetokenlist.get(i); 844 if (st.issolidtoken()) return st; 845 } 846 return null; 847 } 848 849 /** 850 * Placeholder function for PostgreSQL-style meta command validation. 851 * Always returns false - Greenplum doesn't use SQL*Plus commands. 852 */ 853 private boolean isvalidsqlpluscmdInPostgresql(String astr) { 854 return false; 855 } 856 857 858 /** 859 * Check if forward slash (/) is in a valid position to be treated as a SQL*Plus command. 860 */ 861 private boolean IsValidPlaceForDivToSqlplusCmd(TSourceTokenList tokenList, int pos) { 862 // Implementation similar to TSourceTokenList.TokenBeforeCurToken 863 // This checks if there's a newline before the current token 864 if (pos <= 0) return false; 865 for (int i = pos - 1; i >= 0; i--) { 866 TSourceToken st = tokenList.get(i); 867 if (st.tokencode == TBaseType.lexnewline) { 868 return true; 869 } 870 if (st.issolidtoken()) { 871 return false; 872 } 873 } 874 return false; 875 } 876 877 /** 878 * Hook method called after each statement is parsed. 879 * <p> 880 * Greenplum doesn't need special post-processing, so this is a no-op. 881 */ 882 @Override 883 protected void afterStatementParsed(TCustomSqlStatement stmt) { 884 // No special post-processing needed for Greenplum 885 } 886 887 /** 888 * Handle error recovery for CREATE TABLE statements. 889 * <p> 890 * This attempts to recover from syntax errors in table properties 891 * by marking unparseable tokens as SQL*Plus commands and retrying. 892 * <p> 893 * Migrated from TGSqlParser.doparse() lines 16914-16971 894 */ 895 private void handleCreateTableErrorRecovery(TCustomSqlStatement stmt) { 896 // Only handle CREATE TABLE and CREATE INDEX (but not for Couchbase) 897 if (!((stmt.sqlstatementtype == ESqlStatementType.sstcreatetable) 898 || ((stmt.sqlstatementtype == ESqlStatementType.sstcreateindex) && (vendor != EDbVendor.dbvcouchbase)))) { 899 return; 900 } 901 902 // Don't recover if strict parsing is enabled 903 if (TBaseType.c_createTableStrictParsing) { 904 return; 905 } 906 907 // only parse main body of create table, 908 // ignore vendor-specific table properties after closing parenthesis 909 TCustomSqlStatement errorSqlStatement = stmt; 910 911 int nested = 0; 912 boolean isIgnore = false, isFoundIgnoreToken = false; 913 TSourceToken firstIgnoreToken = null; 914 915 for (int k = 0; k < errorSqlStatement.sourcetokenlist.size(); k++) { 916 TSourceToken st = errorSqlStatement.sourcetokenlist.get(k); 917 918 if (isIgnore) { 919 if (st.issolidtoken() && (st.tokencode != ';')) { 920 isFoundIgnoreToken = true; 921 if (firstIgnoreToken == null) { 922 firstIgnoreToken = st; 923 } 924 } 925 if (st.tokencode != ';') { 926 st.tokencode = TBaseType.sqlpluscmd; 927 } 928 continue; 929 } 930 931 if (st.tokencode == (int) ')') { 932 nested--; 933 if (nested == 0) { 934 //let's check if next token is: AS ( SELECT 935 boolean isSelect = false; 936 TSourceToken st1 = st.searchToken(TBaseType.rrw_as, 1); 937 if (st1 != null) { 938 TSourceToken st2 = st.searchToken((int) '(', 2); 939 if (st2 != null) { 940 TSourceToken st3 = st.searchToken(TBaseType.rrw_select, 3); 941 isSelect = (st3 != null); 942 } 943 } 944 if (!isSelect) isIgnore = true; 945 } 946 } 947 948 if ((st.tokencode == (int) '(') || (st.tokencode == TBaseType.left_parenthesis_2)) { 949 nested++; 950 } 951 } 952 953 // For Oracle, check if the first ignored token is a valid table property 954 if ((vendor == EDbVendor.dbvoracle) && ((firstIgnoreToken != null) && (!TBaseType.searchOracleTablePros(firstIgnoreToken.toString())))) { 955 // if it is not a valid Oracle table properties option, let raise the error. 956 isFoundIgnoreToken = false; 957 } 958 959 if (isFoundIgnoreToken) { 960 errorSqlStatement.clearError(); 961 stmt.parsestatement(null, false, parserContext.isOnlyNeedRawParseTree()); 962 } 963 } 964}