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.TLexerSnowflake; 009import gudusoft.gsqlparser.TParserSnowflake; 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.TCommonStoredProcedureSqlStatement; 021import gudusoft.gsqlparser.stmt.oracle.TSqlplusCmdStatement; 022import gudusoft.gsqlparser.stmt.oracle.TPlsqlCreatePackage; 023import gudusoft.gsqlparser.stmt.snowflake.TCreateTaskStmt; 024import gudusoft.gsqlparser.sqlcmds.ISqlCmds; 025import gudusoft.gsqlparser.sqlcmds.SqlCmdsFactory; 026import gudusoft.gsqlparser.compiler.TContext; 027import gudusoft.gsqlparser.sqlenv.TSQLEnv; 028import gudusoft.gsqlparser.compiler.TGlobalScope; 029import gudusoft.gsqlparser.compiler.TFrame; 030import gudusoft.gsqlparser.resolver.TSQLResolver; 031import gudusoft.gsqlparser.TLog; 032import gudusoft.gsqlparser.compiler.TASTEvaluator; 033import gudusoft.gsqlparser.stmt.TRoutine; 034import gudusoft.gsqlparser.util.TSnowflakeParameterChecker; 035 036import java.io.BufferedReader; 037import java.util.ArrayList; 038import java.util.List; 039import java.util.Stack; 040 041import static gudusoft.gsqlparser.ESqlStatementType.*; 042 043/** 044 * Snowflake database SQL parser implementation. 045 * 046 * <p>This parser handles Snowflake-specific SQL syntax including: 047 * <ul> 048 * <li>Snowflake stored procedures (SQL and JavaScript)</li> 049 * <li>Snowflake-specific functions (FLATTEN, PIVOT, UNPIVOT, etc.)</li> 050 * <li>Snowflake tasks and streams</li> 051 * <li>Snowflake semi-structured data handling (VARIANT, ARRAY, OBJECT)</li> 052 * <li>Special token handling (AT, LEFT/RIGHT joins, DATE/TIME functions)</li> 053 * <li>Transaction control (BEGIN TRANSACTION, COMMIT, ROLLBACK)</li> 054 * </ul> 055 * 056 * <p><b>Design Notes:</b> 057 * <ul> 058 * <li>Extends {@link AbstractSqlParser} using the template method pattern</li> 059 * <li>Uses {@link TLexerSnowflake} for tokenization</li> 060 * <li>Uses {@link TParserSnowflake} for parsing</li> 061 * <li>Delimiter character: ';' for SQL statements</li> 062 * </ul> 063 * 064 * <p><b>Usage Example:</b> 065 * <pre> 066 * // Get Snowflake parser from factory 067 * SqlParser parser = SqlParserFactory.get(EDbVendor.dbvsnowflake); 068 * 069 * // Build context 070 * ParserContext context = new ParserContext.Builder(EDbVendor.dbvsnowflake) 071 * .sqlText("SELECT * FROM customers WHERE region = 'US'") 072 * .build(); 073 * 074 * // Parse 075 * SqlParseResult result = parser.parse(context); 076 * 077 * // Access statements 078 * TStatementList statements = result.getSqlStatements(); 079 * </pre> 080 * 081 * @see SqlParser 082 * @see AbstractSqlParser 083 * @see TLexerSnowflake 084 * @see TParserSnowflake 085 * @since 3.2.0.0 086 */ 087public class SnowflakeSqlParser extends AbstractSqlParser { 088 089 /** 090 * Construct Snowflake SQL parser. 091 * <p> 092 * Configures the parser for Snowflake database with default delimiter (;). 093 * <p> 094 * Following the original TGSqlParser pattern, the lexer and parser are 095 * created once in the constructor and reused for all parsing operations. 096 */ 097 public SnowflakeSqlParser() { 098 super(EDbVendor.dbvsnowflake); 099 this.delimiterChar = ';'; 100 this.defaultDelimiterStr = ";"; 101 102 // Create lexer once - will be reused for all parsing operations 103 this.flexer = new TLexerSnowflake(); 104 this.flexer.delimiterchar = this.delimiterChar; 105 this.flexer.defaultDelimiterStr = this.defaultDelimiterStr; 106 107 // Set parent's lexer reference for shared tokenization logic 108 this.lexer = this.flexer; 109 110 // Create parser once - will be reused for all parsing operations 111 this.fparser = new TParserSnowflake(null); 112 this.fparser.lexer = this.flexer; 113 } 114 115 // ========== Parser Components ========== 116 117 /** The Snowflake lexer used for tokenization */ 118 public TLexerSnowflake flexer; 119 120 /** SQL parser (for Snowflake statements) */ 121 private TParserSnowflake fparser; 122 123 /** Current statement being built during extraction */ 124 private TCustomSqlStatement gcurrentsqlstatement; 125 126 // Stored procedure parsing state tracking 127 private enum stored_procedure_type { 128 procedure, function, package_spec, package_body, block_with_declare, 129 block_with_begin, create_trigger, create_library, others 130 } 131 132 private enum stored_procedure_status { 133 start, is_as, body, bodyend, end 134 } 135 136 private static final int stored_procedure_nested_level = 50; 137 138 // Note: Global context and frame stack fields inherited from AbstractSqlParser: 139 // - protected TContext globalContext 140 // - protected TSQLEnv sqlEnv 141 // - protected Stack<TFrame> frameStack 142 // - protected TFrame globalFrame 143 144 // ========== AbstractSqlParser Abstract Methods Implementation ========== 145 146 /** 147 * Return the Snowflake lexer instance. 148 */ 149 @Override 150 protected TCustomLexer getLexer(ParserContext context) { 151 return this.flexer; 152 } 153 154 /** 155 * Return the Snowflake SQL parser instance with updated token list. 156 */ 157 @Override 158 protected TCustomParser getParser(ParserContext context, TSourceTokenList tokens) { 159 this.fparser.sourcetokenlist = tokens; 160 return this.fparser; 161 } 162 163 /** 164 * Snowflake does not use a secondary parser (unlike Oracle with PL/SQL). 165 */ 166 @Override 167 protected TCustomParser getSecondaryParser(ParserContext context, TSourceTokenList tokens) { 168 return null; 169 } 170 171 /** 172 * Call Snowflake-specific tokenization logic. 173 * <p> 174 * Delegates to dosnowflakesqltexttotokenlist which handles Snowflake's 175 * specific keyword recognition and token generation. 176 */ 177 @Override 178 protected void tokenizeVendorSql() { 179 dosnowflakesqltexttotokenlist(); 180 } 181 182 /** 183 * Setup Snowflake parser for raw statement extraction. 184 * <p> 185 * Snowflake uses a single parser, so we inject sqlcmds and update 186 * the token list for the main parser only. 187 */ 188 @Override 189 protected void setupVendorParsersForExtraction() { 190 // Inject sqlcmds into parser (required for make_stmt) 191 this.fparser.sqlcmds = this.sqlcmds; 192 193 // Update token list for parser 194 this.fparser.sourcetokenlist = this.sourcetokenlist; 195 } 196 197 /** 198 * Call Snowflake-specific raw statement extraction logic. 199 * <p> 200 * Delegates to dosnowflakegetrawsqlstatements which handles Snowflake's 201 * statement delimiters and stored procedure boundaries. 202 */ 203 @Override 204 protected void extractVendorRawStatements(SqlParseResult.Builder builder) { 205 int errorCount = dosnowflakegetrawsqlstatements(builder); 206 // Error count is tracked internally; errors are already added to syntaxErrors list 207 208 // Expand dollar string and single-quoted procedure bodies into token lists 209 expandDollarString(); 210 211 // Set the extracted statements in the builder 212 builder.sqlStatements(this.sqlstatements); 213 } 214 215 /** 216 * Expand dollar-delimited and single-quoted string literals in Snowflake procedure/function bodies. 217 * <p> 218 * For CREATE PROCEDURE/FUNCTION statements with LANGUAGE SQL, this method: 219 * 1. Finds string literals (starting with $$ or ') that follow AS keyword 220 * 2. Extracts and tokenizes the SQL code inside the quotes 221 * 3. Replaces the single string token with expanded tokens for proper parsing 222 * <p> 223 * This is essential for Snowflake's syntax: AS '...' or AS $$...$$ 224 */ 225 private void expandDollarString() { 226 TSourceToken st; 227 TCustomSqlStatement sql; 228 ArrayList<TSourceToken> dollarTokens = new ArrayList<>(); 229 boolean isSQLLanguage = true; 230 231 // Iterate all create procedure and create function, other sql statement just skipped 232 for (int i = 0; i < sqlstatements.size(); i++) { 233 sql = sqlstatements.get(i); 234 if (!((sql.sqlstatementtype == ESqlStatementType.sstcreateprocedure) || 235 (sql.sqlstatementtype == ESqlStatementType.sstcreatefunction))) continue; 236 237 isSQLLanguage = true; 238 for (int j = 0; j < sql.sourcetokenlist.size(); j++) { 239 st = sql.sourcetokenlist.get(j); 240 241 if (sql.sqlstatementtype == ESqlStatementType.sstcreateprocedure) { 242 if (st.tokencode == TBaseType.rrw_snowflake_language) { 243 TSourceToken lang = st.nextSolidToken(); 244 if ((lang != null) && (!lang.toString().equalsIgnoreCase("sql"))) { 245 isSQLLanguage = false; 246 } 247 } 248 } 249 250 if (!isSQLLanguage) break; 251 252 if (st.tokencode == TBaseType.sconst) { 253 if (st.toString().startsWith("$$")) { 254 dollarTokens.add(st); 255 } else if (st.toString().startsWith("'")) { 256 // https://docs.snowflake.com/en/sql-reference/sql/create-procedure 257 // string literal delimiter can be $ or ' 258 if (st.prevSolidToken().tokencode == TBaseType.rrw_as) { 259 dollarTokens.add(st); 260 } 261 } 262 } 263 }//check tokens 264 265 for (int m = dollarTokens.size() - 1; m >= 0; m--) { 266 // Token Expansion: 267 // For each identified string literal: 268 // Extracts the content between the quotes 269 // Tokenizes the extracted SQL code 270 // Verifies it's a valid code block (starts with DECLARE or BEGIN) 271 // Replaces the original quoted string with expanded tokens in the source token list 272 273 st = dollarTokens.get(m); 274 275 // Create a new parser to tokenize the procedure body 276 // Use TGSqlParser for internal tokenization (simpler than using SnowflakeSqlParser for this snippet) 277 gudusoft.gsqlparser.TGSqlParser parser = new gudusoft.gsqlparser.TGSqlParser(this.vendor); 278 279 // Extract the body content from the string literal 280 // For procedure bodies, we need special handling for single-quoted strings: 281 // - Remove outer quotes 282 // - Unescape '' to ' (SQL standard) 283 // - Preserve backslash-quote-quote patterns: \'' -> \' 284 // (backslash + escaped quote should become backslash + single quote for re-parsing) 285 // See mantisbt issue 4298 for details 286 String tokenStr = st.toString(); 287 String bodyContent; 288 if (tokenStr.startsWith("$$")) { 289 // Dollar-quoted string: just strip the $$ delimiters 290 bodyContent = TBaseType.getStringInsideLiteral(tokenStr); 291 } else if (tokenStr.startsWith("'")) { 292 // Single-quoted string: custom unescaping 293 // Do NOT use getStringInsideLiteral() as it incorrectly handles \' 294 bodyContent = tokenStr.substring(1, tokenStr.length() - 1); 295 // Replace \'' with placeholder, unescape '', then restore \' 296 // \'' in original means backslash + escaped quote -> value \' 297 // In re-parsed body, \' is the escape sequence for this 298 bodyContent = bodyContent.replace("\\''", "\u0000BSQQ\u0000"); 299 bodyContent = bodyContent.replace("''", "'"); 300 bodyContent = bodyContent.replace("\u0000BSQQ\u0000", "\\'"); 301 } else { 302 bodyContent = tokenStr; 303 } 304 parser.sqltext = bodyContent; 305 306 TSourceToken startQuote = new TSourceToken(st.toString().substring(0, 1)); 307 startQuote.tokencode = TBaseType.lexspace; // Set as space, can be ignored during parsing, but preserved in toString() 308 TSourceToken endQuote = new TSourceToken(st.toString().substring(0, 1)); 309 endQuote.tokencode = TBaseType.lexspace; 310 311 // use getrawsqlstatements() instead of tokenizeSqltext() to get the source token list because 312 // some token will be transformed to other token, which will be processed in dosnowflakegetrawsqlstatements() 313 parser.getrawsqlstatements(); 314 315 TSourceToken st2; 316 boolean isValidBlock = false; 317 for (int k = 0; k < parser.sourcetokenlist.size(); k++) { 318 st2 = parser.sourcetokenlist.get(k); 319 if (st2.isnonsolidtoken()) continue; 320 if ((st2.tokencode == TBaseType.rrw_declare) || (st2.tokencode == TBaseType.rrw_begin)) { 321 isValidBlock = true; 322 } 323 break; 324 } 325 326 if (isValidBlock) { 327 TSourceToken semiColon = null; 328 st.tokenstatus = ETokenStatus.tsdeleted; 329 int startPosOfThisSQL = sql.getStartToken().posinlist; 330 331 sql.sourcetokenlist.add((st.posinlist++) - startPosOfThisSQL, startQuote); // Add opening quote 332 for (int k = 0; k < parser.sourcetokenlist.size(); k++) { 333 st2 = parser.sourcetokenlist.get(k); 334 if (st2.tokencode == ';') { 335 semiColon = st2; 336 TSourceToken prevSolidToken = st2.prevSolidToken(); 337 if ((prevSolidToken != null) && (prevSolidToken.tokencode == TBaseType.rrw_begin)) { 338 // begin; => begin transaction; 339 prevSolidToken.tokencode = TBaseType.rrw_snowflake_begin_transaction; 340 } 341 } 342 if ((st2.tokencode == TBaseType.rrw_snowflake_work) || (st2.tokencode == TBaseType.rrw_snowflake_transaction)) { 343 // begin work; => begin transaction; 344 TSourceToken prevSolidToken = st2.prevSolidToken(); 345 if ((prevSolidToken != null) && (prevSolidToken.tokencode == TBaseType.rrw_begin)) { 346 // begin; => begin transaction; 347 prevSolidToken.tokencode = TBaseType.rrw_snowflake_begin_transaction; 348 } 349 } 350 sql.sourcetokenlist.add((st.posinlist++) - startPosOfThisSQL, st2); 351 } 352 if (semiColon != null) { 353 if (semiColon.prevSolidToken().tokencode == TBaseType.rrw_end) { 354 // Set as space, can be ignored during parsing, but preserved in toString() 355 semiColon.tokencode = TBaseType.lexspace; 356 } 357 } 358 359 sql.sourcetokenlist.add((st.posinlist++) - startPosOfThisSQL, endQuote); // Add closing quote 360 TBaseType.resetTokenChain(sql.sourcetokenlist, 0); // Reset token chain to ensure new tokens are accessible in toString() 361 } 362 } 363 364 dollarTokens.clear(); 365 }//statement 366 } 367 368 /** 369 * Perform full parsing of statements with syntax checking. 370 * <p> 371 * This method orchestrates the parsing of all statements. 372 */ 373 @Override 374 protected TStatementList performParsing(ParserContext context, 375 TCustomParser parser, 376 TCustomParser secondaryParser, 377 TSourceTokenList tokens, 378 TStatementList rawStatements) { 379 // Store references 380 this.fparser = (TParserSnowflake) parser; 381 this.sourcetokenlist = tokens; 382 this.parserContext = context; 383 384 // Use the raw statements passed from AbstractSqlParser.parse() 385 this.sqlstatements = rawStatements; 386 387 // Initialize statement parsing infrastructure 388 this.sqlcmds = SqlCmdsFactory.get(vendor); 389 390 // Inject sqlcmds into parser (required for make_stmt and other methods) 391 this.fparser.sqlcmds = this.sqlcmds; 392 393 // Initialize global context for semantic analysis 394 initializeGlobalContext(); 395 396 // Parse each statement with exception handling for robustness 397 for (int i = 0; i < sqlstatements.size(); i++) { 398 TCustomSqlStatement stmt = sqlstatements.getRawSql(i); 399 400 try { 401 stmt.setFrameStack(frameStack); 402 403 // Parse the statement 404 int parseResult = stmt.parsestatement(null, false, context.isOnlyNeedRawParseTree()); 405 406 // Handle error recovery for CREATE TABLE/INDEX 407 boolean doRecover = TBaseType.ENABLE_ERROR_RECOVER_IN_CREATE_TABLE; 408 if (doRecover && ((parseResult != 0) || (stmt.getErrorCount() > 0))) { 409 handleCreateTableErrorRecovery(stmt); 410 } 411 412 // Collect syntax errors 413 if ((parseResult != 0) || (stmt.getErrorCount() > 0)) { 414 copyErrorsFromStatement(stmt); 415 } 416 417 } catch (Exception ex) { 418 // Use inherited exception handler from AbstractSqlParser 419 // This provides consistent error handling across all database parsers 420 handleStatementParsingException(stmt, i, ex); 421 continue; 422 } 423 } 424 425 // Clean up frame stack 426 if (globalFrame != null) { 427 globalFrame.popMeFromStack(frameStack); 428 } 429 430 return this.sqlstatements; 431 } 432 433 // Note: initializeGlobalContext() inherited from AbstractSqlParser 434 // Note: No override of afterStatementParsed() needed - default (no-op) is appropriate for Snowflake 435 436 /** 437 * Handle error recovery for CREATE TABLE/INDEX statements. 438 */ 439 private void handleCreateTableErrorRecovery(TCustomSqlStatement stmt) { 440 if (((stmt.sqlstatementtype == ESqlStatementType.sstcreatetable) 441 || (stmt.sqlstatementtype == ESqlStatementType.sstcreateindex)) 442 && (!TBaseType.c_createTableStrictParsing)) { 443 444 int nested = 0; 445 boolean isIgnore = false, isFoundIgnoreToken = false; 446 TSourceToken firstIgnoreToken = null; 447 448 for (int k = 0; k < stmt.sourcetokenlist.size(); k++) { 449 TSourceToken st = stmt.sourcetokenlist.get(k); 450 if (isIgnore) { 451 if (st.issolidtoken() && (st.tokencode != ';')) { 452 isFoundIgnoreToken = true; 453 if (firstIgnoreToken == null) { 454 firstIgnoreToken = st; 455 } 456 } 457 if (st.tokencode != ';') { 458 st.tokencode = TBaseType.sqlpluscmd; 459 } 460 continue; 461 } 462 if (st.tokencode == (int) ')') { 463 nested--; 464 if (nested == 0) { 465 boolean isSelect = false; 466 TSourceToken st1 = st.searchToken(TBaseType.rrw_as, 1); 467 if (st1 != null) { 468 TSourceToken st2 = st.searchToken((int) '(', 2); 469 if (st2 != null) { 470 TSourceToken st3 = st.searchToken(TBaseType.rrw_select, 3); 471 isSelect = (st3 != null); 472 } 473 } 474 if (!isSelect) isIgnore = true; 475 } 476 } else if (st.tokencode == (int) '(') { 477 nested++; 478 } 479 } 480 481 if (isFoundIgnoreToken) { 482 stmt.clearError(); 483 stmt.parsestatement(null, false); 484 } 485 } 486 } 487 488 /** 489 * Perform Snowflake-specific semantic analysis using TSQLResolver. 490 */ 491 @Override 492 protected void performSemanticAnalysis(ParserContext context, TStatementList statements) { 493 if (TBaseType.isEnableResolver() && getSyntaxErrors().isEmpty()) { 494 TSQLResolver resolver = new TSQLResolver(globalContext, statements); 495 resolver.resolve(); 496 } 497 } 498 499 /** 500 * Perform interpretation/evaluation on parsed statements. 501 */ 502 @Override 503 protected void performInterpreter(ParserContext context, TStatementList statements) { 504 if (TBaseType.ENABLE_INTERPRETER && getSyntaxErrors().isEmpty()) { 505 TLog.clearLogs(); 506 TGlobalScope interpreterScope = new TGlobalScope(sqlEnv); 507 TLog.enableInterpreterLogOnly(); 508 TASTEvaluator astEvaluator = new TASTEvaluator(statements, interpreterScope); 509 astEvaluator.eval(); 510 } 511 } 512 513 // ========== Snowflake-Specific Tokenization ========== 514 515 /** 516 * Snowflake-specific tokenization logic. 517 * <p> 518 * Extracted from: TGSqlParser.dosnowflakesqltexttotokenlist() (lines 3289-3439) 519 */ 520 private void dosnowflakesqltexttotokenlist() { 521 522 boolean insqlpluscmd = false; 523 boolean isvalidplace = true; 524 boolean waitingreturnforfloatdiv = false; 525 boolean waitingreturnforsemicolon = false; 526 boolean continuesqlplusatnewline = false; 527 528 TSourceToken lct = null, prevst = null; 529 530 TSourceToken asourcetoken, lcprevst; 531 int yychar; 532 533 asourcetoken = getanewsourcetoken(); 534 if (asourcetoken == null) return; 535 yychar = asourcetoken.tokencode; 536 537 while (yychar > 0) { 538 sourcetokenlist.add(asourcetoken); 539 switch (yychar) { 540 case TBaseType.cmtdoublehyphen: 541 case TBaseType.cmtslashstar: 542 case TBaseType.lexspace: { 543 if (insqlpluscmd) { 544 asourcetoken.insqlpluscmd = true; 545 } 546 break; 547 } 548 case TBaseType.lexnewline: { 549 if (insqlpluscmd) { 550 insqlpluscmd = false; 551 isvalidplace = true; 552 553 if (continuesqlplusatnewline) { 554 insqlpluscmd = true; 555 isvalidplace = false; 556 asourcetoken.insqlpluscmd = true; 557 } 558 } 559 560 if (waitingreturnforsemicolon) { 561 isvalidplace = true; 562 } 563 if (waitingreturnforfloatdiv) { 564 isvalidplace = true; 565 lct.tokencode = TBaseType.sqlpluscmd; 566 if (lct.tokentype != ETokenType.ttslash) { 567 lct.tokentype = ETokenType.ttsqlpluscmd; 568 } 569 } 570 flexer.insqlpluscmd = insqlpluscmd; 571 break; 572 } //case newline 573 default: { 574 //solid tokentext 575 continuesqlplusatnewline = false; 576 waitingreturnforsemicolon = false; 577 waitingreturnforfloatdiv = false; 578 if (insqlpluscmd) { 579 asourcetoken.insqlpluscmd = true; 580 if (asourcetoken.getAstext().equalsIgnoreCase("-")) { 581 continuesqlplusatnewline = true; 582 } 583 } else { 584 if (asourcetoken.tokentype == ETokenType.ttsemicolon) { 585 waitingreturnforsemicolon = true; 586 } 587 if ((asourcetoken.tokentype == ETokenType.ttslash) 588 // and (isvalidplace or sourcetokenlist.TokenBeforeCurToken(#10,false,false,false)) then 589 && (isvalidplace || (IsValidPlaceForDivToSqlplusCmd(sourcetokenlist, asourcetoken.posinlist)))) { 590 lct = asourcetoken; 591 waitingreturnforfloatdiv = true; 592 } 593 if ((isvalidplace) && isvalidsqlpluscmdInPostgresql(asourcetoken.toString())) { 594 asourcetoken.tokencode = TBaseType.sqlpluscmd; 595 if (asourcetoken.tokentype != ETokenType.ttslash) { 596 asourcetoken.tokentype = ETokenType.ttsqlpluscmd; 597 } 598 insqlpluscmd = true; 599 flexer.insqlpluscmd = insqlpluscmd; 600 } 601 } 602 isvalidplace = false; 603 604 // the inner keyword tokentext should be convert to TBaseType.ident when 605 // next solid tokentext is not join 606 607 if (prevst != null) { 608 if (prevst.tokencode == TBaseType.rrw_inner)//flexer.getkeywordvalue("INNER")) 609 { 610 if (asourcetoken.tokencode != flexer.getkeywordvalue("JOIN")) { 611 prevst.tokencode = TBaseType.ident; 612 } 613 } 614 615 616 if ((prevst.tokencode == TBaseType.rrw_not) 617 && (asourcetoken.tokencode == flexer.getkeywordvalue("DEFERRABLE"))) { 618 prevst.tokencode = flexer.getkeywordvalue("NOT_DEFERRABLE"); 619 } 620 621 } 622 623 if (asourcetoken.tokencode == TBaseType.rrw_inner) { 624 prevst = asourcetoken; 625 } else if (asourcetoken.tokencode == TBaseType.rrw_not) { 626 prevst = asourcetoken; 627 } else { 628 prevst = null; 629 } 630 631 632 } 633 } 634 635 //flexer.yylexwrap(asourcetoken); 636 asourcetoken = getanewsourcetoken(); 637 if (asourcetoken != null) { 638 yychar = asourcetoken.tokencode; 639 } else { 640 yychar = 0; 641 642 if (waitingreturnforfloatdiv) { // / at the end of line treat as sqlplus command 643 //isvalidplace = true; 644 lct.tokencode = TBaseType.sqlpluscmd; 645 if (lct.tokentype != ETokenType.ttslash) { 646 lct.tokentype = ETokenType.ttsqlpluscmd; 647 } 648 } 649 650 } 651 652 if ((yychar == 0) && (prevst != null)) { 653 if (prevst.tokencode == TBaseType.rrw_inner)// flexer.getkeywordvalue("RW_INNER")) 654 { 655 prevst.tokencode = TBaseType.ident; 656 } 657 } 658 659 660 } // while 661 662 663 } 664 665 // ========== Snowflake-Specific Raw Statement Extraction ========== 666 667 /** 668 * Snowflake-specific raw statement extraction logic. 669 * <p> 670 * Extracted from: TGSqlParser.dosnowflakegetrawsqlstatements() (lines 8646-9388) 671 */ 672 private int dosnowflakegetrawsqlstatements(SqlParseResult.Builder builder) { 673 int waitingEnd = 0; 674 boolean foundEnd = false; 675 676 int waitingEnds[] = new int[stored_procedure_nested_level]; 677 stored_procedure_type sptype[] = new stored_procedure_type[stored_procedure_nested_level]; 678 stored_procedure_status procedure_status[] = new stored_procedure_status[stored_procedure_nested_level]; 679 boolean endBySlashOnly = true; 680 int nestedProcedures = 0, nestedParenthesis = 0; 681 boolean inDollarBody = false; 682 683 if (TBaseType.assigned(sqlstatements)) sqlstatements.clear(); 684 if (!TBaseType.assigned(sourcetokenlist)) return -1; 685 686 gcurrentsqlstatement = null; 687 EFindSqlStateType gst = EFindSqlStateType.stnormal; 688 TSourceToken lcprevsolidtoken = null, ast = null; 689 690 for (int i = 0; i < sourcetokenlist.size(); i++) { 691 692 if ((ast != null) && (ast.issolidtoken())) 693 lcprevsolidtoken = ast; 694 695 ast = sourcetokenlist.get(i); 696 sourcetokenlist.curpos = i; 697 698 if ((ast.tokencode == TBaseType.rrw_right) || (ast.tokencode == TBaseType.rrw_left)) { 699 TSourceToken stLparen = ast.searchToken('(', 1); 700 if (stLparen != null) { //match ( 701 ast.tokencode = TBaseType.ident; 702 } 703 TSourceToken stNextToken = ast.nextSolidToken(); 704 if ((stNextToken != null) && ((stNextToken.tokencode == TBaseType.rrw_join) || (stNextToken.tokencode == TBaseType.rrw_outer))) { 705 if (ast.tokencode == TBaseType.rrw_left) { 706 ast.tokencode = TBaseType.rrw_snowflake_left_join; 707 } else { 708 ast.tokencode = TBaseType.rrw_snowflake_right_join; 709 } 710 } 711 } else if (ast.tokencode == TBaseType.rrw_snowflake_at) { 712 TSourceToken stLparen = ast.searchToken('(', 1); 713 if (stLparen != null) { //match ( 714 ast.tokencode = TBaseType.rrw_snowflake_at_before_parenthesis; 715 } 716 } else if (ast.tokencode == TBaseType.rrw_date) { 717 TSourceToken stLparen = ast.searchToken('(', 1); 718 if (stLparen != null) { //date ( 719 ast.tokencode = TBaseType.rrw_snowflake_date; 720 } else { 721 stLparen = ast.searchToken('.', 1); 722 if (stLparen != null) { //date ( 723 ast.tokencode = TBaseType.ident; 724 } 725 } 726 } else if (ast.tokencode == TBaseType.rrw_time) { 727 TSourceToken stLparen = ast.searchToken('(', 1); 728 if (stLparen != null) { //date ( 729 ast.tokencode = TBaseType.rrw_snowflake_time; 730 } else { 731 stLparen = ast.searchToken('.', 1); 732 if (stLparen != null) { //date ( 733 ast.tokencode = TBaseType.ident; 734 } 735 } 736 } else if (ast.tokencode == TBaseType.rrw_char) { 737 TSourceToken stLparen = ast.searchToken('(', 1); 738 if (stLparen != null) { //date ( 739 ast.tokencode = TBaseType.rrw_snowflake_char; 740 } else { 741 stLparen = ast.searchToken('.', 1); 742 if (stLparen != null) { //date ( 743 ast.tokencode = TBaseType.ident; 744 } 745 } 746 } else if (ast.tokencode == TBaseType.rrw_snowflake_window) { 747 TSourceToken stAs = ast.searchToken(TBaseType.rrw_as, 2); 748 if (stAs != null) { //date ( 749 ast.tokencode = TBaseType.rrw_snowflake_window_as; 750 } else { 751 } 752 } else if ((ast.tokencode == TBaseType.rrw_snowflake_pivot) || (ast.tokencode == TBaseType.rrw_snowflake_unpivot)) { 753 TSourceToken stLparen = ast.searchToken('(', 1); 754 if (stLparen != null) { //pivot (, unpivot 755 756 } else { 757 ast.tokencode = TBaseType.ident; 758 } 759 } else if (ast.tokencode == TBaseType.rrw_snowflake_flatten) { 760 TSourceToken stLeftParens = ast.searchToken('(', 1); 761 if (stLeftParens != null) { //flatten ( 762 763 } else { 764 ast.tokencode = TBaseType.ident; // change it to an identifier, can be used as db object name. 765 } 766 } else if (ast.tokencode == TBaseType.rrw_snowflake_offset) { 767 TSourceToken stFrom = ast.searchToken(TBaseType.rrw_from, -ast.posinlist, TBaseType.rrw_select, true); 768 if (stFrom == null) { 769 // FORM keyword before OFFSET is not found, then offset must be a column name, 770 // just like this: SELECT column1 offset FROM table2 771 ast.tokencode = TBaseType.ident; // change it to an identifier, can be used as db object name. 772 } 773 } else if (ast.tokencode == TBaseType.rrw_replace) { 774 TSourceToken stStar = ast.prevSolidToken(); 775 if (stStar.tokencode == '*') { 776 ast.tokencode = TBaseType.rrw_snowflake_replace_after_star; 777 } 778 } else if (ast.tokencode == TBaseType.rrw_snowflake_transaction) { 779 TSourceToken stBegin = ast.prevSolidToken(); 780 if ((stBegin != null) && (stBegin.tokencode == TBaseType.rrw_begin)) { 781 stBegin.tokencode = TBaseType.rrw_snowflake_begin_transaction; 782 } 783 } else if (ast.tokencode == TBaseType.rrw_begin) { 784 // begin; 785 // begin work; 786 // begin transaction; 787 TSourceToken stNext = ast.nextSolidToken(); 788 if ((stNext != null) && ((stNext.tokencode == ';') 789 || (stNext.tokencode == TBaseType.rrw_snowflake_work) || (stNext.tokencode == TBaseType.rrw_snowflake_transaction)) 790 ) { 791 ast.tokencode = TBaseType.rrw_snowflake_begin_transaction; 792 } 793 } else if ((ast.tokencode == TBaseType.rrw_snowflake_top) || (ast.tokencode == TBaseType.rrw_text) || (ast.tokencode == TBaseType.rrw_snowflake_default)) { 794 TSourceToken stPeriod = ast.nextSolidToken(); 795 if ((stPeriod != null) && (stPeriod.tokencode == '.')) { 796 ast.tokencode = TBaseType.ident; 797 } 798 } else if (ast.tokencode == TBaseType.rrw_snowflake_limit) { 799 TSourceToken stPrev = ast.prevSolidToken(); 800 if ((stPrev != null) && (stPrev.tokencode == ',')) { 801 ast.tokencode = TBaseType.ident; 802 } 803 } else if (ast.tokencode == TBaseType.ident) { 804 // check whether it is a snowflake parameter name 805 // 这个调用可能会有性能问题,因为每个ident都会调用一次 806 if (TSnowflakeParameterChecker.isSnowflakeParameter(ast.toString())) { 807 ast.tokencode = TBaseType.rrw_snowflake_parameter_name; 808 } 809 } 810 811 812 switch (gst) { 813 case sterror: { 814 if (ast.tokentype == ETokenType.ttsemicolon) { 815 gcurrentsqlstatement.sourcetokenlist.add(ast); 816 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 817 gst = EFindSqlStateType.stnormal; 818 } else { 819 gcurrentsqlstatement.sourcetokenlist.add(ast); 820 } 821 break; 822 } //sterror 823 824 case stnormal: { 825 if ((ast.tokencode == TBaseType.cmtdoublehyphen) 826 || (ast.tokencode == TBaseType.cmtslashstar) 827 || (ast.tokencode == TBaseType.lexspace) 828 || (ast.tokencode == TBaseType.lexnewline) 829 || (ast.tokentype == ETokenType.ttsemicolon)) { 830 if (gcurrentsqlstatement != null) { 831 gcurrentsqlstatement.sourcetokenlist.add(ast); 832 } 833 834 if ((lcprevsolidtoken != null) && (ast.tokentype == ETokenType.ttsemicolon)) { 835 if (lcprevsolidtoken.tokentype == ETokenType.ttsemicolon) { 836 // ;;;; continuous semicolon,treat it as comment 837 ast.tokentype = ETokenType.ttsimplecomment; 838 ast.tokencode = TBaseType.cmtdoublehyphen; 839 } 840 } 841 842 continue; 843 } 844 845 if (ast.tokencode == TBaseType.sqlpluscmd) { 846 gst = EFindSqlStateType.stsqlplus; 847 gcurrentsqlstatement = new TSqlplusCmdStatement(vendor); 848 gcurrentsqlstatement.sourcetokenlist.add(ast); 849 continue; 850 } 851 852 // find a tokentext to start sql or plsql mode 853 gcurrentsqlstatement = sqlcmds.issql(ast, gst, gcurrentsqlstatement); 854 855 if (gcurrentsqlstatement != null) { 856 if (gcurrentsqlstatement.issnowflakeplsql()) { 857 nestedProcedures = 0; 858 gst = EFindSqlStateType.ststoredprocedure; 859 gcurrentsqlstatement.sourcetokenlist.add(ast); 860 861 switch (gcurrentsqlstatement.sqlstatementtype) { 862 case sstplsql_createprocedure: 863 case sstcreateprocedure: 864 sptype[nestedProcedures] = stored_procedure_type.procedure; 865 break; 866 case sstplsql_createfunction: 867 sptype[nestedProcedures] = stored_procedure_type.function; 868 break; 869 case sstplsql_createpackage: 870 sptype[nestedProcedures] = stored_procedure_type.package_spec; 871 if (ast.searchToken(TBaseType.rrw_body, 5) != null) { 872 sptype[nestedProcedures] = stored_procedure_type.package_body; 873 } 874 break; 875 case sst_plsql_block: 876 sptype[nestedProcedures] = stored_procedure_type.block_with_declare; 877 if (ast.tokencode == TBaseType.rrw_begin) { 878 sptype[nestedProcedures] = stored_procedure_type.block_with_begin; 879 } 880 break; 881 case sstplsql_createtrigger: 882 sptype[nestedProcedures] = stored_procedure_type.create_trigger; 883 break; 884 case sstoraclecreatelibrary: 885 sptype[nestedProcedures] = stored_procedure_type.create_library; 886 break; 887 case sstplsql_createtype_placeholder: 888 gst = EFindSqlStateType.stsql; 889 break; 890 default: 891 sptype[nestedProcedures] = stored_procedure_type.others; 892 break; 893 } 894 895 if (sptype[0] == stored_procedure_type.block_with_declare) { 896 // sd 897 endBySlashOnly = false; 898 procedure_status[0] = stored_procedure_status.is_as; 899 } else if (sptype[0] == stored_procedure_type.block_with_begin) { 900 // sb 901 endBySlashOnly = false; 902 procedure_status[0] = stored_procedure_status.body; 903 } else if (sptype[0] == stored_procedure_type.procedure) { 904 // ss 905 endBySlashOnly = false; 906 procedure_status[0] = stored_procedure_status.start; 907 } else if (sptype[0] == stored_procedure_type.function) { 908 // ss 909 endBySlashOnly = false; 910 procedure_status[0] = stored_procedure_status.start; 911 } else if (sptype[0] == stored_procedure_type.package_spec) { 912 // ss 913 endBySlashOnly = false; 914 procedure_status[0] = stored_procedure_status.start; 915 } else if (sptype[0] == stored_procedure_type.package_body) { 916 // ss 917 endBySlashOnly = false; 918 procedure_status[0] = stored_procedure_status.start; 919 } else if (sptype[0] == stored_procedure_type.create_trigger) { 920 // ss 921 endBySlashOnly = false; 922 procedure_status[0] = stored_procedure_status.start; 923 //procedure_status[0] = stored_procedure_status.body; 924 } else if (sptype[0] == stored_procedure_type.create_library) { 925 // ss 926 endBySlashOnly = false; 927 procedure_status[0] = stored_procedure_status.bodyend; 928 } else { 929 // so 930 endBySlashOnly = true; 931 procedure_status[0] = stored_procedure_status.bodyend; 932 } 933 //foundEnd = false; 934 if ((ast.tokencode == TBaseType.rrw_begin) 935 || (ast.tokencode == TBaseType.rrw_package) 936 //||(ast.tokencode == TBaseType.rrw_procedure) 937 || (ast.searchToken(TBaseType.rrw_package, 4) != null) 938 ) { 939 //waitingEnd = 1; 940 waitingEnds[nestedProcedures] = 1; 941 } 942 943 } else { 944 gst = EFindSqlStateType.stsql; 945 gcurrentsqlstatement.sourcetokenlist.add(ast); 946 } 947 } else { 948 //error tokentext found 949 950 this.syntaxErrors.add(new TSyntaxError(ast.getAstext(), ast.lineNo, (ast.columnNo < 0 ? 0 : ast.columnNo) 951 , "Error when tokenlize", EErrorType.spwarning, TBaseType.MSG_WARNING_ERROR_WHEN_TOKENIZE, null, ast.posinlist)); 952 953 ast.tokentype = ETokenType.tttokenlizererrortoken; 954 gst = EFindSqlStateType.sterror; 955 956 gcurrentsqlstatement = new TUnknownSqlStatement(vendor); 957 gcurrentsqlstatement.sqlstatementtype = ESqlStatementType.sstinvalid; 958 gcurrentsqlstatement.sourcetokenlist.add(ast); 959 960 } 961 962 break; 963 } // stnormal 964 965 case stsqlplus: { 966 if (ast.insqlpluscmd) { 967 gcurrentsqlstatement.sourcetokenlist.add(ast); 968 } else { 969 gst = EFindSqlStateType.stnormal; //this tokentext must be newline, 970 gcurrentsqlstatement.sourcetokenlist.add(ast); // so add it here 971 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 972 } 973 974 break; 975 }//case stsqlplus 976 977 case stsql: { 978 if (gcurrentsqlstatement instanceof TRoutine) { 979 if (isDollarFunctionDelimiter(ast.tokencode, this.vendor)) { 980 if (inDollarBody) { 981 inDollarBody = false; 982 } else { 983 inDollarBody = true; 984 } 985 } 986 987 if (inDollarBody) { 988 gcurrentsqlstatement.sourcetokenlist.add(ast); 989 continue; 990 } 991 } else if (gcurrentsqlstatement instanceof TCreateTaskStmt) { 992 if (ast.tokencode == TBaseType.rrw_as) { 993 TSourceToken tmpNext = ast.nextSolidToken(); 994 if ((tmpNext != null) && (tmpNext.tokencode == TBaseType.rrw_begin)) { 995 // begin ... end block in create task statement, mantisbt/view.php?id=3531 996 997 gst = EFindSqlStateType.ststoredprocedure; 998 nestedProcedures = 0; 999 procedure_status[nestedProcedures] = stored_procedure_status.body; 1000 waitingEnds[nestedProcedures] = 0; 1001 gcurrentsqlstatement.sourcetokenlist.add(ast); 1002 continue; 1003 } 1004 } 1005 } 1006 1007 if (ast.tokentype == ETokenType.ttsemicolon) { 1008 gst = EFindSqlStateType.stnormal; 1009 gcurrentsqlstatement.sourcetokenlist.add(ast); 1010 gcurrentsqlstatement.semicolonended = ast; 1011 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 1012 continue; 1013 } 1014 1015 if (sourcetokenlist.sqlplusaftercurtoken()) //most probaly is / cmd 1016 { 1017 gst = EFindSqlStateType.stnormal; 1018 gcurrentsqlstatement.sourcetokenlist.add(ast); 1019 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 1020 continue; 1021 } 1022 gcurrentsqlstatement.sourcetokenlist.add(ast); 1023 break; 1024 }//case stsql 1025 1026 case ststoredprocedure: { 1027 if (procedure_status[nestedProcedures] != stored_procedure_status.bodyend) { 1028 gcurrentsqlstatement.sourcetokenlist.add(ast); 1029 } 1030 1031 switch (procedure_status[nestedProcedures]) { 1032 case start: 1033 if ((ast.tokencode == TBaseType.rrw_as) || (ast.tokencode == TBaseType.rrw_is)) { 1034 // s1 1035 if (sptype[nestedProcedures] != stored_procedure_type.create_trigger) { 1036 if ((sptype[0] == stored_procedure_type.package_spec) && (nestedProcedures > 0)) { 1037 //when it's a package specification, only top level accept as/is 1038 } else { 1039 procedure_status[nestedProcedures] = stored_procedure_status.is_as; 1040 if (ast.searchToken("language", 1) != null) { 1041 // if as language is used in create function, then switch state to stored_procedure_status.body directly. 1042// CREATE OR REPLACE FUNCTION THING.addressparse(p_addressline1 VARCHAR2) RETURN VARCHAR2 AUTHID DEFINER 1043// as Language JAVA NAME 'AddressParser.parse(java.lang.String) return java.lang.String'; 1044// / 1045 if (nestedProcedures == 0) { 1046 // procedure_status[nestedProcedures] = stored_procedure_status.bodyend; 1047 gst = EFindSqlStateType.stsql; 1048 } else { 1049 procedure_status[nestedProcedures] = stored_procedure_status.body; 1050 nestedProcedures--; 1051 //if (nestedProcedures > 0){ nestedProcedures--;} 1052 } 1053 1054 } 1055 } 1056 } 1057 } else if (ast.tokencode == TBaseType.rrw_begin) { 1058 // s4 1059 if (sptype[nestedProcedures] == stored_procedure_type.create_trigger) 1060 waitingEnds[nestedProcedures]++; 1061 1062 if (nestedProcedures > 0) { 1063 nestedProcedures--; 1064 } 1065 procedure_status[nestedProcedures] = stored_procedure_status.body; 1066 } else if (ast.tokencode == TBaseType.rrw_end) { 1067 //s10 1068 if ((nestedProcedures > 0) && (waitingEnds[nestedProcedures - 1] == 1) 1069 && ((sptype[nestedProcedures - 1] == stored_procedure_type.package_body) 1070 || (sptype[nestedProcedures - 1] == stored_procedure_type.package_spec))) { 1071 nestedProcedures--; 1072 procedure_status[nestedProcedures] = stored_procedure_status.bodyend; 1073 } 1074 } else if ((ast.tokencode == TBaseType.rrw_procedure) || (ast.tokencode == TBaseType.rrw_function)) { 1075 //s3 1076 if ((nestedProcedures > 0) && (waitingEnds[nestedProcedures] == 0) 1077 && (procedure_status[nestedProcedures - 1] == stored_procedure_status.is_as)) { 1078 nestedProcedures--; 1079 nestedProcedures++; 1080 waitingEnds[nestedProcedures] = 0; 1081 procedure_status[nestedProcedures] = stored_procedure_status.start; 1082 } 1083 } else if ((sptype[nestedProcedures] == stored_procedure_type.create_trigger) && (ast.tokencode == TBaseType.rrw_declare)) { 1084 procedure_status[nestedProcedures] = stored_procedure_status.is_as; 1085 } else if ((sptype[nestedProcedures] == stored_procedure_type.create_trigger) && (ast.tokentype == ETokenType.ttslash) && (ast.tokencode == TBaseType.sqlpluscmd)) { 1086 // TPlsqlStatementParse(asqlstatement).TerminatorToken := ast; 1087 ast.tokenstatus = ETokenStatus.tsignorebyyacc; 1088 gst = EFindSqlStateType.stnormal; 1089 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 1090 1091 //make / a sqlplus cmd 1092 gcurrentsqlstatement = new TSqlplusCmdStatement(vendor); 1093 gcurrentsqlstatement.sourcetokenlist.add(ast); 1094 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 1095 } else if ((sptype[nestedProcedures] == stored_procedure_type.create_trigger)) { 1096 if (ast.tokencode == TBaseType.rrw_trigger) { 1097 TSourceToken compoundSt = ast.searchToken(TBaseType.rrw_oracle_compound, -1); 1098 if (compoundSt != null) { 1099 //it's trigger with compound trigger block 1100 procedure_status[nestedProcedures] = stored_procedure_status.body; 1101 waitingEnds[nestedProcedures]++; 1102 } 1103 } 1104 } else if ((sptype[nestedProcedures] == stored_procedure_type.function) && (ast.tokencode == TBaseType.rrw_teradata_using)) { 1105 if ((ast.searchToken("aggregate", -1) != null) || (ast.searchToken("pipelined", -1) != null)) { 1106 if (nestedProcedures == 0) { 1107 // procedure_status[nestedProcedures] = stored_procedure_status.bodyend; 1108 gst = EFindSqlStateType.stsql; 1109 } else { 1110 procedure_status[nestedProcedures] = stored_procedure_status.body; 1111 nestedProcedures--; 1112 } 1113 } 1114 1115 } else { 1116 //other tokens, do nothing 1117 if (ast.tokencode == TBaseType.rrw_snowflake_language) { 1118 // check next token which is the language used by this stored procedure 1119 TSourceToken nextSt = ast.nextSolidToken(); 1120 if (nextSt != null) { 1121 if (gcurrentsqlstatement instanceof TRoutine) { // can be TCreateProcedureStmt or TCreateFunctionStmt 1122 TRoutine p = (TRoutine) gcurrentsqlstatement; 1123 p.setRoutineLanguage(nextSt.toString()); 1124 //System.out.println("Find snowflake procedure language: "+p.getRoutineLanguage()); 1125 if (p.getRoutineLanguage().toString().equalsIgnoreCase("javascript")) { 1126 // procedure 中出现 javascript, 则碰到 semicolon 可能是整个 procedure 语句解释,因此可以设为 stsql 状态 1127 // 但因为 $$body$$ 已经被解析为分离的token,因此需要在 stsql 中忽略这些$$body$$中的token 1128 gst = EFindSqlStateType.stsql; 1129 } 1130 } 1131 } 1132 } 1133 } 1134 break; 1135 case is_as: 1136 if ((ast.tokencode == TBaseType.rrw_procedure) || (ast.tokencode == TBaseType.rrw_function)) { 1137 // s2 1138 nestedProcedures++; 1139 waitingEnds[nestedProcedures] = 0; 1140 procedure_status[nestedProcedures] = stored_procedure_status.start; 1141 1142 if (nestedProcedures > stored_procedure_nested_level - 1) { 1143 gst = EFindSqlStateType.sterror; 1144 nestedProcedures--; 1145 } 1146 1147 } else if (ast.tokencode == TBaseType.rrw_begin) { 1148 // s5 1149 if ((nestedProcedures == 0) && 1150 ((sptype[nestedProcedures] == stored_procedure_type.package_body) 1151 || (sptype[nestedProcedures] == stored_procedure_type.package_spec))) { 1152 //top level package or package body's BEGIN keyword already count, 1153 // so don't increase waitingEnds[nestedProcedures] here 1154 1155 } else { 1156 waitingEnds[nestedProcedures]++; 1157 } 1158 procedure_status[nestedProcedures] = stored_procedure_status.body; 1159 } else if (ast.tokencode == TBaseType.rrw_end) { 1160 // s6 1161 if ((nestedProcedures == 0) && (waitingEnds[nestedProcedures] == 1) && 1162 ((sptype[nestedProcedures] == stored_procedure_type.package_body) || (sptype[nestedProcedures] == stored_procedure_type.package_spec))) { 1163 procedure_status[nestedProcedures] = stored_procedure_status.bodyend; 1164 waitingEnds[nestedProcedures]--; 1165 } else { 1166 waitingEnds[nestedProcedures]--; 1167 } 1168 } else if (ast.tokencode == TBaseType.rrw_case) { 1169// if (ast.searchToken(TBaseType.rrw_end,-1) == null){ 1170// //this is not case after END 1171// waitingEnds[nestedProcedures]++; 1172// } 1173 if (ast.searchToken(';', 1) == null) { 1174 //this is not case before ; 1175 waitingEnds[nestedProcedures]++; 1176 } 1177 } else { 1178 //other tokens, do nothing 1179 } 1180 break; 1181 case body: 1182 if ((ast.tokencode == TBaseType.rrw_begin)) { 1183 waitingEnds[nestedProcedures]++; 1184 } else if (ast.tokencode == TBaseType.rrw_if) { 1185 if (ast.searchToken(TBaseType.rrw_snowflake_exists, 1) != null) { 1186 //drop table if exists SANDBOX.ANALYSIS_CONTENT.TABLEAU_DATES; 1187 // don't need END for the above if exists clause 1188 } else if (ast.searchToken(';', 2) == null) { 1189 //this is not if before ; 1190 1191 // 2015-02-27, change 1 to 2 make it able to detect label name after case 1192 // like this: END CASE l1; 1193 waitingEnds[nestedProcedures]++; 1194 } 1195 } else if (ast.tokencode == TBaseType.rrw_for) { 1196 if (ast.searchToken(';', 2) == null) { 1197 //this is not for before ; 1198 1199 // 2015-02-27, change 1 to 2 make it able to detect label name after case 1200 // like this: END CASE l1; 1201 waitingEnds[nestedProcedures]++; 1202 } 1203 } else if (ast.tokencode == TBaseType.rrw_case) { 1204// if (ast.searchToken(TBaseType.rrw_end,-1) == null){ 1205// //this is not case after END 1206// waitingEnds[nestedProcedures]++; 1207// } 1208 if (ast.searchToken(';', 2) == null) { 1209 //this is not case before ; 1210 if (ast.searchToken(TBaseType.rrw_end, -1) == null) { 1211 waitingEnds[nestedProcedures]++; 1212 } 1213 } 1214 } else if (ast.tokencode == TBaseType.rrw_loop) { 1215 if (!((ast.searchToken(TBaseType.rrw_end, -1) != null) 1216 && (ast.searchToken(';', 2) != null))) { 1217 // exclude loop like this: 1218 // end loop [labelname]; 1219 waitingEnds[nestedProcedures]++; 1220 } 1221 1222// if (ast.searchToken(TBaseType.rrw_end,-1) == null){ 1223// //this is not loop after END 1224// waitingEnds[nestedProcedures]++; 1225//// } 1226//// if (ast.searchToken(';',2) == null){ 1227//// //this is no loop before ; 1228//// waitingEnds[nestedProcedures]++; 1229// } else if (ast.searchToken(TBaseType.rrw_null,1) != null){ 1230// // mantis bug tracking system: #65 1231// waitingEnds[nestedProcedures]++; 1232// } 1233 } else if (ast.tokencode == TBaseType.rrw_end) { 1234 //foundEnd = true; 1235 waitingEnds[nestedProcedures]--; 1236 //if (waitingEnd < 0) { waitingEnd = 0;} 1237 if (waitingEnds[nestedProcedures] == 0) { 1238 if (nestedProcedures == 0) { 1239 // s7 1240 procedure_status[nestedProcedures] = stored_procedure_status.bodyend; 1241 } else { 1242 // s71 1243 nestedProcedures--; 1244 procedure_status[nestedProcedures] = stored_procedure_status.is_as; 1245 } 1246 } 1247 } else if ((waitingEnds[nestedProcedures] == 0) && (ast.tokentype == ETokenType.ttslash) && (ast.tokencode == TBaseType.sqlpluscmd)) //and (prevst.NewlineIsLastTokenInTailerToken)) then 1248 { 1249 //sql ref: c:\prg\gsqlparser\Test\TestCases\oracle\createtrigger.sql, line 53 1250 ast.tokenstatus = ETokenStatus.tsignorebyyacc; 1251 gst = EFindSqlStateType.stnormal; 1252 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 1253 1254 //make / a sqlplus cmd 1255 gcurrentsqlstatement = new TSqlplusCmdStatement(vendor); 1256 gcurrentsqlstatement.sourcetokenlist.add(ast); 1257 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 1258 } 1259 break; 1260 case bodyend: 1261 if ((ast.tokentype == ETokenType.ttslash) && (ast.tokencode == TBaseType.sqlpluscmd)) //and (prevst.NewlineIsLastTokenInTailerToken)) then 1262 { 1263 // TPlsqlStatementParse(asqlstatement).TerminatorToken := ast; 1264 ast.tokenstatus = ETokenStatus.tsignorebyyacc; 1265 gst = EFindSqlStateType.stnormal; 1266 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 1267 1268 //make / a sqlplus cmd 1269 gcurrentsqlstatement = new TSqlplusCmdStatement(vendor); 1270 gcurrentsqlstatement.sourcetokenlist.add(ast); 1271 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 1272 } else if ((ast.tokentype == ETokenType.ttperiod) && (sourcetokenlist.returnaftercurtoken(false)) && (sourcetokenlist.returnbeforecurtoken(false))) { // single dot at a seperate line 1273 ast.tokenstatus = ETokenStatus.tsignorebyyacc; 1274 gst = EFindSqlStateType.stnormal; 1275 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 1276 1277 //make ttperiod a sqlplus cmd 1278 gcurrentsqlstatement = new TSqlplusCmdStatement(vendor); 1279 gcurrentsqlstatement.sourcetokenlist.add(ast); 1280 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 1281 } else if ((ast.searchToken(TBaseType.rrw_package, 1) != null) && (!endBySlashOnly)) { 1282 gcurrentsqlstatement.sourcetokenlist.add(ast); 1283 gst = EFindSqlStateType.stnormal; 1284 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 1285 } else if ((ast.searchToken(TBaseType.rrw_procedure, 1) != null) && (!endBySlashOnly)) { 1286 gcurrentsqlstatement.sourcetokenlist.add(ast); 1287 gst = EFindSqlStateType.stnormal; 1288 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 1289 } else if ((ast.searchToken(TBaseType.rrw_function, 1) != null) && (!endBySlashOnly)) { 1290 gcurrentsqlstatement.sourcetokenlist.add(ast); 1291 gst = EFindSqlStateType.stnormal; 1292 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 1293 } else if ((ast.searchToken(TBaseType.rrw_create, 1) != null) && (ast.searchToken(TBaseType.rrw_package, 4) != null) && (!endBySlashOnly)) { 1294 gcurrentsqlstatement.sourcetokenlist.add(ast); 1295 gst = EFindSqlStateType.stnormal; 1296 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 1297 } else if ((ast.searchToken(TBaseType.rrw_create, 1) != null) && (ast.searchToken(TBaseType.rrw_library, 4) != null) && (!endBySlashOnly)) { 1298 gcurrentsqlstatement.sourcetokenlist.add(ast); 1299 gst = EFindSqlStateType.stnormal; 1300 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 1301 } else if ((ast.searchToken(TBaseType.rrw_alter, 1) != null) && (ast.searchToken(TBaseType.rrw_trigger, 2) != null) && (!endBySlashOnly)) { 1302 gcurrentsqlstatement.sourcetokenlist.add(ast); 1303 gst = EFindSqlStateType.stnormal; 1304 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 1305 } else if ((ast.searchToken(TBaseType.rrw_select, 1) != null) && (!endBySlashOnly)) { 1306 gcurrentsqlstatement.sourcetokenlist.add(ast); 1307 gst = EFindSqlStateType.stnormal; 1308 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 1309 } else if ((ast.searchToken(TBaseType.rrw_commit, 1) != null) && (!endBySlashOnly)) { 1310 gcurrentsqlstatement.sourcetokenlist.add(ast); 1311 gst = EFindSqlStateType.stnormal; 1312 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 1313 } else if ((ast.searchToken(TBaseType.rrw_grant, 1) != null) && 1314 (ast.searchToken(TBaseType.rrw_execute, 2) != null) && (!endBySlashOnly)) { 1315 gcurrentsqlstatement.sourcetokenlist.add(ast); 1316 gst = EFindSqlStateType.stnormal; 1317 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 1318 } else if ((gcurrentsqlstatement instanceof TCreateTaskStmt) && (ast.tokencode == ';')) { 1319 gcurrentsqlstatement.sourcetokenlist.add(ast); 1320 gst = EFindSqlStateType.stnormal; 1321 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 1322 } else { 1323 gcurrentsqlstatement.sourcetokenlist.add(ast); 1324 } 1325 break; 1326 case end: 1327 break; 1328 default: 1329 break; 1330 } 1331 1332 1333 if (ast.tokencode == TBaseType.sqlpluscmd) { 1334 //change tokencode back to keyword or TBaseType.ident, because sqlplus cmd 1335 //in a sql statement(almost is plsql block) is not really a sqlplus cmd 1336 int m = flexer.getkeywordvalue(ast.getAstext()); 1337 if (m != 0) { 1338 ast.tokencode = m; 1339 } else if (ast.tokentype == ETokenType.ttslash) { 1340 ast.tokencode = '/'; 1341 } else { 1342 ast.tokencode = TBaseType.ident; 1343 } 1344 } 1345 1346 final int wrapped_keyword_max_pos = 20; 1347 if ((ast.tokencode == TBaseType.rrw_wrapped) && (ast.posinlist - gcurrentsqlstatement.sourcetokenlist.get(0).posinlist < wrapped_keyword_max_pos)) { 1348 if (gcurrentsqlstatement instanceof TCommonStoredProcedureSqlStatement) { 1349 ((TCommonStoredProcedureSqlStatement) gcurrentsqlstatement).setWrapped(true); 1350 } 1351 1352 if (gcurrentsqlstatement instanceof TPlsqlCreatePackage) { 1353 if (ast.prevSolidToken() != null) { 1354 ((TPlsqlCreatePackage) gcurrentsqlstatement).setPackageName(fparser.getNf().createObjectNameWithPart(ast.prevSolidToken())); 1355 } 1356 } 1357 } 1358 1359 break; 1360 } //ststoredprocedure 1361 } //switch 1362 }//for 1363 1364 //last statement 1365 if ((gcurrentsqlstatement != null) && 1366 ((gst == EFindSqlStateType.stsqlplus) || (gst == EFindSqlStateType.stsql) || (gst == EFindSqlStateType.ststoredprocedure) || 1367 (gst == EFindSqlStateType.sterror))) { 1368 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, true, builder); 1369 } 1370 1371 return syntaxErrors.size(); 1372 } 1373 1374 // ========== Helper Methods ========== 1375 1376 /** 1377 * Check if a position is valid for treating division operator as SQL*Plus command. 1378 */ 1379 private boolean IsValidPlaceForDivToSqlplusCmd(TSourceTokenList pstlist, int pPos) { 1380 boolean ret = false; 1381 1382 if ((pPos <= 0) || (pPos > pstlist.size() - 1)) return ret; 1383 //tokentext directly before div must be ttreturn without space appending it 1384 TSourceToken lcst = pstlist.get(pPos - 1); 1385 if (lcst.tokencode == TBaseType.lexnewline) { 1386 String astext = lcst.getAstext(); 1387 ret = (astext.length() > 0 && astext.charAt(astext.length() - 1) == '\n'); 1388 } 1389 1390 return ret; 1391 } 1392 1393 /** 1394 * Placeholder function for PostgreSQL-style SQL*Plus commands. 1395 * Always returns false for Snowflake (not applicable). 1396 */ 1397 private boolean isvalidsqlpluscmdInPostgresql(String astr) { 1398 return false; 1399 } 1400 1401 // Note: isDollarFunctionDelimiter() is now inherited from AbstractSqlParser 1402 // The parent implementation handles all PostgreSQL-family databases including Snowflake 1403 1404 @Override 1405 public String toString() { 1406 return "SnowflakeSqlParser{vendor=" + vendor + "}"; 1407 } 1408}