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