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.TLexerGaussDB; 014import gudusoft.gsqlparser.TParserGaussDB; 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 * GaussDB SQL parser implementation. 039 * 040 * <p>This parser handles GaussDB-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 GaussDB 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 TParserGaussDB}</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 GaussDB-specific state tracking</li> 062 * </ul> 063 * 064 * @see SqlParser 065 * @see AbstractSqlParser 066 * @see TLexerGaussDB 067 * @see TParserGaussDB 068 * @since 3.2.0.0 069 */ 070public class GaussDbSqlParser extends AbstractSqlParser { 071 072 // ========== Lexer and Parser ========== 073 074 /** The GaussDB lexer used for tokenization */ 075 public TLexerGaussDB flexer; 076 077 /** The GaussDB parser used for parsing */ 078 private TParserGaussDB 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 GaussDB SQL parser. 096 * <p> 097 * Configures the parser for GaussDB database with semicolon (;) as the default delimiter. 098 */ 099 public GaussDbSqlParser() { 100 super(EDbVendor.dbvgaussdb); 101 this.delimiterChar = ';'; 102 this.defaultDelimiterStr = ";"; 103 104 // Create lexer once - will be reused for all parsing operations 105 this.flexer = new TLexerGaussDB(); 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 TParserGaussDB(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 // GaussDB 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 dogaussdbtexttotokenlist(); 141 } 142 143 /** 144 * Tokenize GaussDB SQL text into source tokens. 145 * <p> 146 * Migrated from TGSqlParser.dogaussdbtexttotokenlist() (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 dogaussdbtexttotokenlist() { 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/GaussDB 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 GaussDB 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 dogaussdbgetrawsqlstatements(builder); 453 } 454 455 /** 456 * Extract raw SQL statements from token list. 457 * <p> 458 * Migrated from TGSqlParser.dogaussdbgetrawsqlstatements() (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 GaussDB-specific state tracking</li> 466 * <li>Slash (/) and period (.) terminators for procedural blocks</li> 467 * </ul> 468 */ 469 private void dogaussdbgetrawsqlstatements(SqlParseResult.Builder builder) { 470 int waitingEnd = 0; 471 boolean foundEnd = false, enterDeclare = false; 472 isOracleStyleRoutine = true; 473 stProcedure = null; 474 stFunction = null; 475 476 if (TBaseType.assigned(sqlstatements)) sqlstatements.clear(); 477 if (!TBaseType.assigned(sourcetokenlist)) { 478 builder.errorCode(-1); 479 return; 480 } 481 482 gcurrentsqlstatement = null; 483 EFindSqlStateType gst = EFindSqlStateType.stnormal; 484 TSourceToken lcprevsolidtoken = null, ast = null; 485 486 // Handle single PL block mode 487 if (parserContext != null && parserContext.isSinglePLBlock()) { 488 gcurrentsqlstatement = new TCommonBlock(vendor); 489 } 490 491 for (int i = 0; i < sourcetokenlist.size(); i++) { 492 493 if ((ast != null) && (ast.issolidtoken())) 494 lcprevsolidtoken = ast; 495 496 ast = sourcetokenlist.get(i); 497 sourcetokenlist.curpos = i; 498 499 if (parserContext != null && parserContext.isSinglePLBlock()) { 500 gcurrentsqlstatement.sourcetokenlist.add(ast); 501 continue; 502 } 503 504 // Token preprocessing: Adjust token codes based on context 505 if (ast.tokencode == TBaseType.JSON_EXIST) { 506 TSourceToken stConstant = ast.searchToken(TBaseType.sconst, 1); 507 if (stConstant == null) { 508 ast.tokencode = TBaseType.ident; 509 } 510 } else if (ast.tokencode == TBaseType.rrw_postgresql_POSITION) { 511 TSourceToken st1 = ast.nextSolidToken(); 512 if (st1 != null) { 513 if (st1.tokencode == '(') { 514 ast.tokencode = TBaseType.rrw_postgresql_POSITION_FUNCTION; 515 } 516 } 517 } else if (ast.tokencode == TBaseType.rrw_postgresql_ordinality) { 518 TSourceToken lcprevst = getprevsolidtoken(ast); 519 520 if (lcprevst != null) { 521 if (lcprevst.tokencode == TBaseType.rrw_with) { 522 TSourceToken lcnextst = ast.nextSolidToken(); 523 if ((lcnextst != null) && (lcnextst.tokencode == TBaseType.rrw_as)) { 524 // with ordinality as (select 1 as x) select * from ordinality; 525 // don't change with to rrw_postgresql_with_lookahead 526 } else { 527 lcprevst.tokencode = TBaseType.rrw_postgresql_with_lookahead; 528 } 529 530 } 531 } 532 } else if ((ast.tokencode == TBaseType.rrw_postgresql_filter) 533 || (ast.tokencode == TBaseType.GAUSSDB_TO_BINARY_DOUBLE) 534 || (ast.tokencode == TBaseType.GAUSSDB_TO_BINARY_FLOAT) 535 || (ast.tokencode == TBaseType.GAUSSDB_TO_NUMBER) 536 || (ast.tokencode == TBaseType.GAUSSDB_TO_DATE) 537 || (ast.tokencode == TBaseType.GAUSSDB_TO_TIMESTAMP) 538 || (ast.tokencode == TBaseType.GAUSSDB_TO_TIMESTAMP_TZ) 539 ) { 540 TSourceToken st1 = ast.nextSolidToken(); 541 if (st1 != null) { 542 if (st1.tokencode == '(') { 543 // Keep as function 544 } else { 545 ast.tokencode = TBaseType.ident; 546 } 547 } 548 } else if (ast.tokencode == TBaseType.rrw_postgresql_jsonb) { 549 TSourceToken st1 = ast.nextSolidToken(); 550 if (st1 != null) { 551 if (st1.tokencode == '?') { 552 st1.tokencode = TBaseType.OP_JSONB_QUESTION; 553 } 554 } 555 } else if (ast.tokencode == TBaseType.GAUSSDB_TO_NUMBER) { 556 TSourceToken st1 = ast.searchToken('(', 1); 557 if (st1 == null) { 558 ast.tokencode = TBaseType.ident; 559 } 560 } else if (ast.tokencode == '?') { 561 TSourceToken st1 = ast.nextSolidToken(); 562 if (st1 != null) { 563 if (st1.tokencode == TBaseType.sconst) { 564 ast.tokencode = TBaseType.OP_JSONB_QUESTION; 565 } 566 } 567 } else if (ast.tokencode == TBaseType.rrw_values) { 568 TSourceToken stParen = ast.searchToken('(', 1); 569 if (stParen != null) { 570 TSourceToken stInsert = ast.searchToken(TBaseType.rrw_insert, -ast.posinlist); 571 if (stInsert != null) { 572 TSourceToken stSemiColon = ast.searchToken(';', -ast.posinlist); 573 if ((stSemiColon != null) && (stSemiColon.posinlist > stInsert.posinlist)) { 574 // INSERT INTO test values (16,1), (8,2), (4,4), (2,0), (97, 16); 575 // VALUES (1); 576 // don't treat values(1) as insert values 577 578 } else { 579 TSourceToken stFrom = ast.searchToken(TBaseType.rrw_from, -ast.posinlist); 580 if ((stFrom != null) && (stFrom.posinlist > stInsert.posinlist)) { 581 // Don't treat values after from keyword as an insert values 582 } else { 583 ast.tokencode = TBaseType.rrw_postgresql_insert_values; 584 } 585 } 586 } 587 } 588 } 589 590 switch (gst) { 591 case sterror: { 592 if (ast.tokentype == ETokenType.ttsemicolon) { 593 appendToken(gcurrentsqlstatement, ast); 594 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder); 595 gst = EFindSqlStateType.stnormal; 596 } else { 597 appendToken(gcurrentsqlstatement, ast); 598 } 599 break; 600 } //sterror 601 602 case stnormal: { 603 if ((ast.tokencode == TBaseType.cmtdoublehyphen) 604 || (ast.tokencode == TBaseType.cmtslashstar) 605 || (ast.tokencode == TBaseType.lexspace) 606 || (ast.tokencode == TBaseType.lexnewline) 607 || (ast.tokentype == ETokenType.ttsemicolon)) { 608 if (gcurrentsqlstatement != null) { 609 appendToken(gcurrentsqlstatement, ast); 610 } 611 612 if ((lcprevsolidtoken != null) && (ast.tokentype == ETokenType.ttsemicolon)) { 613 if (lcprevsolidtoken.tokentype == ETokenType.ttsemicolon) { 614 // ;;;; continuous semicolon, treat it as comment 615 ast.tokentype = ETokenType.ttsimplecomment; 616 ast.tokencode = TBaseType.cmtdoublehyphen; 617 } 618 } 619 620 continue; 621 } 622 623 if (ast.tokencode == TBaseType.sqlpluscmd) { 624 gst = EFindSqlStateType.stsqlplus; 625 gcurrentsqlstatement = new TSqlplusCmdStatement(vendor); 626 appendToken(gcurrentsqlstatement, ast); 627 continue; 628 } 629 630 // find a token to start sql or plsql mode 631 gcurrentsqlstatement = sqlcmds.issql(ast, gst, gcurrentsqlstatement); 632 633 if (gcurrentsqlstatement != null) { 634 enterDeclare = false; 635 if (gcurrentsqlstatement.isGaussDBStoredProcedure()) { 636 appendToken(gcurrentsqlstatement, ast); 637 if (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstplsql_createpackage) { 638 gst = EFindSqlStateType.stGaussDBPkgSpec; 639 TPlsqlCreatePackage pkgspec = (TPlsqlCreatePackage) gcurrentsqlstatement; 640 pkgspec.setPackageNameStr(findPkgName(ast.getNextTokenInChain(), 1)); 641 } else if (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstoraclecreatepackagebody) { 642 gst = EFindSqlStateType.stGaussDBPkgBody; 643 TPlsqlCreatePackage pkgspec = (TPlsqlCreatePackage) gcurrentsqlstatement; 644 pkgspec.setPackageNameStr(findPkgName(ast.getNextTokenInChain(), 2)); 645 } else { 646 gst = EFindSqlStateType.ststoredprocedure; 647 648 foundEnd = false; 649 if ((ast.tokencode == TBaseType.rrw_begin) 650 || (ast.tokencode == TBaseType.rrw_package) 651 || (ast.searchToken(TBaseType.rrw_package, 4) != null) 652 ) { 653 waitingEnd = 1; 654 } else if (ast.tokencode == TBaseType.rrw_declare) { 655 enterDeclare = true; 656 } 657 658 if (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstcreateprocedure) { 659 // create procedure, create or replace procedure 660 stProcedure = ast.searchToken(TBaseType.rrw_procedure, 3); 661 } else if (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstcreatefunction) { 662 // create function, create or replace function 663 stFunction = ast.searchToken(TBaseType.rrw_function, 3); 664 } 665 } 666 } else { 667 gst = EFindSqlStateType.stsql; 668 appendToken(gcurrentsqlstatement, ast); 669 } 670 } else { 671 //error token found 672 673 this.syntaxErrors.add(new TSyntaxError(ast.getAstext(), ast.lineNo, (ast.columnNo < 0 ? 0 : ast.columnNo) 674 , "Error when tokenlize", EErrorType.spwarning, TBaseType.MSG_WARNING_ERROR_WHEN_TOKENIZE, null, ast.posinlist)); 675 676 ast.tokentype = ETokenType.tttokenlizererrortoken; 677 gst = EFindSqlStateType.sterror; 678 679 gcurrentsqlstatement = new TUnknownSqlStatement(vendor); 680 gcurrentsqlstatement.sqlstatementtype = ESqlStatementType.sstinvalid; 681 appendToken(gcurrentsqlstatement, ast); 682 683 } 684 685 break; 686 } // stnormal 687 688 case stsqlplus: { 689 if (ast.insqlpluscmd) { 690 appendToken(gcurrentsqlstatement, ast); 691 } else { 692 gst = EFindSqlStateType.stnormal; //this token must be newline, 693 appendToken(gcurrentsqlstatement, ast); // so add it here 694 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder); 695 } 696 697 break; 698 }//case stsqlplus 699 700 case stsql: { 701 if (ast.tokentype == ETokenType.ttsemicolon) { 702 gst = EFindSqlStateType.stnormal; 703 appendToken(gcurrentsqlstatement, ast); 704 gcurrentsqlstatement.semicolonended = ast; 705 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder); 706 continue; 707 } 708 709 if (sourcetokenlist.sqlplusaftercurtoken()) { //most probably is / cmd 710 // In GaussDB, if / appears in regular SQL (non-stored procedure) and is identified as sqlplus, 711 // it should be a false positive. For example, in division expressions. 712 // We need to restore / to a regular character / 713 TSourceToken st = ast.nextSolidToken(); 714 if (st.tokentype == ETokenType.ttslash) { 715 st.tokencode = '/'; 716 } else { // Non-/ sqlplus commands are still treated as sqlplus, but whether GaussDB has Oracle-like sqlplus needs further verification 717 gst = EFindSqlStateType.stnormal; 718 appendToken(gcurrentsqlstatement, ast); 719 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder); 720 continue; 721 } 722 } 723 724 if (ast.tokencode == TBaseType.cmtdoublehyphen) { 725 if (ast.toString().trim().endsWith(TBaseType.sqlflow_stmt_delimiter_str)) { // -- sqlflow-delimiter 726 gst = EFindSqlStateType.stnormal; 727 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder); 728 continue; 729 } 730 } 731 732 appendToken(gcurrentsqlstatement, ast); 733 break; 734 }//case stsql 735 736 case ststoredprocedure: { 737 if (ast.tokencode == TBaseType.rrw_postgresql_function_delimiter) { 738 appendToken(gcurrentsqlstatement, ast); 739 isOracleStyleRoutine = false; 740 gst = EFindSqlStateType.ststoredprocedurePgStartBody; 741 continue; 742 } 743 744 if (ast.tokencode == TBaseType.rrw_postgresql_language) { 745 // check next token which is the language used by this stored procedure 746 TSourceToken nextSt = ast.nextSolidToken(); 747 if (nextSt != null) { 748 if (gcurrentsqlstatement instanceof TRoutine) { // can be TCreateProcedureStmt or TCreateFunctionStmt 749 TRoutine p = (TRoutine) gcurrentsqlstatement; 750 p.setRoutineLanguage(nextSt.toString()); 751 isOracleStyleRoutine = false; 752 } 753 } 754 } 755 756 if ((ast.tokentype == ETokenType.ttsemicolon) && (waitingEnd == 0) && (!enterDeclare)) { 757 gst = EFindSqlStateType.stnormal; 758 appendToken(gcurrentsqlstatement, ast); 759 gcurrentsqlstatement.semicolonended = ast; 760 761 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder); 762 continue; 763 } 764 765 if ((ast.tokencode == TBaseType.rrw_begin) 766 ) { 767 waitingEnd++; 768 enterDeclare = false; 769 } else if ( 770 (ast.tokencode == TBaseType.rrw_declare) 771 || (ast.tokencode == TBaseType.rrw_as) || (ast.tokencode == TBaseType.rrw_is) 772 ) { 773 TSourceToken next = ast.nextSolidToken(); 774 if ((next != null) && ((next.tokencode == TBaseType.sconst) || (next.tokencode == TBaseType.GAUSSDB_NULL))) { 775 // CREATE FUNCTION func_add_sql(integer, integer) RETURNS integer 776 // AS 'select $1 + $2;' 777 // LANGUAGE SQL 778 // IMMUTABLE 779 // RETURNS NULL ON NULL INPUT; 780 } else { 781 enterDeclare = true; 782 } 783 } else if ((ast.tokencode == TBaseType.rrw_if) 784 ) { 785 if (ast.searchToken(TBaseType.rrw_end, -1) == null) { 786 //this is not if after END 787 waitingEnd++; 788 } 789 } else if ((ast.tokencode == TBaseType.rrw_case) 790 ) { 791 if (ast.searchToken(TBaseType.rrw_end, -1) == null) { 792 //this is not case after END 793 waitingEnd++; 794 } 795 } else if ((ast.tokencode == TBaseType.rrw_loop) 796 ) { 797 if (ast.searchToken(TBaseType.rrw_end, -1) == null) { 798 //this is not loop after END 799 waitingEnd++; 800 } 801 } else if (ast.tokencode == TBaseType.rrw_end) { 802 foundEnd = true; 803 waitingEnd--; 804 if (waitingEnd < 0) { 805 waitingEnd = 0; 806 } 807 } 808 809 if ((ast.tokentype == ETokenType.ttslash) && (ast.tokencode == TBaseType.sqlpluscmd)) //and (prevst.NewlineIsLastTokenInTailerToken)) then 810 { 811 // TPlsqlStatementParse(asqlstatement).TerminatorToken := ast; 812 ast.tokenstatus = ETokenStatus.tsignorebyyacc; 813 gst = EFindSqlStateType.stnormal; 814 815 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder); 816 817 //make / a sqlplus cmd 818 gcurrentsqlstatement = new TSqlplusCmdStatement(vendor); 819 appendToken(gcurrentsqlstatement, ast); 820 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder); 821 } else if ((ast.tokentype == ETokenType.ttperiod) && (sourcetokenlist.returnaftercurtoken(false)) && (sourcetokenlist.returnbeforecurtoken(false))) { // single dot at a separate line 822 ast.tokenstatus = ETokenStatus.tsignorebyyacc; 823 gst = EFindSqlStateType.stnormal; 824 825 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder); 826 827 //make ttperiod a sqlplus cmd 828 gcurrentsqlstatement = new TSqlplusCmdStatement(vendor); 829 appendToken(gcurrentsqlstatement, ast); 830 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder); 831 } else { 832 appendToken(gcurrentsqlstatement, ast); 833 if ((ast.tokentype == ETokenType.ttsemicolon) && (waitingEnd == 0) && (!enterDeclare) 834 && (foundEnd) 835 ) { 836 gst = EFindSqlStateType.stnormal; 837 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder); 838 } 839 } 840 841 if (ast.tokencode == TBaseType.sqlpluscmd) { 842 //change tokencode back to keyword or TBaseType.ident, because sqlplus cmd 843 //in a sql statement(almost is plsql block) is not really a sqlplus cmd 844 int m = flexer.getkeywordvalue(ast.getAstext()); 845 if (m != 0) { 846 ast.tokencode = m; 847 } else { 848 ast.tokencode = TBaseType.ident; 849 } 850 } 851 852 if ((gst == EFindSqlStateType.ststoredprocedure) && (ast.tokencode == TBaseType.cmtdoublehyphen)) { 853 if (ast.toString().trim().endsWith(TBaseType.sqlflow_stmt_delimiter_str)) { // -- sqlflow-delimiter 854 gst = EFindSqlStateType.stnormal; 855 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder); 856 } 857 } 858 859 break; 860 } //ststoredprocedure 861 862 case ststoredprocedurePgStartBody: { 863 appendToken(gcurrentsqlstatement, ast); 864 865 if (ast.tokencode == TBaseType.rrw_postgresql_function_delimiter) { 866 gst = EFindSqlStateType.ststoredprocedurePgEndBody; 867 continue; 868 } 869 870 break; 871 } 872 873 case ststoredprocedurePgEndBody: { 874 875 if (ast.tokentype == ETokenType.ttsemicolon) { 876 gst = EFindSqlStateType.stnormal; 877 appendToken(gcurrentsqlstatement, ast); 878 gcurrentsqlstatement.semicolonended = ast; 879 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder); 880 continue; 881 } else if (ast.tokencode == TBaseType.cmtdoublehyphen) { 882 if (ast.toString().trim().endsWith(TBaseType.sqlflow_stmt_delimiter_str)) { // -- sqlflow-delimiter 883 gst = EFindSqlStateType.stnormal; 884 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder); 885 continue; 886 } 887 } 888 889 appendToken(gcurrentsqlstatement, ast); 890 891 if (ast.tokencode == TBaseType.rrw_postgresql_language) { 892 // check next token which is the language used by this stored procedure 893 TSourceToken nextSt = ast.nextSolidToken(); 894 if (nextSt != null) { 895 if (gcurrentsqlstatement instanceof TRoutine) { // can be TCreateProcedureStmt or TCreateFunctionStmt 896 TRoutine p = (TRoutine) gcurrentsqlstatement; 897 p.setRoutineLanguage(nextSt.toString()); 898 } 899 } 900 } 901 902 break; 903 } 904 905 case stGaussDBPkgSpec: { 906 appendToken(gcurrentsqlstatement, ast); 907 908 if (ast.tokencode == TBaseType.rrw_end) { 909 TPlsqlCreatePackage plsqlCreatePackage = (TPlsqlCreatePackage) gcurrentsqlstatement; 910 911 String pkgName = findPkgName(ast.getNextTokenInChain(), 3); 912 if ((pkgName != null) && (pkgName.equalsIgnoreCase(plsqlCreatePackage.getPackageNameStr()))) { 913 gst = EFindSqlStateType.stGaussDBPkgSpecEnd; 914 } 915 } 916 break; 917 } 918 case stGaussDBPkgSpecEnd: { 919 appendToken(gcurrentsqlstatement, ast); 920 if (ast.tokencode == ';') { 921 gst = EFindSqlStateType.stnormal; 922 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder); 923 continue; 924 } 925 break; 926 } 927 928 case stGaussDBPkgBody: { 929 appendToken(gcurrentsqlstatement, ast); 930 if (ast.tokencode == TBaseType.rrw_end) { 931 TPlsqlCreatePackage plsqlCreatePackage = (TPlsqlCreatePackage) gcurrentsqlstatement; 932 933 String pkgName = findPkgName(ast.getNextTokenInChain(), 3); 934 if ((pkgName != null) && (pkgName.equalsIgnoreCase(plsqlCreatePackage.getPackageNameStr()))) { 935 gst = EFindSqlStateType.stGaussDBPkgBodyEnd; 936 } 937 } 938 break; 939 } 940 case stGaussDBPkgBodyEnd: { 941 appendToken(gcurrentsqlstatement, ast); 942 if (ast.tokencode == ';') { 943 gst = EFindSqlStateType.stnormal; 944 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder); 945 continue; 946 } 947 break; 948 } 949 950 } //switch 951 }//for 952 953 //last statement 954 boolean isSinglePLBlock = parserContext != null && parserContext.isSinglePLBlock(); 955 if ((gcurrentsqlstatement != null) && 956 ((gst == EFindSqlStateType.stsqlplus) || (gst == EFindSqlStateType.stsql) 957 || (gst == EFindSqlStateType.ststoredprocedure) 958 || (gst == EFindSqlStateType.ststoredprocedurePgEndBody) 959 || (gst == EFindSqlStateType.sterror) || (isSinglePLBlock) 960 )) { 961 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, true, builder); 962 } 963 964 // Populate builder with extracted statements 965 builder.sqlStatements(this.sqlstatements); 966 builder.errorCode(0); 967 builder.errorMessage(""); 968 } 969 970 /** 971 * Helper method to append a token to a statement. 972 * <p> 973 * Sets the token's statement reference and adds it to the statement's token list. 974 */ 975 private void appendToken(TCustomSqlStatement statement, TSourceToken token) { 976 if (statement == null || token == null) { 977 return; 978 } 979 token.stmt = statement; 980 statement.sourcetokenlist.add(token); 981 } 982 983 // ========== Phase 4: Statement Parsing ========== 984 985 @Override 986 protected TStatementList performParsing(ParserContext context, TCustomParser parser, 987 TCustomParser secondaryParser, TSourceTokenList tokens, 988 TStatementList rawStatements) { 989 if (TBaseType.DUMP_RESOLVER_LOG_TO_CONSOLE) { 990 System.out.println("GaussDbSqlParser.performParsing() CALLED with " + 991 (rawStatements != null ? rawStatements.size() : 0) + " statements"); 992 } 993 994 // Store references 995 this.fparser = (TParserGaussDB) parser; 996 this.sourcetokenlist = tokens; 997 this.parserContext = context; 998 999 // Use the raw statements passed from AbstractSqlParser.parse() 1000 // (already extracted - DO NOT re-extract to avoid duplication) 1001 this.sqlstatements = rawStatements; 1002 1003 // Initialize sqlcmds 1004 this.sqlcmds = SqlCmdsFactory.get(vendor); 1005 this.fparser.sqlcmds = this.sqlcmds; 1006 1007 // Initialize global context using inherited method 1008 initializeGlobalContext(); 1009 1010 // Parse each statement 1011 for (int i = 0; i < sqlstatements.size(); i++) { 1012 TCustomSqlStatement stmt = sqlstatements.getRawSql(i); 1013 try { 1014 stmt.setFrameStack(frameStack); 1015 int parseResult = stmt.parsestatement(null, false, context.isOnlyNeedRawParseTree()); 1016 1017 // Error recovery 1018 boolean doRecover = TBaseType.ENABLE_ERROR_RECOVER_IN_CREATE_TABLE; 1019 if (doRecover && ((parseResult != 0) || (stmt.getErrorCount() > 0))) { 1020 handleCreateTableErrorRecovery(stmt); 1021 } 1022 1023 // Collect errors 1024 if ((parseResult != 0) || (stmt.getErrorCount() > 0)) { 1025 copyErrorsFromStatement(stmt); 1026 } 1027 } catch (Exception ex) { 1028 // Use inherited exception handler 1029 handleStatementParsingException(stmt, i, ex); 1030 continue; 1031 } 1032 } 1033 1034 // Clean up frame stack 1035 if (globalFrame != null) { 1036 globalFrame.popMeFromStack(frameStack); 1037 } 1038 1039 return sqlstatements; 1040 } 1041 1042 /** 1043 * Handle CREATE TABLE error recovery. 1044 * <p> 1045 * Migrated from TGSqlParser.doparse() error recovery logic. 1046 * <p> 1047 * Attempts to recover from parsing errors in CREATE TABLE statements by 1048 * marking unparseable table properties as sqlpluscmd and retrying. 1049 * This allows parsing CREATE TABLE statements with vendor-specific extensions 1050 * that may not be in the grammar. 1051 */ 1052 private void handleCreateTableErrorRecovery(TCustomSqlStatement stmt) { 1053 // Check if this is a CREATE TABLE or CREATE INDEX statement 1054 if (!(stmt.sqlstatementtype == ESqlStatementType.sstcreatetable || 1055 stmt.sqlstatementtype == ESqlStatementType.sstcreateindex)) { 1056 return; 1057 } 1058 1059 // Check if strict parsing is disabled 1060 if (TBaseType.c_createTableStrictParsing) { 1061 return; 1062 } 1063 1064 TCustomSqlStatement errorSqlStatement = stmt; 1065 1066 int nested = 0; 1067 boolean isIgnore = false; 1068 boolean isFoundIgnoreToken = false; 1069 TSourceToken firstIgnoreToken = null; 1070 1071 // Iterate through tokens to find the closing parenthesis of table definition 1072 for (int k = 0; k < errorSqlStatement.sourcetokenlist.size(); k++) { 1073 TSourceToken st = errorSqlStatement.sourcetokenlist.get(k); 1074 1075 if (isIgnore) { 1076 // We're past the table definition, mark tokens as ignoreable 1077 if (st.issolidtoken() && (st.tokencode != ';')) { 1078 isFoundIgnoreToken = true; 1079 if (firstIgnoreToken == null) { 1080 firstIgnoreToken = st; 1081 } 1082 } 1083 // Mark all tokens (except semicolon) as sqlpluscmd to ignore them 1084 if (st.tokencode != ';') { 1085 st.tokencode = TBaseType.sqlpluscmd; 1086 } 1087 continue; 1088 } 1089 1090 // Track closing parentheses 1091 if (st.tokencode == (int) ')') { 1092 nested--; 1093 if (nested == 0) { 1094 // Found the closing parenthesis of table definition 1095 // Check if next tokens are "AS ( SELECT" (CTAS pattern) 1096 boolean isSelect = false; 1097 TSourceToken st1 = st.searchToken(TBaseType.rrw_as, 1); 1098 if (st1 != null) { 1099 TSourceToken st2 = st.searchToken((int) '(', 2); 1100 if (st2 != null) { 1101 TSourceToken st3 = st.searchToken(TBaseType.rrw_select, 3); 1102 isSelect = (st3 != null); 1103 } 1104 } 1105 // If not a CTAS, start ignoring subsequent tokens 1106 if (!isSelect) { 1107 isIgnore = true; 1108 } 1109 } 1110 } 1111 1112 // Track opening parentheses 1113 if ((st.tokencode == (int) '(') || (st.tokencode == TBaseType.left_parenthesis_2)) { 1114 nested++; 1115 } 1116 } 1117 1118 // If we found ignoreable tokens, clear errors and retry parsing 1119 if (isFoundIgnoreToken) { 1120 errorSqlStatement.clearError(); 1121 int retryResult = errorSqlStatement.parsestatement(null, false, parserContext.isOnlyNeedRawParseTree()); 1122 } 1123 } 1124 1125 // ========== Phase 5: Semantic Analysis & Interpretation ========== 1126 1127 @Override 1128 protected void performSemanticAnalysis(ParserContext context, TStatementList statements) { 1129 if (!TBaseType.isEnableResolver()) { 1130 return; 1131 } 1132 1133 if (getSyntaxErrors().isEmpty()) { 1134 TSQLResolver resolver = new TSQLResolver(this.globalContext, statements); 1135 resolver.resolve(); 1136 } 1137 } 1138 1139 @Override 1140 protected void performInterpreter(ParserContext context, TStatementList statements) { 1141 if (!TBaseType.ENABLE_INTERPRETER) { 1142 return; 1143 } 1144 1145 if (getSyntaxErrors().isEmpty()) { 1146 TGlobalScope interpreterScope = new TGlobalScope(sqlEnv); 1147 TASTEvaluator astEvaluator = new TASTEvaluator(statements, interpreterScope); 1148 astEvaluator.eval(); 1149 } 1150 } 1151 1152 // ========== Vendor-Specific Statement Completion ========== 1153 1154 /** 1155 * GaussDB-specific statement completion logic. 1156 * <p> 1157 * Migrated from TGSqlParser.doongetrawsqlstatementevent() (lines 5129-5141). 1158 * <p> 1159 * For CREATE PROCEDURE/FUNCTION statements, marks Oracle-style routines 1160 * by setting special token codes to distinguish them from PostgreSQL-style routines. 1161 * 1162 * @param statement the completed statement 1163 */ 1164 @Override 1165 protected void onRawStatementCompleteVendorSpecific(TCustomSqlStatement statement) { 1166 // First, call parent implementation for PostgreSQL-family logic 1167 // (GaussDB inherits PostgreSQL compatibility, so this applies too) 1168 super.onRawStatementCompleteVendorSpecific(statement); 1169 1170 // GaussDB-specific: Mark Oracle-style procedures/functions 1171 // Migrated from TGSqlParser.doongetrawsqlstatementevent() lines 5129-5141 1172 if ((statement.sqlstatementtype == ESqlStatementType.sstcreateprocedure) 1173 || (statement.sqlstatementtype == ESqlStatementType.sstcreatefunction)) { 1174 1175 if (isOracleStyleRoutine) { 1176 if (stFunction != null) { 1177 stFunction.tokencode = TBaseType.GAUSSDB_FUNCTION_ORA; 1178 } else if (stProcedure != null) { 1179 stProcedure.tokencode = TBaseType.GAUSSDB_PROCEDURE_ORA; 1180 } 1181 } 1182 1183 // Reset state for next statement 1184 stFunction = null; 1185 stProcedure = null; 1186 isOracleStyleRoutine = true; 1187 } 1188 } 1189 1190 @Override 1191 public EDbVendor getVendor() { 1192 return vendor; 1193 } 1194 1195 @Override 1196 public String toString() { 1197 return "GaussDbSqlParser{vendor=" + vendor + "}"; 1198 } 1199}