001package gudusoft.gsqlparser.parser; 002 003import gudusoft.gsqlparser.EDbVendor; 004import gudusoft.gsqlparser.EErrorType; 005import gudusoft.gsqlparser.EFindSqlStateType; 006import gudusoft.gsqlparser.ESqlStatementType; 007import gudusoft.gsqlparser.ETokenStatus; 008import gudusoft.gsqlparser.ETokenType; 009import gudusoft.gsqlparser.TBaseType; 010import gudusoft.gsqlparser.TCustomLexer; 011import gudusoft.gsqlparser.TCustomParser; 012import gudusoft.gsqlparser.TCustomSqlStatement; 013import gudusoft.gsqlparser.TLexerEdb; 014import gudusoft.gsqlparser.TParserEdb; 015import gudusoft.gsqlparser.TSourceToken; 016import gudusoft.gsqlparser.TSourceTokenList; 017import gudusoft.gsqlparser.TStatementList; 018import gudusoft.gsqlparser.TSyntaxError; 019import gudusoft.gsqlparser.TLog; 020import gudusoft.gsqlparser.compiler.TASTEvaluator; 021import gudusoft.gsqlparser.compiler.TContext; 022import gudusoft.gsqlparser.compiler.TFrame; 023import gudusoft.gsqlparser.compiler.TGlobalScope; 024import gudusoft.gsqlparser.resolver.TSQLResolver; 025import gudusoft.gsqlparser.sqlcmds.ISqlCmds; 026import gudusoft.gsqlparser.sqlcmds.SqlCmdsFactory; 027import gudusoft.gsqlparser.sqlenv.TSQLEnv; 028import gudusoft.gsqlparser.stmt.TCommonBlock; 029import gudusoft.gsqlparser.stmt.TCreateTableSqlStatement; 030import gudusoft.gsqlparser.stmt.TRoutine; 031import gudusoft.gsqlparser.stmt.TUnknownSqlStatement; 032import gudusoft.gsqlparser.stmt.oracle.TPlsqlCreatePackage; 033import gudusoft.gsqlparser.stmt.oracle.TSqlplusCmdStatement; 034 035import java.util.Stack; 036 037/** 038 * Edb SQL parser implementation. 039 * 040 * <p>This parser handles Edb-specific SQL syntax including: 041 * <ul> 042 * <li>PostgreSQL-based SQL syntax with Oracle compatibility</li> 043 * <li>Stored procedures and functions (Oracle-style and PostgreSQL-style)</li> 044 * <li>Package specifications and bodies</li> 045 * <li>SQL*Plus-like commands</li> 046 * <li>Dynamic delimiter support</li> 047 * </ul> 048 * 049 * <p><b>Implementation Status:</b> MIGRATED 050 * <ul> 051 * <li><b>Completed:</b> Migrated from TGSqlParser to AbstractSqlParser</li> 052 * <li><b>Current:</b> Fully self-contained Edb parser</li> 053 * </ul> 054 * 055 * <p><b>Design Notes:</b> 056 * <ul> 057 * <li>Extends {@link AbstractSqlParser} using template method pattern</li> 058 * <li>Uses single parser: {@link TParserEdb}</li> 059 * <li>Primary delimiter: semicolon (;)</li> 060 * <li>Supports both Oracle-style and PostgreSQL-style routines</li> 061 * <li>Handles package specifications and bodies with Edb-specific state tracking</li> 062 * </ul> 063 * 064 * @see SqlParser 065 * @see AbstractSqlParser 066 * @see TLexerEdb 067 * @see TParserEdb 068 * @since 3.2.0.0 069 */ 070public class EdbSqlParser extends AbstractSqlParser { 071 072 // ========== Lexer and Parser ========== 073 074 /** The Edb lexer used for tokenization */ 075 public TLexerEdb flexer; 076 077 /** The Edb parser used for parsing */ 078 private TParserEdb fparser; 079 080 // ========== Statement Extraction State ========== 081 082 /** Current statement being built during raw extraction */ 083 private TCustomSqlStatement gcurrentsqlstatement; 084 085 /** Flag to indicate if this is an Oracle-style routine */ 086 private boolean isOracleStyleRoutine = true; 087 088 /** Procedure statement reference for tracking */ 089 private TSourceToken stProcedure = null; 090 091 /** Function statement reference for tracking */ 092 private TSourceToken stFunction = null; 093 094 /** 095 * Construct Edb SQL parser. 096 * <p> 097 * Configures the parser for Edb database with semicolon (;) as the default delimiter. 098 */ 099 public EdbSqlParser() { 100 super(EDbVendor.dbvedb); 101 this.delimiterChar = ';'; 102 this.defaultDelimiterStr = ";"; 103 104 // Create lexer once - will be reused for all parsing operations 105 this.flexer = new TLexerEdb(); 106 this.flexer.delimiterchar = this.delimiterChar; 107 this.flexer.defaultDelimiterStr = this.defaultDelimiterStr; 108 109 // Set parent's lexer reference for shared tokenization logic 110 this.lexer = this.flexer; 111 112 // Create parser once - will be reused for all parsing operations 113 this.fparser = new TParserEdb(null); 114 this.fparser.lexer = this.flexer; 115 } 116 117 // ========== Abstract Method Implementations ========== 118 119 @Override 120 protected TCustomLexer getLexer(ParserContext context) { 121 return this.flexer; 122 } 123 124 @Override 125 protected TCustomParser getParser(ParserContext context, TSourceTokenList tokens) { 126 this.fparser.sourcetokenlist = tokens; 127 return this.fparser; 128 } 129 130 @Override 131 protected TCustomParser getSecondaryParser(ParserContext context, TSourceTokenList tokens) { 132 // Edb does not have a secondary parser 133 return null; 134 } 135 136 // ========== Phase 2: Tokenization (Hook Pattern) ========== 137 138 @Override 139 protected void tokenizeVendorSql() { 140 doedbtexttotokenlist(); 141 } 142 143 /** 144 * Tokenize Edb SQL text into source tokens. 145 * <p> 146 * Migrated from TGSqlParser.doedbtexttotokenlist() (lines 2899-3026). 147 * <p> 148 * Handles: 149 * <ul> 150 * <li>SQL*Plus-like command detection</li> 151 * <li>Forward slash disambiguation (division vs SQL*Plus command)</li> 152 * <li>PostgreSQL-style keyword handling (INNER, NOT DEFERRABLE, etc.)</li> 153 * </ul> 154 */ 155 private void doedbtexttotokenlist() { 156 boolean insqlpluscmd = false; 157 boolean isvalidplace = true; 158 boolean waitingreturnforfloatdiv = false; 159 boolean waitingreturnforsemicolon = false; 160 boolean continuesqlplusatnewline = false; 161 162 TSourceToken lct = null, prevst = null; 163 164 TSourceToken asourcetoken, lcprevst; 165 int yychar; 166 167 asourcetoken = getanewsourcetoken(); 168 if (asourcetoken == null) return; 169 yychar = asourcetoken.tokencode; 170 171 while (yychar > 0) { 172 sourcetokenlist.add(asourcetoken); 173 switch (yychar) { 174 case TBaseType.cmtdoublehyphen: 175 case TBaseType.cmtslashstar: 176 case TBaseType.lexspace: { 177 if (insqlpluscmd) { 178 asourcetoken.insqlpluscmd = true; 179 } 180 break; 181 } 182 case TBaseType.lexnewline: { 183 if (insqlpluscmd) { 184 insqlpluscmd = false; 185 isvalidplace = true; 186 187 if (continuesqlplusatnewline) { 188 insqlpluscmd = true; 189 isvalidplace = false; 190 asourcetoken.insqlpluscmd = true; 191 } 192 } 193 194 if (waitingreturnforsemicolon) { 195 isvalidplace = true; 196 } 197 if (waitingreturnforfloatdiv) { 198 isvalidplace = true; 199 lct.tokencode = TBaseType.sqlpluscmd; 200 if (lct.tokentype != ETokenType.ttslash) { 201 lct.tokentype = ETokenType.ttsqlpluscmd; 202 } 203 } 204 flexer.insqlpluscmd = insqlpluscmd; 205 break; 206 } //case newline 207 default: { 208 //solid token 209 continuesqlplusatnewline = false; 210 waitingreturnforsemicolon = false; 211 waitingreturnforfloatdiv = false; 212 if (insqlpluscmd) { 213 asourcetoken.insqlpluscmd = true; 214 if (asourcetoken.toString().equalsIgnoreCase("-")) { 215 continuesqlplusatnewline = true; 216 } 217 } else { 218 if (asourcetoken.tokentype == ETokenType.ttsemicolon) { 219 waitingreturnforsemicolon = true; 220 } 221 if ((asourcetoken.tokentype == ETokenType.ttslash) 222 && (isvalidplace || (isValidPlaceForDivToSqlplusCmd(sourcetokenlist, asourcetoken.posinlist)))) { 223 lct = asourcetoken; 224 waitingreturnforfloatdiv = true; 225 } 226 if ((isvalidplace) && isvalidsqlpluscmdInPostgresql(asourcetoken.toString())) { 227 asourcetoken.tokencode = TBaseType.sqlpluscmd; 228 if (asourcetoken.tokentype != ETokenType.ttslash) { 229 asourcetoken.tokentype = ETokenType.ttsqlpluscmd; 230 } 231 insqlpluscmd = true; 232 flexer.insqlpluscmd = insqlpluscmd; 233 } 234 } 235 isvalidplace = false; 236 237 // the inner keyword token should be converted to TBaseType.ident when 238 // next solid token is not join 239 240 if (prevst != null) { 241 if (prevst.tokencode == TBaseType.rrw_inner)//flexer.getkeywordvalue("INNER")) 242 { 243 if (asourcetoken.tokencode != flexer.getkeywordvalue("JOIN")) { 244 prevst.tokencode = TBaseType.ident; 245 } 246 } 247 248 249 if ((prevst.tokencode == TBaseType.rrw_not) 250 && (asourcetoken.tokencode == flexer.getkeywordvalue("DEFERRABLE"))) { 251 prevst.tokencode = flexer.getkeywordvalue("NOT_DEFERRABLE"); 252 asourcetoken.tokenstatus = ETokenStatus.tsignorebyyacc; 253 } 254 } 255 256 if (asourcetoken.tokencode == TBaseType.rrw_inner) { 257 prevst = asourcetoken; 258 } else if (asourcetoken.tokencode == TBaseType.rrw_not) { 259 prevst = asourcetoken; 260 } else { 261 prevst = null; 262 } 263 264 if ((asourcetoken.tokencode == flexer.getkeywordvalue("DIRECT_LOAD")) 265 || (asourcetoken.tokencode == flexer.getkeywordvalue("ALL"))) { 266 // RW_COMPRESS RW_FOR RW_ALL RW_OPERATIONS 267 // RW_COMPRESS RW_FOR RW_DIRECT_LOAD RW_OPERATIONS 268 // change rw_for to TBaseType.rw_for1, it conflicts with compress for update in create materialized view 269 270 lcprevst = getprevsolidtoken(asourcetoken); 271 if (lcprevst != null) { 272 if (lcprevst.tokencode == TBaseType.rrw_for) 273 lcprevst.tokencode = TBaseType.rw_for1; 274 } 275 } 276 277 if (asourcetoken.tokencode == TBaseType.rrw_dense_rank) { 278 //keep keyword can be column alias, make keep in keep_denserankclause as a different token code 279 TSourceToken stKeep = asourcetoken.searchToken(TBaseType.rrw_keep, -2); 280 if (stKeep != null) { 281 stKeep.tokencode = TBaseType.rrw_keep_before_dense_rank; 282 } 283 } 284 285 if ((asourcetoken.tokencode == TBaseType.rrw_postgresql_rowtype) 286 || (asourcetoken.tokencode == TBaseType.rrw_postgresql_type)) { 287 TSourceToken stPercent = asourcetoken.searchToken('%', -1); 288 if (stPercent != null) { 289 stPercent.tokencode = TBaseType.rowtype_operator; 290 } 291 } 292 293 if (asourcetoken.tokencode == TBaseType.JSON_EXIST) { 294 TSourceToken stPercent = asourcetoken.searchToken('=', -1); 295 if (stPercent != null) { // = ?, ? after = should not be treated as json exist operator 296 asourcetoken.tokencode = TBaseType.ident; 297 } 298 } 299 300 if (asourcetoken.tokencode == TBaseType.rrw_update) { // on conflict do update in insert statement 301 TSourceToken stDo = asourcetoken.searchToken(TBaseType.rrw_do, -1); 302 if (stDo != null) { 303 asourcetoken.tokencode = TBaseType.rrw_postgresql_do_update; 304 } 305 } 306 307 break; 308 } 309 } 310 asourcetoken = getanewsourcetoken(); 311 if (asourcetoken != null) { 312 yychar = asourcetoken.tokencode; 313 } else { 314 yychar = 0; 315 316 if (waitingreturnforfloatdiv) { 317 // / at the end of line treat as sqlplus command 318 lct.tokencode = TBaseType.sqlpluscmd; 319 if (lct.tokentype != ETokenType.ttslash) { 320 lct.tokentype = ETokenType.ttsqlpluscmd; 321 } 322 } 323 } 324 } 325 } 326 327 /** 328 * Check if a valid place for division operator to be treated as SQL*Plus command. 329 * <p> 330 * Migrated from TGSqlParser.IsValidPlaceForDivToSqlplusCmd() (lines 2641-2655). 331 */ 332 private boolean isValidPlaceForDivToSqlplusCmd(TSourceTokenList pstlist, int pPos) { 333 boolean ret = false; 334 335 if ((pPos <= 0) || (pPos > pstlist.size() - 1)) return ret; 336 //token directly before div must be ttreturn without space appending it 337 TSourceToken lcst = pstlist.get(pPos - 1); 338 if (lcst.tokentype != ETokenType.ttreturn) { 339 return ret; 340 } 341 342 if (!(lcst.getAstext().charAt(lcst.getAstext().length() - 1) == ' ')) { 343 ret = true; 344 } 345 346 return ret; 347 } 348 349 /** 350 * Check if a string is a valid SQL*Plus command in PostgreSQL/Edb context. 351 * <p> 352 * Migrated from TGSqlParser.isvalidsqlpluscmdInPostgresql() (lines 2658-2660). 353 * <p> 354 * Note: This is a placeholder function that always returns false. 355 */ 356 private boolean isvalidsqlpluscmdInPostgresql(String astr) { 357 return false; 358 } 359 360 /** 361 * Get the previous solid token before the given token. 362 * <p> 363 * Migrated from TGSqlParser.getprevsolidtoken() (lines 2752-2773). 364 */ 365 private TSourceToken getprevsolidtoken(TSourceToken ptoken) { 366 TSourceToken ret = null; 367 TSourceTokenList lctokenlist = ptoken.container; 368 if (lctokenlist != null) { 369 if ((ptoken.posinlist > 0) && (lctokenlist.size() > ptoken.posinlist - 1)) { 370 if (!((lctokenlist.get(ptoken.posinlist - 1).tokentype == ETokenType.ttwhitespace) 371 || (lctokenlist.get(ptoken.posinlist - 1).tokentype == ETokenType.ttreturn) 372 || (lctokenlist.get(ptoken.posinlist - 1).tokentype == ETokenType.ttsimplecomment) 373 || (lctokenlist.get(ptoken.posinlist - 1).tokentype == ETokenType.ttbracketedcomment) 374 )) { 375 ret = lctokenlist.get(ptoken.posinlist - 1); 376 } else { 377 ret = getprevsolidtoken(lctokenlist.get(ptoken.posinlist - 1)); 378 } 379 } 380 } 381 return ret; 382 } 383 384 /** 385 * Find package name in Edb package statements. 386 * <p> 387 * Migrated from TGSqlParser.findPkgName() (lines 7437-7485). 388 * <p> 389 * @param st Starting token 390 * @param mode 1=package spec, 2=package body, 3=package name after END 391 * @return Package name string 392 */ 393 private String findPkgName(TSourceToken st, int mode) { 394 String pkgName = ""; 395 396 boolean inpkgname = false; 397 if (mode == 3) inpkgname = true; 398 int MAX_TRY_TOKENS = 20; 399 int tryTokens = 0; 400 while (st != null) { 401 402 if (st.isnonsolidtoken()) { 403 st = st.getNextTokenInChain(); 404 continue; 405 } 406 tryTokens++; 407 408 if ((mode == 1) && (st.tokencode == TBaseType.rrw_package)) { 409 inpkgname = true; 410 st = st.getNextTokenInChain(); 411 continue; 412 } else if ((mode == 2) && (st.tokencode == TBaseType.rrw_body)) { 413 inpkgname = true; 414 st = st.getNextTokenInChain(); 415 continue; 416 } 417 418 if (((mode == 1) || (mode == 2)) 419 && ((st.tokencode == TBaseType.rrw_is) || (st.tokencode == TBaseType.rrw_as)) 420 ) { 421 break; 422 } else if ((mode == 3) && (st.tokencode == ';')) { 423 break; 424 } 425 426 if (!inpkgname) { 427 st = st.getNextTokenInChain(); 428 continue; 429 } 430 pkgName = pkgName + st.toString(); 431 st = st.getNextTokenInChain(); 432 433 if (tryTokens > MAX_TRY_TOKENS) { 434 TBaseType.log(String.format("Package name is not found in create package"), TLog.ERROR, st); 435 break; 436 } 437 } 438 439 return pkgName; 440 } 441 442 // ========== Phase 3: Raw Statement Extraction (Hook Pattern) ========== 443 444 @Override 445 protected void setupVendorParsersForExtraction() { 446 this.fparser.sqlcmds = this.sqlcmds; 447 this.fparser.sourcetokenlist = this.sourcetokenlist; 448 } 449 450 @Override 451 protected void extractVendorRawStatements(SqlParseResult.Builder builder) { 452 doedbgetrawsqlstatements(builder); 453 } 454 455 /** 456 * Extract raw SQL statements from token list. 457 * <p> 458 * Migrated from TGSqlParser.doedbgetrawsqlstatements() (lines 7495-8049). 459 * <p> 460 * This method implements a state machine to identify statement boundaries: 461 * <ul> 462 * <li>Regular SQL statements terminated by semicolon</li> 463 * <li>Oracle-style stored procedures with BEGIN/END pairs</li> 464 * <li>PostgreSQL-style procedures with $$ delimiters</li> 465 * <li>Package specifications and bodies with Edb-specific state tracking</li> 466 * <li>Slash (/) and period (.) terminators for procedural blocks</li> 467 * </ul> 468 */ 469 private void doedbgetrawsqlstatements(SqlParseResult.Builder builder) { 470 int waitingEnd = 0; 471 boolean foundEnd = false; 472 int declarePending = 0; // tracks nested IS/AS/DECLARE without matching BEGIN 473 isOracleStyleRoutine = true; 474 stProcedure = null; 475 stFunction = null; 476 477 if (TBaseType.assigned(sqlstatements)) sqlstatements.clear(); 478 if (!TBaseType.assigned(sourcetokenlist)) { 479 builder.errorCode(-1); 480 return; 481 } 482 483 gcurrentsqlstatement = null; 484 EFindSqlStateType gst = EFindSqlStateType.stnormal; 485 TSourceToken lcprevsolidtoken = null, ast = null; 486 487 // Handle single PL block mode 488 if (parserContext != null && parserContext.isSinglePLBlock()) { 489 gcurrentsqlstatement = new TCommonBlock(vendor); 490 } 491 492 for (int i = 0; i < sourcetokenlist.size(); i++) { 493 494 if ((ast != null) && (ast.issolidtoken())) 495 lcprevsolidtoken = ast; 496 497 ast = sourcetokenlist.get(i); 498 sourcetokenlist.curpos = i; 499 500 if (parserContext != null && parserContext.isSinglePLBlock()) { 501 gcurrentsqlstatement.sourcetokenlist.add(ast); 502 continue; 503 } 504 505 // Token preprocessing: Adjust token codes based on context 506 if (ast.tokencode == TBaseType.JSON_EXIST) { 507 TSourceToken stConstant = ast.searchToken(TBaseType.sconst, 1); 508 if (stConstant == null) { 509 ast.tokencode = TBaseType.ident; 510 } 511 } else if (ast.tokencode == TBaseType.rrw_postgresql_POSITION) { 512 TSourceToken st1 = ast.nextSolidToken(); 513 if (st1 != null) { 514 if (st1.tokencode == '(') { 515 ast.tokencode = TBaseType.rrw_postgresql_POSITION_FUNCTION; 516 } 517 } 518 } else if (ast.tokencode == TBaseType.rrw_postgresql_ordinality) { 519 TSourceToken lcprevst = getprevsolidtoken(ast); 520 521 if (lcprevst != null) { 522 if (lcprevst.tokencode == TBaseType.rrw_with) { 523 TSourceToken lcnextst = ast.nextSolidToken(); 524 if ((lcnextst != null) && (lcnextst.tokencode == TBaseType.rrw_as)) { 525 // with ordinality as (select 1 as x) select * from ordinality; 526 // don't change with to rrw_postgresql_with_lookahead 527 } else { 528 lcprevst.tokencode = TBaseType.rrw_postgresql_with_lookahead; 529 } 530 531 } 532 } 533 } else if ((ast.tokencode == TBaseType.rrw_postgresql_filter) 534 || (ast.tokencode == TBaseType.GAUSSDB_TO_BINARY_DOUBLE) 535 || (ast.tokencode == TBaseType.GAUSSDB_TO_BINARY_FLOAT) 536 || (ast.tokencode == TBaseType.GAUSSDB_TO_NUMBER) 537 || (ast.tokencode == TBaseType.GAUSSDB_TO_DATE) 538 || (ast.tokencode == TBaseType.GAUSSDB_TO_TIMESTAMP) 539 || (ast.tokencode == TBaseType.GAUSSDB_TO_TIMESTAMP_TZ) 540 ) { 541 TSourceToken st1 = ast.nextSolidToken(); 542 if (st1 != null) { 543 if (st1.tokencode == '(') { 544 // Keep as function 545 } else { 546 ast.tokencode = TBaseType.ident; 547 } 548 } 549 } else if (ast.tokencode == TBaseType.rrw_postgresql_jsonb) { 550 TSourceToken st1 = ast.nextSolidToken(); 551 if (st1 != null) { 552 if (st1.tokencode == '?') { 553 st1.tokencode = TBaseType.OP_JSONB_QUESTION; 554 } 555 } 556 } else if (ast.tokencode == TBaseType.GAUSSDB_TO_NUMBER) { 557 TSourceToken st1 = ast.searchToken('(', 1); 558 if (st1 == null) { 559 ast.tokencode = TBaseType.ident; 560 } 561 } else if (ast.tokencode == '?') { 562 TSourceToken st1 = ast.nextSolidToken(); 563 if (st1 != null) { 564 if (st1.tokencode == TBaseType.sconst) { 565 ast.tokencode = TBaseType.OP_JSONB_QUESTION; 566 } 567 } 568 } else if (ast.tokencode == TBaseType.rrw_values) { 569 TSourceToken stParen = ast.searchToken('(', 1); 570 if (stParen != null) { 571 TSourceToken stInsert = ast.searchToken(TBaseType.rrw_insert, -ast.posinlist); 572 if (stInsert != null) { 573 TSourceToken stSemiColon = ast.searchToken(';', -ast.posinlist); 574 if ((stSemiColon != null) && (stSemiColon.posinlist > stInsert.posinlist)) { 575 // INSERT INTO test values (16,1), (8,2), (4,4), (2,0), (97, 16); 576 // VALUES (1); 577 // don't treat values(1) as insert values 578 579 } else { 580 TSourceToken stFrom = ast.searchToken(TBaseType.rrw_from, -ast.posinlist); 581 if ((stFrom != null) && (stFrom.posinlist > stInsert.posinlist)) { 582 // Don't treat values after from keyword as an insert values 583 } else { 584 ast.tokencode = TBaseType.rrw_postgresql_insert_values; 585 } 586 } 587 } 588 } 589 } 590 591 switch (gst) { 592 case sterror: { 593 if (ast.tokentype == ETokenType.ttsemicolon) { 594 appendToken(gcurrentsqlstatement, ast); 595 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder); 596 gst = EFindSqlStateType.stnormal; 597 } else { 598 appendToken(gcurrentsqlstatement, ast); 599 } 600 break; 601 } //sterror 602 603 case stnormal: { 604 if ((ast.tokencode == TBaseType.cmtdoublehyphen) 605 || (ast.tokencode == TBaseType.cmtslashstar) 606 || (ast.tokencode == TBaseType.lexspace) 607 || (ast.tokencode == TBaseType.lexnewline) 608 || (ast.tokentype == ETokenType.ttsemicolon)) { 609 if (gcurrentsqlstatement != null) { 610 appendToken(gcurrentsqlstatement, ast); 611 } 612 613 if ((lcprevsolidtoken != null) && (ast.tokentype == ETokenType.ttsemicolon)) { 614 if (lcprevsolidtoken.tokentype == ETokenType.ttsemicolon) { 615 // ;;;; continuous semicolon, treat it as comment 616 ast.tokentype = ETokenType.ttsimplecomment; 617 ast.tokencode = TBaseType.cmtdoublehyphen; 618 } 619 } 620 621 continue; 622 } 623 624 if (ast.tokencode == TBaseType.sqlpluscmd) { 625 gst = EFindSqlStateType.stsqlplus; 626 gcurrentsqlstatement = new TSqlplusCmdStatement(vendor); 627 appendToken(gcurrentsqlstatement, ast); 628 continue; 629 } 630 631 // find a token to start sql or plsql mode 632 gcurrentsqlstatement = sqlcmds.issql(ast, gst, gcurrentsqlstatement); 633 634 if (gcurrentsqlstatement != null) { 635 declarePending = 0; 636 if (gcurrentsqlstatement.isGaussDBStoredProcedure()) { 637 appendToken(gcurrentsqlstatement, ast); 638 if (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstplsql_createpackage) { 639 gst = EFindSqlStateType.stGaussDBPkgSpec; 640 TPlsqlCreatePackage pkgspec = (TPlsqlCreatePackage) gcurrentsqlstatement; 641 pkgspec.setPackageNameStr(findPkgName(ast.getNextTokenInChain(), 1)); 642 } else if (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstoraclecreatepackagebody) { 643 gst = EFindSqlStateType.stGaussDBPkgBody; 644 TPlsqlCreatePackage pkgspec = (TPlsqlCreatePackage) gcurrentsqlstatement; 645 pkgspec.setPackageNameStr(findPkgName(ast.getNextTokenInChain(), 2)); 646 } else { 647 gst = EFindSqlStateType.ststoredprocedure; 648 649 foundEnd = false; 650 if ((ast.tokencode == TBaseType.rrw_begin) 651 || (ast.tokencode == TBaseType.rrw_package) 652 || (ast.searchToken(TBaseType.rrw_package, 4) != null) 653 ) { 654 waitingEnd = 1; 655 } else if (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstplsql_createtypebody) { 656 // CREATE TYPE BODY has AS...END wrapper around member definitions 657 // each member has its own BEGIN...END, so we need an extra nesting level 658 waitingEnd = 1; 659 } else if (ast.tokencode == TBaseType.rrw_declare) { 660 declarePending = 1; 661 } 662 663 if (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstcreateprocedure) { 664 // create procedure, create or replace procedure 665 stProcedure = ast.searchToken(TBaseType.rrw_procedure, 3); 666 } else if (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstcreatefunction) { 667 // create function, create or replace function 668 stFunction = ast.searchToken(TBaseType.rrw_function, 3); 669 } 670 } 671 } else { 672 gst = EFindSqlStateType.stsql; 673 appendToken(gcurrentsqlstatement, ast); 674 } 675 } else { 676 //error token found 677 678 this.syntaxErrors.add(new TSyntaxError(ast.getAstext(), ast.lineNo, (ast.columnNo < 0 ? 0 : ast.columnNo) 679 , "Error when tokenlize", EErrorType.spwarning, TBaseType.MSG_WARNING_ERROR_WHEN_TOKENIZE, null, ast.posinlist)); 680 681 ast.tokentype = ETokenType.tttokenlizererrortoken; 682 gst = EFindSqlStateType.sterror; 683 684 gcurrentsqlstatement = new TUnknownSqlStatement(vendor); 685 gcurrentsqlstatement.sqlstatementtype = ESqlStatementType.sstinvalid; 686 appendToken(gcurrentsqlstatement, ast); 687 688 } 689 690 break; 691 } // stnormal 692 693 case stsqlplus: { 694 if (ast.insqlpluscmd) { 695 appendToken(gcurrentsqlstatement, ast); 696 } else { 697 gst = EFindSqlStateType.stnormal; //this token must be newline, 698 appendToken(gcurrentsqlstatement, ast); // so add it here 699 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder); 700 } 701 702 break; 703 }//case stsqlplus 704 705 case stsql: { 706 if (ast.tokentype == ETokenType.ttsemicolon) { 707 gst = EFindSqlStateType.stnormal; 708 appendToken(gcurrentsqlstatement, ast); 709 gcurrentsqlstatement.semicolonended = ast; 710 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder); 711 continue; 712 } 713 714 if (sourcetokenlist.sqlplusaftercurtoken()) { //most probably is / cmd 715 // In Edb, if / appears in regular SQL (non-stored procedure) and is identified as sqlplus, 716 // it should be a false positive. For example, in division expressions. 717 // We need to restore / to a regular character / 718 TSourceToken st = ast.nextSolidToken(); 719 if (st.tokentype == ETokenType.ttslash) { 720 st.tokencode = '/'; 721 } else { // Non-/ sqlplus commands are still treated as sqlplus, but whether Edb has Oracle-like sqlplus needs further verification 722 gst = EFindSqlStateType.stnormal; 723 appendToken(gcurrentsqlstatement, ast); 724 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder); 725 continue; 726 } 727 } 728 729 if (ast.tokencode == TBaseType.cmtdoublehyphen) { 730 if (ast.toString().trim().endsWith(TBaseType.sqlflow_stmt_delimiter_str)) { // -- sqlflow-delimiter 731 gst = EFindSqlStateType.stnormal; 732 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder); 733 continue; 734 } 735 } 736 737 appendToken(gcurrentsqlstatement, ast); 738 break; 739 }//case stsql 740 741 case ststoredprocedure: { 742 if (ast.tokencode == TBaseType.rrw_postgresql_function_delimiter) { 743 appendToken(gcurrentsqlstatement, ast); 744 isOracleStyleRoutine = false; 745 gst = EFindSqlStateType.ststoredprocedurePgStartBody; 746 continue; 747 } 748 749 if (ast.tokencode == TBaseType.rrw_postgresql_language) { 750 // check next token which is the language used by this stored procedure 751 TSourceToken nextSt = ast.nextSolidToken(); 752 if (nextSt != null) { 753 if (gcurrentsqlstatement instanceof TRoutine) { // can be TCreateProcedureStmt or TCreateFunctionStmt 754 TRoutine p = (TRoutine) gcurrentsqlstatement; 755 p.setRoutineLanguage(nextSt.toString()); 756 isOracleStyleRoutine = false; 757 } 758 } 759 } 760 761 if ((ast.tokentype == ETokenType.ttsemicolon) && (waitingEnd == 0) && (declarePending == 0)) { 762 gst = EFindSqlStateType.stnormal; 763 appendToken(gcurrentsqlstatement, ast); 764 gcurrentsqlstatement.semicolonended = ast; 765 766 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder); 767 continue; 768 } 769 770 // Compound trigger: COMPOUND TRIGGER adds an extra nesting level 771 // because the body has internal END...; for each timing point section 772 // but the outer END trigger_name; is the real end of the trigger. 773 if (ast.toString().equalsIgnoreCase("COMPOUND")) { 774 TSourceToken nextSt2 = ast.nextSolidToken(); 775 if (nextSt2 != null && nextSt2.toString().equalsIgnoreCase("TRIGGER")) { 776 waitingEnd++; 777 } 778 } 779 780 if ((ast.tokencode == TBaseType.rrw_begin) 781 ) { 782 waitingEnd++; 783 if (declarePending > 0) { 784 declarePending--; 785 } 786 } else if ( 787 (ast.tokencode == TBaseType.rrw_declare) 788 || (ast.tokencode == TBaseType.rrw_as) || (ast.tokencode == TBaseType.rrw_is) 789 ) { 790 TSourceToken next = ast.nextSolidToken(); 791 if ((next != null) && ((next.tokencode == TBaseType.sconst) || (next.tokencode == TBaseType.GAUSSDB_NULL))) { 792 // CREATE FUNCTION func_add_sql(integer, integer) RETURNS integer 793 // AS 'select $1 + $2;' 794 // LANGUAGE SQL 795 // IMMUTABLE 796 // RETURNS NULL ON NULL INPUT; 797 } else { 798 declarePending++; 799 } 800 } else if ((ast.tokencode == TBaseType.rrw_if) 801 ) { 802 if (ast.searchToken(TBaseType.rrw_end, -1) == null) { 803 //this is not if after END 804 waitingEnd++; 805 } 806 } else if ((ast.tokencode == TBaseType.rrw_case) 807 ) { 808 if (ast.searchToken(TBaseType.rrw_end, -1) == null) { 809 //this is not case after END 810 waitingEnd++; 811 } 812 } else if ((ast.tokencode == TBaseType.rrw_loop) 813 ) { 814 if (ast.searchToken(TBaseType.rrw_end, -1) == null) { 815 //this is not loop after END 816 waitingEnd++; 817 } 818 } else if (ast.tokencode == TBaseType.rrw_end) { 819 foundEnd = true; 820 waitingEnd--; 821 if (waitingEnd < 0) { 822 waitingEnd = 0; 823 } 824 } 825 826 if ((ast.tokentype == ETokenType.ttslash) && (ast.tokencode == TBaseType.sqlpluscmd)) //and (prevst.NewlineIsLastTokenInTailerToken)) then 827 { 828 // TPlsqlStatementParse(asqlstatement).TerminatorToken := ast; 829 ast.tokenstatus = ETokenStatus.tsignorebyyacc; 830 gst = EFindSqlStateType.stnormal; 831 832 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder); 833 834 //make / a sqlplus cmd 835 gcurrentsqlstatement = new TSqlplusCmdStatement(vendor); 836 appendToken(gcurrentsqlstatement, ast); 837 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder); 838 } else if ((ast.tokentype == ETokenType.ttperiod) && (sourcetokenlist.returnaftercurtoken(false)) && (sourcetokenlist.returnbeforecurtoken(false))) { // single dot at a separate line 839 ast.tokenstatus = ETokenStatus.tsignorebyyacc; 840 gst = EFindSqlStateType.stnormal; 841 842 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder); 843 844 //make ttperiod a sqlplus cmd 845 gcurrentsqlstatement = new TSqlplusCmdStatement(vendor); 846 appendToken(gcurrentsqlstatement, ast); 847 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder); 848 } else { 849 appendToken(gcurrentsqlstatement, ast); 850 if ((ast.tokentype == ETokenType.ttsemicolon) && (waitingEnd == 0) && (declarePending == 0) 851 && (foundEnd) 852 ) { 853 gst = EFindSqlStateType.stnormal; 854 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder); 855 } 856 } 857 858 if (ast.tokencode == TBaseType.sqlpluscmd) { 859 //change tokencode back to keyword or TBaseType.ident, because sqlplus cmd 860 //in a sql statement(almost is plsql block) is not really a sqlplus cmd 861 int m = flexer.getkeywordvalue(ast.getAstext()); 862 if (m != 0) { 863 ast.tokencode = m; 864 } else { 865 ast.tokencode = TBaseType.ident; 866 } 867 } 868 869 if ((gst == EFindSqlStateType.ststoredprocedure) && (ast.tokencode == TBaseType.cmtdoublehyphen)) { 870 if (ast.toString().trim().endsWith(TBaseType.sqlflow_stmt_delimiter_str)) { // -- sqlflow-delimiter 871 gst = EFindSqlStateType.stnormal; 872 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder); 873 } 874 } 875 876 break; 877 } //ststoredprocedure 878 879 case ststoredprocedurePgStartBody: { 880 appendToken(gcurrentsqlstatement, ast); 881 882 if (ast.tokencode == TBaseType.rrw_postgresql_function_delimiter) { 883 gst = EFindSqlStateType.ststoredprocedurePgEndBody; 884 continue; 885 } 886 887 break; 888 } 889 890 case ststoredprocedurePgEndBody: { 891 892 if (ast.tokentype == ETokenType.ttsemicolon) { 893 gst = EFindSqlStateType.stnormal; 894 appendToken(gcurrentsqlstatement, ast); 895 gcurrentsqlstatement.semicolonended = ast; 896 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder); 897 continue; 898 } else if (ast.tokencode == TBaseType.cmtdoublehyphen) { 899 if (ast.toString().trim().endsWith(TBaseType.sqlflow_stmt_delimiter_str)) { // -- sqlflow-delimiter 900 gst = EFindSqlStateType.stnormal; 901 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder); 902 continue; 903 } 904 } 905 906 appendToken(gcurrentsqlstatement, ast); 907 908 if (ast.tokencode == TBaseType.rrw_postgresql_language) { 909 // check next token which is the language used by this stored procedure 910 TSourceToken nextSt = ast.nextSolidToken(); 911 if (nextSt != null) { 912 if (gcurrentsqlstatement instanceof TRoutine) { // can be TCreateProcedureStmt or TCreateFunctionStmt 913 TRoutine p = (TRoutine) gcurrentsqlstatement; 914 p.setRoutineLanguage(nextSt.toString()); 915 } 916 } 917 } 918 919 break; 920 } 921 922 case stGaussDBPkgSpec: { 923 appendToken(gcurrentsqlstatement, ast); 924 925 if (ast.tokencode == TBaseType.rrw_end) { 926 TPlsqlCreatePackage plsqlCreatePackage = (TPlsqlCreatePackage) gcurrentsqlstatement; 927 928 String pkgName = findPkgName(ast.getNextTokenInChain(), 3); 929 if ((pkgName != null) && (pkgName.equalsIgnoreCase(plsqlCreatePackage.getPackageNameStr()))) { 930 gst = EFindSqlStateType.stGaussDBPkgSpecEnd; 931 } 932 } 933 break; 934 } 935 case stGaussDBPkgSpecEnd: { 936 appendToken(gcurrentsqlstatement, ast); 937 if (ast.tokencode == ';') { 938 gst = EFindSqlStateType.stnormal; 939 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder); 940 continue; 941 } 942 break; 943 } 944 945 case stGaussDBPkgBody: { 946 appendToken(gcurrentsqlstatement, ast); 947 if (ast.tokencode == TBaseType.rrw_end) { 948 TPlsqlCreatePackage plsqlCreatePackage = (TPlsqlCreatePackage) gcurrentsqlstatement; 949 950 String pkgName = findPkgName(ast.getNextTokenInChain(), 3); 951 if ((pkgName != null) && (pkgName.equalsIgnoreCase(plsqlCreatePackage.getPackageNameStr()))) { 952 gst = EFindSqlStateType.stGaussDBPkgBodyEnd; 953 } 954 } 955 break; 956 } 957 case stGaussDBPkgBodyEnd: { 958 appendToken(gcurrentsqlstatement, ast); 959 if (ast.tokencode == ';') { 960 gst = EFindSqlStateType.stnormal; 961 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder); 962 continue; 963 } 964 break; 965 } 966 967 } //switch 968 }//for 969 970 //last statement 971 boolean isSinglePLBlock = parserContext != null && parserContext.isSinglePLBlock(); 972 if ((gcurrentsqlstatement != null) && 973 ((gst == EFindSqlStateType.stsqlplus) || (gst == EFindSqlStateType.stsql) 974 || (gst == EFindSqlStateType.ststoredprocedure) 975 || (gst == EFindSqlStateType.ststoredprocedurePgEndBody) 976 || (gst == EFindSqlStateType.sterror) || (isSinglePLBlock) 977 )) { 978 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, true, builder); 979 } 980 981 // Populate builder with extracted statements 982 builder.sqlStatements(this.sqlstatements); 983 builder.errorCode(0); 984 builder.errorMessage(""); 985 } 986 987 /** 988 * Helper method to append a token to a statement. 989 * <p> 990 * Sets the token's statement reference and adds it to the statement's token list. 991 */ 992 private void appendToken(TCustomSqlStatement statement, TSourceToken token) { 993 if (statement == null || token == null) { 994 return; 995 } 996 token.stmt = statement; 997 statement.sourcetokenlist.add(token); 998 } 999 1000 // ========== Phase 4: Statement Parsing ========== 1001 1002 @Override 1003 protected TStatementList performParsing(ParserContext context, TCustomParser parser, 1004 TCustomParser secondaryParser, TSourceTokenList tokens, 1005 TStatementList rawStatements) { 1006 if (TBaseType.DUMP_RESOLVER_LOG_TO_CONSOLE) { 1007 System.out.println("EdbSqlParser.performParsing() CALLED with " + 1008 (rawStatements != null ? rawStatements.size() : 0) + " statements"); 1009 } 1010 1011 // Store references 1012 this.fparser = (TParserEdb) parser; 1013 this.sourcetokenlist = tokens; 1014 this.parserContext = context; 1015 1016 // Use the raw statements passed from AbstractSqlParser.parse() 1017 // (already extracted - DO NOT re-extract to avoid duplication) 1018 this.sqlstatements = rawStatements; 1019 1020 // Initialize sqlcmds 1021 this.sqlcmds = SqlCmdsFactory.get(vendor); 1022 this.fparser.sqlcmds = this.sqlcmds; 1023 1024 // Initialize global context using inherited method 1025 initializeGlobalContext(); 1026 1027 // Parse each statement 1028 for (int i = 0; i < sqlstatements.size(); i++) { 1029 TCustomSqlStatement stmt = sqlstatements.getRawSql(i); 1030 try { 1031 stmt.setFrameStack(frameStack); 1032 int parseResult = stmt.parsestatement(null, false, context.isOnlyNeedRawParseTree()); 1033 1034 // Error recovery 1035 boolean doRecover = TBaseType.ENABLE_ERROR_RECOVER_IN_CREATE_TABLE; 1036 if (doRecover && ((parseResult != 0) || (stmt.getErrorCount() > 0))) { 1037 handleCreateTableErrorRecovery(stmt); 1038 } 1039 1040 // Collect errors 1041 if ((parseResult != 0) || (stmt.getErrorCount() > 0)) { 1042 copyErrorsFromStatement(stmt); 1043 } 1044 } catch (Exception ex) { 1045 // Use inherited exception handler 1046 handleStatementParsingException(stmt, i, ex); 1047 continue; 1048 } 1049 } 1050 1051 // Clean up frame stack 1052 if (globalFrame != null) { 1053 globalFrame.popMeFromStack(frameStack); 1054 } 1055 1056 return sqlstatements; 1057 } 1058 1059 /** 1060 * Handle CREATE TABLE error recovery. 1061 * <p> 1062 * Migrated from TGSqlParser.doparse() error recovery logic. 1063 * <p> 1064 * Attempts to recover from parsing errors in CREATE TABLE statements by 1065 * marking unparseable table properties as sqlpluscmd and retrying. 1066 * This allows parsing CREATE TABLE statements with vendor-specific extensions 1067 * that may not be in the grammar. 1068 */ 1069 private void handleCreateTableErrorRecovery(TCustomSqlStatement stmt) { 1070 // Check if this is a CREATE TABLE or CREATE INDEX statement 1071 if (!(stmt.sqlstatementtype == ESqlStatementType.sstcreatetable || 1072 stmt.sqlstatementtype == ESqlStatementType.sstcreateindex)) { 1073 return; 1074 } 1075 1076 // Check if strict parsing is disabled 1077 if (TBaseType.c_createTableStrictParsing) { 1078 return; 1079 } 1080 1081 TCustomSqlStatement errorSqlStatement = stmt; 1082 1083 int nested = 0; 1084 boolean isIgnore = false; 1085 boolean isFoundIgnoreToken = false; 1086 TSourceToken firstIgnoreToken = null; 1087 1088 // Iterate through tokens to find the closing parenthesis of table definition 1089 for (int k = 0; k < errorSqlStatement.sourcetokenlist.size(); k++) { 1090 TSourceToken st = errorSqlStatement.sourcetokenlist.get(k); 1091 1092 if (isIgnore) { 1093 // We're past the table definition, mark tokens as ignoreable 1094 if (st.issolidtoken() && (st.tokencode != ';')) { 1095 isFoundIgnoreToken = true; 1096 if (firstIgnoreToken == null) { 1097 firstIgnoreToken = st; 1098 } 1099 } 1100 // Mark all tokens (except semicolon) as sqlpluscmd to ignore them 1101 if (st.tokencode != ';') { 1102 st.tokencode = TBaseType.sqlpluscmd; 1103 } 1104 continue; 1105 } 1106 1107 // Track closing parentheses 1108 if (st.tokencode == (int) ')') { 1109 nested--; 1110 if (nested == 0) { 1111 // Found the closing parenthesis of table definition 1112 // Check if next tokens are "AS ( SELECT" (CTAS pattern) 1113 boolean isSelect = false; 1114 TSourceToken st1 = st.searchToken(TBaseType.rrw_as, 1); 1115 if (st1 != null) { 1116 TSourceToken st2 = st.searchToken((int) '(', 2); 1117 if (st2 != null) { 1118 TSourceToken st3 = st.searchToken(TBaseType.rrw_select, 3); 1119 isSelect = (st3 != null); 1120 } 1121 } 1122 // If not a CTAS, start ignoring subsequent tokens 1123 if (!isSelect) { 1124 isIgnore = true; 1125 } 1126 } 1127 } 1128 1129 // Track opening parentheses 1130 if ((st.tokencode == (int) '(') || (st.tokencode == TBaseType.left_parenthesis_2)) { 1131 nested++; 1132 } 1133 } 1134 1135 // If we found ignoreable tokens, clear errors and retry parsing 1136 if (isFoundIgnoreToken) { 1137 errorSqlStatement.clearError(); 1138 int retryResult = errorSqlStatement.parsestatement(null, false, parserContext.isOnlyNeedRawParseTree()); 1139 } 1140 } 1141 1142 // ========== Phase 5: Semantic Analysis & Interpretation ========== 1143 1144 @Override 1145 protected void performSemanticAnalysis(ParserContext context, TStatementList statements) { 1146 if (!TBaseType.isEnableResolver()) { 1147 return; 1148 } 1149 1150 if (getSyntaxErrors().isEmpty()) { 1151 TSQLResolver resolver = new TSQLResolver(this.globalContext, statements); 1152 resolver.resolve(); 1153 } 1154 } 1155 1156 @Override 1157 protected void performInterpreter(ParserContext context, TStatementList statements) { 1158 if (!TBaseType.ENABLE_INTERPRETER) { 1159 return; 1160 } 1161 1162 if (getSyntaxErrors().isEmpty()) { 1163 TGlobalScope interpreterScope = new TGlobalScope(sqlEnv); 1164 TASTEvaluator astEvaluator = new TASTEvaluator(statements, interpreterScope); 1165 astEvaluator.eval(); 1166 } 1167 } 1168 1169 // ========== Vendor-Specific Statement Completion ========== 1170 1171 /** 1172 * Edb-specific statement completion logic. 1173 * <p> 1174 * Migrated from TGSqlParser.doongetrawsqlstatementevent() (lines 5129-5141). 1175 * <p> 1176 * For CREATE PROCEDURE/FUNCTION statements, marks Oracle-style routines 1177 * by setting special token codes to distinguish them from PostgreSQL-style routines. 1178 * 1179 * @param statement the completed statement 1180 */ 1181 @Override 1182 protected void onRawStatementCompleteVendorSpecific(TCustomSqlStatement statement) { 1183 // First, call parent implementation for PostgreSQL-family logic 1184 // (Edb inherits PostgreSQL compatibility, so this applies too) 1185 super.onRawStatementCompleteVendorSpecific(statement); 1186 1187 // Edb-specific: Mark Oracle-style procedures/functions 1188 // Migrated from TGSqlParser.doongetrawsqlstatementevent() lines 5129-5141 1189 if ((statement.sqlstatementtype == ESqlStatementType.sstcreateprocedure) 1190 || (statement.sqlstatementtype == ESqlStatementType.sstcreatefunction)) { 1191 1192 if (isOracleStyleRoutine) { 1193 if (stFunction != null) { 1194 stFunction.tokencode = TBaseType.GAUSSDB_FUNCTION_ORA; 1195 } else if (stProcedure != null) { 1196 stProcedure.tokencode = TBaseType.GAUSSDB_PROCEDURE_ORA; 1197 } 1198 } 1199 1200 // Reset state for next statement 1201 stFunction = null; 1202 stProcedure = null; 1203 isOracleStyleRoutine = true; 1204 } 1205 } 1206 1207 @Override 1208 public EDbVendor getVendor() { 1209 return vendor; 1210 } 1211 1212 @Override 1213 public String toString() { 1214 return "EdbSqlParser{vendor=" + vendor + "}"; 1215 } 1216}