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.io.IOException; 038import java.io.StringReader; 039import java.util.ArrayList; 040import java.util.List; 041import java.util.Stack; 042 043import static gudusoft.gsqlparser.ESqlStatementType.*; 044 045/** 046 * Snowflake database SQL parser implementation. 047 * 048 * <p>This parser handles Snowflake-specific SQL syntax including: 049 * <ul> 050 * <li>Snowflake stored procedures (SQL and JavaScript)</li> 051 * <li>Snowflake-specific functions (FLATTEN, PIVOT, UNPIVOT, etc.)</li> 052 * <li>Snowflake tasks and streams</li> 053 * <li>Snowflake semi-structured data handling (VARIANT, ARRAY, OBJECT)</li> 054 * <li>Special token handling (AT, LEFT/RIGHT joins, DATE/TIME functions)</li> 055 * <li>Transaction control (BEGIN TRANSACTION, COMMIT, ROLLBACK)</li> 056 * </ul> 057 * 058 * <p><b>Design Notes:</b> 059 * <ul> 060 * <li>Extends {@link AbstractSqlParser} using the template method pattern</li> 061 * <li>Uses {@link TLexerSnowflake} for tokenization</li> 062 * <li>Uses {@link TParserSnowflake} for parsing</li> 063 * <li>Delimiter character: ';' for SQL statements</li> 064 * </ul> 065 * 066 * <p><b>Usage Example:</b> 067 * <pre> 068 * // Get Snowflake parser from factory 069 * SqlParser parser = SqlParserFactory.get(EDbVendor.dbvsnowflake); 070 * 071 * // Build context 072 * ParserContext context = new ParserContext.Builder(EDbVendor.dbvsnowflake) 073 * .sqlText("SELECT * FROM customers WHERE region = 'US'") 074 * .build(); 075 * 076 * // Parse 077 * SqlParseResult result = parser.parse(context); 078 * 079 * // Access statements 080 * TStatementList statements = result.getSqlStatements(); 081 * </pre> 082 * 083 * @see SqlParser 084 * @see AbstractSqlParser 085 * @see TLexerSnowflake 086 * @see TParserSnowflake 087 * @since 3.2.0.0 088 */ 089public class SnowflakeSqlParser extends AbstractSqlParser { 090 091 /** 092 * Construct Snowflake SQL parser. 093 * <p> 094 * Configures the parser for Snowflake database with default delimiter (;). 095 * <p> 096 * Following the original TGSqlParser pattern, the lexer and parser are 097 * created once in the constructor and reused for all parsing operations. 098 */ 099 public SnowflakeSqlParser() { 100 super(EDbVendor.dbvsnowflake); 101 this.delimiterChar = ';'; 102 this.defaultDelimiterStr = ";"; 103 104 // Create lexer once - will be reused for all parsing operations 105 this.flexer = new TLexerSnowflake(); 106 this.flexer.delimiterchar = this.delimiterChar; 107 this.flexer.defaultDelimiterStr = this.defaultDelimiterStr; 108 109 // Set parent's lexer reference for shared tokenization logic 110 this.lexer = this.flexer; 111 112 // Create parser once - will be reused for all parsing operations 113 this.fparser = new TParserSnowflake(null); 114 this.fparser.lexer = this.flexer; 115 } 116 117 // ========== Parser Components ========== 118 119 /** The Snowflake lexer used for tokenization */ 120 public TLexerSnowflake flexer; 121 122 /** SQL parser (for Snowflake statements) */ 123 private TParserSnowflake fparser; 124 125 /** Current statement being built during extraction */ 126 private TCustomSqlStatement gcurrentsqlstatement; 127 128 // Stored procedure parsing state tracking 129 private enum stored_procedure_type { 130 procedure, function, package_spec, package_body, block_with_declare, 131 block_with_begin, create_trigger, create_library, others 132 } 133 134 private enum stored_procedure_status { 135 start, is_as, body, bodyend, end 136 } 137 138 private static final int stored_procedure_nested_level = 50; 139 140 // Note: Global context and frame stack fields inherited from AbstractSqlParser: 141 // - protected TContext globalContext 142 // - protected TSQLEnv sqlEnv 143 // - protected Stack<TFrame> frameStack 144 // - protected TFrame globalFrame 145 146 // ========== AbstractSqlParser Abstract Methods Implementation ========== 147 148 /** 149 * Return the Snowflake lexer instance. 150 */ 151 @Override 152 protected TCustomLexer getLexer(ParserContext context) { 153 return this.flexer; 154 } 155 156 /** 157 * Return the Snowflake SQL parser instance with updated token list. 158 */ 159 @Override 160 protected TCustomParser getParser(ParserContext context, TSourceTokenList tokens) { 161 this.fparser.sourcetokenlist = tokens; 162 this.fparser.reclassifyStagePathKeywords(); 163 return this.fparser; 164 } 165 166 /** 167 * Snowflake does not use a secondary parser (unlike Oracle with PL/SQL). 168 */ 169 @Override 170 protected TCustomParser getSecondaryParser(ParserContext context, TSourceTokenList tokens) { 171 return null; 172 } 173 174 /** 175 * Call Snowflake-specific tokenization logic. 176 * <p> 177 * Delegates to dosnowflakesqltexttotokenlist which handles Snowflake's 178 * specific keyword recognition and token generation. 179 */ 180 @Override 181 protected void tokenizeVendorSql() { 182 preprocessBackslashEscapedQuotes(); 183 dosnowflakesqltexttotokenlist(); 184 } 185 186 /** 187 * Preprocess SQL input to convert \\' (backslash-escaped quotes from serialized SQL) 188 * into regular single quotes before tokenization. 189 * <p> 190 * Some SQL export tools serialize single quotes as \\' when embedding SQL in strings. 191 * The Snowflake lexer does not handle \\ in init state, so we normalize them here. 192 */ 193 private void preprocessBackslashEscapedQuotes() { 194 if (this.flexer.yyinput == null) return; 195 try { 196 StringBuilder sb = new StringBuilder(); 197 char[] buf = new char[4096]; 198 int n; 199 while ((n = this.flexer.yyinput.read(buf)) != -1) { 200 sb.append(buf, 0, n); 201 } 202 String sql = sb.toString(); 203 if (sql.contains("\\\\'")) { 204 sql = sql.replace("\\\\'", "'"); 205 } 206 this.flexer.yyinput = new BufferedReader(new StringReader(sql)); 207 } catch (IOException e) { 208 // If reading fails, leave the original reader in place 209 } 210 } 211 212 /** 213 * Post-tokenization: merge {identifier} template variable tokens into IDENT tokens. 214 * Handles patterns like kayodatalake__{env}.schema.table where {env} is a 215 * dbt/Jinja template variable. Also handles standalone {var} patterns. 216 */ 217 @Override 218 protected void doAfterTokenize(TSourceTokenList tokens) { 219 super.doAfterTokenize(tokens); 220 mergeBraceTemplateVariables(tokens); 221 } 222 223 private static boolean isIdentOrKeyword(TSourceToken t) { 224 return t.tokencode == TBaseType.ident 225 || t.tokentype == ETokenType.ttkeyword 226 || t.tokentype == ETokenType.ttidentifier; 227 } 228 229 private void mergeBraceTemplateVariables(TSourceTokenList tokens) { 230 for (int i = 0; i < tokens.size() - 2; i++) { 231 TSourceToken tok = tokens.get(i); 232 if (tok.tokencode != '{') continue; 233 234 // Look for pattern: '{' IDENT_OR_KEYWORD '}' 235 int identIdx = i + 1; 236 TSourceToken identTok = tokens.get(identIdx); 237 if (!isIdentOrKeyword(identTok)) continue; 238 239 int closeIdx = identIdx + 1; 240 if (closeIdx >= tokens.size()) continue; 241 TSourceToken closeTok = tokens.get(closeIdx); 242 if (closeTok.tokencode != '}') continue; 243 244 // Found {name} pattern. Check if preceding token is an IDENT to merge with. 245 String mergedText = "{" + identTok.astext + "}"; 246 int mergeStart = i; 247 248 // Check preceding token for an identifier to merge with (e.g., kayodatalake__{env}) 249 int prevIdx = i - 1; 250 if (prevIdx >= 0 && isIdentOrKeyword(tokens.get(prevIdx))) { 251 mergedText = tokens.get(prevIdx).astext + mergedText; 252 mergeStart = prevIdx; 253 } 254 255 // Set the first token of the merge range to the merged IDENT 256 TSourceToken anchor = tokens.get(mergeStart); 257 anchor.astext = mergedText; 258 anchor.tokencode = TBaseType.ident; 259 anchor.tokentype = ETokenType.ttidentifier; 260 261 // Convert remaining tokens to whitespace 262 for (int j = mergeStart + 1; j <= closeIdx; j++) { 263 tokens.get(j).tokentype = ETokenType.ttwhitespace; 264 tokens.get(j).tokencode = TBaseType.lexspace; 265 } 266 267 i = closeIdx; // skip past merged tokens 268 } 269 } 270 271 /** 272 * Setup Snowflake parser for raw statement extraction. 273 * <p> 274 * Snowflake uses a single parser, so we inject sqlcmds and update 275 * the token list for the main parser only. 276 */ 277 @Override 278 protected void setupVendorParsersForExtraction() { 279 // Inject sqlcmds into parser (required for make_stmt) 280 this.fparser.sqlcmds = this.sqlcmds; 281 282 // Update token list for parser 283 this.fparser.sourcetokenlist = this.sourcetokenlist; 284 } 285 286 /** 287 * Call Snowflake-specific raw statement extraction logic. 288 * <p> 289 * Delegates to dosnowflakegetrawsqlstatements which handles Snowflake's 290 * statement delimiters and stored procedure boundaries. 291 */ 292 @Override 293 protected void extractVendorRawStatements(SqlParseResult.Builder builder) { 294 int errorCount = dosnowflakegetrawsqlstatements(builder); 295 // Error count is tracked internally; errors are already added to syntaxErrors list 296 297 // Expand dollar string and single-quoted procedure bodies into token lists 298 expandDollarString(); 299 300 // Set the extracted statements in the builder 301 builder.sqlStatements(this.sqlstatements); 302 } 303 304 /** 305 * Expand dollar-delimited and single-quoted string literals in Snowflake procedure/function bodies. 306 * <p> 307 * For CREATE PROCEDURE/FUNCTION statements with LANGUAGE SQL, this method: 308 * 1. Finds string literals (starting with $$ or ') that follow AS keyword 309 * 2. Extracts and tokenizes the SQL code inside the quotes 310 * 3. Replaces the single string token with expanded tokens for proper parsing 311 * <p> 312 * This is essential for Snowflake's syntax: AS '...' or AS $$...$$ 313 */ 314 private void expandDollarString() { 315 TSourceToken st; 316 TCustomSqlStatement sql; 317 ArrayList<TSourceToken> dollarTokens = new ArrayList<>(); 318 boolean isSQLLanguage = true; 319 320 // Iterate all create procedure and create function, other sql statement just skipped 321 for (int i = 0; i < sqlstatements.size(); i++) { 322 sql = sqlstatements.get(i); 323 if (!((sql.sqlstatementtype == ESqlStatementType.sstcreateprocedure) || 324 (sql.sqlstatementtype == ESqlStatementType.sstcreatefunction))) continue; 325 326 isSQLLanguage = true; 327 for (int j = 0; j < sql.sourcetokenlist.size(); j++) { 328 st = sql.sourcetokenlist.get(j); 329 330 if (sql.sqlstatementtype == ESqlStatementType.sstcreateprocedure) { 331 if (st.tokencode == TBaseType.rrw_snowflake_language) { 332 TSourceToken lang = st.nextSolidToken(); 333 if ((lang != null) && (!lang.toString().equalsIgnoreCase("sql"))) { 334 isSQLLanguage = false; 335 } 336 } 337 } 338 339 if (!isSQLLanguage) break; 340 341 if (st.tokencode == TBaseType.sconst) { 342 if (st.toString().startsWith("$$")) { 343 dollarTokens.add(st); 344 } else if (st.toString().startsWith("'")) { 345 // https://docs.snowflake.com/en/sql-reference/sql/create-procedure 346 // string literal delimiter can be $ or ' 347 if (st.prevSolidToken().tokencode == TBaseType.rrw_as) { 348 dollarTokens.add(st); 349 } 350 } 351 } 352 }//check tokens 353 354 for (int m = dollarTokens.size() - 1; m >= 0; m--) { 355 // Token Expansion: 356 // For each identified string literal: 357 // Extracts the content between the quotes 358 // Tokenizes the extracted SQL code 359 // Verifies it's a valid code block (starts with DECLARE or BEGIN) 360 // Replaces the original quoted string with expanded tokens in the source token list 361 362 st = dollarTokens.get(m); 363 364 // Create a new parser to tokenize the procedure body 365 // Use TGSqlParser for internal tokenization (simpler than using SnowflakeSqlParser for this snippet) 366 gudusoft.gsqlparser.TGSqlParser parser = new gudusoft.gsqlparser.TGSqlParser(this.vendor); 367 368 // Extract the body content from the string literal 369 // For procedure bodies, we need special handling for single-quoted strings: 370 // - Remove outer quotes 371 // - Unescape '' to ' (SQL standard) 372 // - Preserve backslash-quote-quote patterns: \'' -> \' 373 // (backslash + escaped quote should become backslash + single quote for re-parsing) 374 // See mantisbt issue 4298 for details 375 String tokenStr = st.toString(); 376 String bodyContent; 377 if (tokenStr.startsWith("$$")) { 378 // Dollar-quoted string: just strip the $$ delimiters 379 bodyContent = TBaseType.getStringInsideLiteral(tokenStr); 380 } else if (tokenStr.startsWith("'")) { 381 // Single-quoted string: custom unescaping 382 // Do NOT use getStringInsideLiteral() as it incorrectly handles \' 383 bodyContent = tokenStr.substring(1, tokenStr.length() - 1); 384 // Unescape body-level escapes for re-parsing: 385 // \\'' (2 backslashes + 2 quotes) = escaped backslash + escaped quote 386 // at the body level = one literal quote char in the body SQL. 387 // \'' (1 backslash + 2 quotes) = backslash + escaped quote 388 // preserved as \' (C-style escaped quote) for re-parsing. 389 // '' (2 quotes) = escaped quote = one literal quote char. 390 bodyContent = bodyContent.replace("\\\\''", "\u0000BSQQ2\u0000"); 391 bodyContent = bodyContent.replace("\\''", "\u0000BSQQ\u0000"); 392 bodyContent = bodyContent.replace("''", "'"); 393 bodyContent = bodyContent.replace("\u0000BSQQ\u0000", "\\'"); 394 bodyContent = bodyContent.replace("\u0000BSQQ2\u0000", "'"); 395 } else { 396 bodyContent = tokenStr; 397 } 398 parser.sqltext = bodyContent; 399 400 TSourceToken startQuote = new TSourceToken(st.toString().substring(0, 1)); 401 startQuote.tokencode = TBaseType.lexspace; // Set as space, can be ignored during parsing, but preserved in toString() 402 TSourceToken endQuote = new TSourceToken(st.toString().substring(0, 1)); 403 endQuote.tokencode = TBaseType.lexspace; 404 405 // use getrawsqlstatements() instead of tokenizeSqltext() to get the source token list because 406 // some token will be transformed to other token, which will be processed in dosnowflakegetrawsqlstatements() 407 parser.getrawsqlstatements(); 408 409 TSourceToken st2; 410 boolean isValidBlock = false; 411 for (int k = 0; k < parser.sourcetokenlist.size(); k++) { 412 st2 = parser.sourcetokenlist.get(k); 413 if (st2.isnonsolidtoken()) continue; 414 if ((st2.tokencode == TBaseType.rrw_declare) || (st2.tokencode == TBaseType.rrw_begin)) { 415 isValidBlock = true; 416 } 417 break; 418 } 419 420 if (isValidBlock) { 421 TSourceToken semiColon = null; 422 st.tokenstatus = ETokenStatus.tsdeleted; 423 int startPosOfThisSQL = sql.getStartToken().posinlist; 424 425 sql.sourcetokenlist.add((st.posinlist++) - startPosOfThisSQL, startQuote); // Add opening quote 426 for (int k = 0; k < parser.sourcetokenlist.size(); k++) { 427 st2 = parser.sourcetokenlist.get(k); 428 if (st2.tokencode == ';') { 429 semiColon = st2; 430 TSourceToken prevSolidToken = st2.prevSolidToken(); 431 if ((prevSolidToken != null) && (prevSolidToken.tokencode == TBaseType.rrw_begin)) { 432 // begin; => begin transaction; 433 prevSolidToken.tokencode = TBaseType.rrw_snowflake_begin_transaction; 434 } 435 } 436 if ((st2.tokencode == TBaseType.rrw_snowflake_work) || (st2.tokencode == TBaseType.rrw_snowflake_transaction)) { 437 // begin work; => begin transaction; 438 TSourceToken prevSolidToken = st2.prevSolidToken(); 439 if ((prevSolidToken != null) && (prevSolidToken.tokencode == TBaseType.rrw_begin)) { 440 // begin; => begin transaction; 441 prevSolidToken.tokencode = TBaseType.rrw_snowflake_begin_transaction; 442 } 443 } 444 sql.sourcetokenlist.add((st.posinlist++) - startPosOfThisSQL, st2); 445 } 446 if (semiColon != null) { 447 if (semiColon.prevSolidToken().tokencode == TBaseType.rrw_end) { 448 // Set as space, can be ignored during parsing, but preserved in toString() 449 semiColon.tokencode = TBaseType.lexspace; 450 } 451 } 452 453 sql.sourcetokenlist.add((st.posinlist++) - startPosOfThisSQL, endQuote); // Add closing quote 454 TBaseType.resetTokenChain(sql.sourcetokenlist, 0); // Reset token chain to ensure new tokens are accessible in toString() 455 } 456 } 457 458 dollarTokens.clear(); 459 }//statement 460 } 461 462 /** 463 * Perform full parsing of statements with syntax checking. 464 * <p> 465 * This method orchestrates the parsing of all statements. 466 */ 467 @Override 468 protected TStatementList performParsing(ParserContext context, 469 TCustomParser parser, 470 TCustomParser secondaryParser, 471 TSourceTokenList tokens, 472 TStatementList rawStatements) { 473 // Store references 474 this.fparser = (TParserSnowflake) parser; 475 this.sourcetokenlist = tokens; 476 this.parserContext = context; 477 478 // Use the raw statements passed from AbstractSqlParser.parse() 479 this.sqlstatements = rawStatements; 480 481 // Initialize statement parsing infrastructure 482 this.sqlcmds = SqlCmdsFactory.get(vendor); 483 484 // Inject sqlcmds into parser (required for make_stmt and other methods) 485 this.fparser.sqlcmds = this.sqlcmds; 486 487 // Initialize global context for semantic analysis 488 initializeGlobalContext(); 489 490 // Parse each statement with exception handling for robustness 491 for (int i = 0; i < sqlstatements.size(); i++) { 492 TCustomSqlStatement stmt = sqlstatements.getRawSql(i); 493 494 try { 495 stmt.setFrameStack(frameStack); 496 497 // Parse the statement 498 int parseResult = stmt.parsestatement(null, false, context.isOnlyNeedRawParseTree()); 499 500 // Handle error recovery for CREATE TABLE/INDEX 501 boolean doRecover = TBaseType.ENABLE_ERROR_RECOVER_IN_CREATE_TABLE; 502 if (doRecover && ((parseResult != 0) || (stmt.getErrorCount() > 0))) { 503 handleCreateTableErrorRecovery(stmt); 504 } 505 506 // Collect syntax errors 507 if ((parseResult != 0) || (stmt.getErrorCount() > 0)) { 508 copyErrorsFromStatement(stmt); 509 } 510 511 } catch (Exception ex) { 512 // Use inherited exception handler from AbstractSqlParser 513 // This provides consistent error handling across all database parsers 514 handleStatementParsingException(stmt, i, ex); 515 continue; 516 } 517 } 518 519 // Clean up frame stack 520 if (globalFrame != null) { 521 globalFrame.popMeFromStack(frameStack); 522 } 523 524 return this.sqlstatements; 525 } 526 527 // Note: initializeGlobalContext() inherited from AbstractSqlParser 528 // Note: No override of afterStatementParsed() needed - default (no-op) is appropriate for Snowflake 529 530 /** 531 * Handle error recovery for CREATE TABLE/INDEX statements. 532 */ 533 private void handleCreateTableErrorRecovery(TCustomSqlStatement stmt) { 534 if (((stmt.sqlstatementtype == ESqlStatementType.sstcreatetable) 535 || (stmt.sqlstatementtype == ESqlStatementType.sstcreateindex)) 536 && (!TBaseType.c_createTableStrictParsing)) { 537 538 int nested = 0; 539 boolean isIgnore = false, isFoundIgnoreToken = false; 540 TSourceToken firstIgnoreToken = null; 541 542 for (int k = 0; k < stmt.sourcetokenlist.size(); k++) { 543 TSourceToken st = stmt.sourcetokenlist.get(k); 544 if (isIgnore) { 545 if (st.issolidtoken() && (st.tokencode != ';')) { 546 isFoundIgnoreToken = true; 547 if (firstIgnoreToken == null) { 548 firstIgnoreToken = st; 549 } 550 } 551 if (st.tokencode != ';') { 552 st.tokencode = TBaseType.sqlpluscmd; 553 } 554 continue; 555 } 556 if (st.tokencode == (int) ')') { 557 nested--; 558 if (nested == 0) { 559 boolean isSelect = false; 560 TSourceToken st1 = st.searchToken(TBaseType.rrw_as, 1); 561 if (st1 != null) { 562 TSourceToken st2 = st.searchToken((int) '(', 2); 563 if (st2 != null) { 564 TSourceToken st3 = st.searchToken(TBaseType.rrw_select, 3); 565 isSelect = (st3 != null); 566 } 567 } 568 if (!isSelect) isIgnore = true; 569 } 570 } else if (st.tokencode == (int) '(') { 571 nested++; 572 } 573 } 574 575 if (isFoundIgnoreToken) { 576 stmt.clearError(); 577 stmt.parsestatement(null, false); 578 } 579 } 580 } 581 582 /** 583 * Perform Snowflake-specific semantic analysis using TSQLResolver. 584 */ 585 @Override 586 protected void performSemanticAnalysis(ParserContext context, TStatementList statements) { 587 if (TBaseType.isEnableResolver() && getSyntaxErrors().isEmpty()) { 588 TSQLResolver resolver = new TSQLResolver(globalContext, statements); 589 resolver.resolve(); 590 } 591 } 592 593 /** 594 * Perform interpretation/evaluation on parsed statements. 595 */ 596 @Override 597 protected void performInterpreter(ParserContext context, TStatementList statements) { 598 if (TBaseType.ENABLE_INTERPRETER && getSyntaxErrors().isEmpty()) { 599 TLog.clearLogs(); 600 TGlobalScope interpreterScope = new TGlobalScope(sqlEnv); 601 TLog.enableInterpreterLogOnly(); 602 TASTEvaluator astEvaluator = new TASTEvaluator(statements, interpreterScope); 603 astEvaluator.eval(); 604 } 605 } 606 607 // ========== Snowflake-Specific Tokenization ========== 608 609 /** 610 * Snowflake-specific tokenization logic. 611 * <p> 612 * Extracted from: TGSqlParser.dosnowflakesqltexttotokenlist() (lines 3289-3439) 613 */ 614 private void dosnowflakesqltexttotokenlist() { 615 616 boolean insqlpluscmd = false; 617 boolean isvalidplace = true; 618 boolean waitingreturnforfloatdiv = false; 619 boolean waitingreturnforsemicolon = false; 620 boolean continuesqlplusatnewline = false; 621 622 TSourceToken lct = null, prevst = null; 623 624 TSourceToken asourcetoken, lcprevst; 625 int yychar; 626 627 asourcetoken = getanewsourcetoken(); 628 if (asourcetoken == null) return; 629 yychar = asourcetoken.tokencode; 630 631 while (yychar > 0) { 632 sourcetokenlist.add(asourcetoken); 633 switch (yychar) { 634 case TBaseType.cmtdoublehyphen: 635 case TBaseType.cmtslashstar: 636 case TBaseType.lexspace: { 637 if (insqlpluscmd) { 638 asourcetoken.insqlpluscmd = true; 639 } 640 break; 641 } 642 case TBaseType.lexnewline: { 643 if (insqlpluscmd) { 644 insqlpluscmd = false; 645 isvalidplace = true; 646 647 if (continuesqlplusatnewline) { 648 insqlpluscmd = true; 649 isvalidplace = false; 650 asourcetoken.insqlpluscmd = true; 651 } 652 } 653 654 if (waitingreturnforsemicolon) { 655 isvalidplace = true; 656 } 657 if (waitingreturnforfloatdiv) { 658 isvalidplace = true; 659 lct.tokencode = TBaseType.sqlpluscmd; 660 if (lct.tokentype != ETokenType.ttslash) { 661 lct.tokentype = ETokenType.ttsqlpluscmd; 662 } 663 } 664 flexer.insqlpluscmd = insqlpluscmd; 665 break; 666 } //case newline 667 default: { 668 //solid tokentext 669 continuesqlplusatnewline = false; 670 waitingreturnforsemicolon = false; 671 waitingreturnforfloatdiv = false; 672 if (insqlpluscmd) { 673 asourcetoken.insqlpluscmd = true; 674 if (asourcetoken.getAstext().equalsIgnoreCase("-")) { 675 continuesqlplusatnewline = true; 676 } 677 } else { 678 if (asourcetoken.tokentype == ETokenType.ttsemicolon) { 679 waitingreturnforsemicolon = true; 680 } 681 if ((asourcetoken.tokentype == ETokenType.ttslash) 682 // and (isvalidplace or sourcetokenlist.TokenBeforeCurToken(#10,false,false,false)) then 683 && (isvalidplace || (IsValidPlaceForDivToSqlplusCmd(sourcetokenlist, asourcetoken.posinlist)))) { 684 lct = asourcetoken; 685 waitingreturnforfloatdiv = true; 686 } 687 if ((isvalidplace) && isvalidsqlpluscmdInPostgresql(asourcetoken.toString())) { 688 asourcetoken.tokencode = TBaseType.sqlpluscmd; 689 if (asourcetoken.tokentype != ETokenType.ttslash) { 690 asourcetoken.tokentype = ETokenType.ttsqlpluscmd; 691 } 692 insqlpluscmd = true; 693 flexer.insqlpluscmd = insqlpluscmd; 694 } 695 } 696 isvalidplace = false; 697 698 // the inner keyword tokentext should be convert to TBaseType.ident when 699 // next solid tokentext is not join 700 701 if (prevst != null) { 702 if (prevst.tokencode == TBaseType.rrw_inner)//flexer.getkeywordvalue("INNER")) 703 { 704 if (asourcetoken.tokencode != flexer.getkeywordvalue("JOIN") 705 && asourcetoken.tokencode != flexer.getkeywordvalue("DIRECTED")) { 706 prevst.tokencode = TBaseType.ident; 707 } 708 } 709 710 711 if ((prevst.tokencode == TBaseType.rrw_not) 712 && (asourcetoken.tokencode == flexer.getkeywordvalue("DEFERRABLE"))) { 713 prevst.tokencode = flexer.getkeywordvalue("NOT_DEFERRABLE"); 714 } 715 716 } 717 718 if (asourcetoken.tokencode == TBaseType.rrw_inner) { 719 prevst = asourcetoken; 720 } else if (asourcetoken.tokencode == TBaseType.rrw_not) { 721 prevst = asourcetoken; 722 } else { 723 prevst = null; 724 } 725 726 727 } 728 } 729 730 //flexer.yylexwrap(asourcetoken); 731 asourcetoken = getanewsourcetoken(); 732 if (asourcetoken != null) { 733 yychar = asourcetoken.tokencode; 734 } else { 735 yychar = 0; 736 737 if (waitingreturnforfloatdiv) { // / at the end of line treat as sqlplus command 738 //isvalidplace = true; 739 lct.tokencode = TBaseType.sqlpluscmd; 740 if (lct.tokentype != ETokenType.ttslash) { 741 lct.tokentype = ETokenType.ttsqlpluscmd; 742 } 743 } 744 745 } 746 747 if ((yychar == 0) && (prevst != null)) { 748 if (prevst.tokencode == TBaseType.rrw_inner)// flexer.getkeywordvalue("RW_INNER")) 749 { 750 prevst.tokencode = TBaseType.ident; 751 } 752 } 753 754 755 } // while 756 757 758 } 759 760 // ========== Snowflake-Specific Raw Statement Extraction ========== 761 762 /** 763 * Snowflake-specific raw statement extraction logic. 764 * <p> 765 * Extracted from: TGSqlParser.dosnowflakegetrawsqlstatements() (lines 8646-9388) 766 */ 767 private int dosnowflakegetrawsqlstatements(SqlParseResult.Builder builder) { 768 int waitingEnd = 0; 769 boolean foundEnd = false; 770 771 int waitingEnds[] = new int[stored_procedure_nested_level]; 772 stored_procedure_type sptype[] = new stored_procedure_type[stored_procedure_nested_level]; 773 stored_procedure_status procedure_status[] = new stored_procedure_status[stored_procedure_nested_level]; 774 boolean endBySlashOnly = true; 775 int nestedProcedures = 0, nestedParenthesis = 0; 776 boolean inDollarBody = false; 777 778 if (TBaseType.assigned(sqlstatements)) sqlstatements.clear(); 779 if (!TBaseType.assigned(sourcetokenlist)) return -1; 780 781 gcurrentsqlstatement = null; 782 EFindSqlStateType gst = EFindSqlStateType.stnormal; 783 TSourceToken lcprevsolidtoken = null, ast = null; 784 785 for (int i = 0; i < sourcetokenlist.size(); i++) { 786 787 if ((ast != null) && (ast.issolidtoken())) 788 lcprevsolidtoken = ast; 789 790 ast = sourcetokenlist.get(i); 791 sourcetokenlist.curpos = i; 792 793 if ((ast.tokencode == TBaseType.rrw_right) || (ast.tokencode == TBaseType.rrw_left)) { 794 TSourceToken stLparen = ast.searchToken('(', 1); 795 if (stLparen != null) { //match ( 796 ast.tokencode = TBaseType.ident; 797 } 798 TSourceToken stNextToken = ast.nextSolidToken(); 799 if ((stNextToken != null) && ((stNextToken.tokencode == TBaseType.rrw_join) || (stNextToken.tokencode == TBaseType.rrw_outer))) { 800 if (ast.tokencode == TBaseType.rrw_left) { 801 ast.tokencode = TBaseType.rrw_snowflake_left_join; 802 } else { 803 ast.tokencode = TBaseType.rrw_snowflake_right_join; 804 } 805 } 806 } else if (ast.tokencode == TBaseType.rrw_snowflake_at) { 807 TSourceToken stLparen = ast.searchToken('(', 1); 808 if (stLparen != null) { //match ( 809 ast.tokencode = TBaseType.rrw_snowflake_at_before_parenthesis; 810 } 811 } else if (ast.tokencode == TBaseType.rrw_snowflake_changes) { 812 TSourceToken stLparen = ast.searchToken('(', 1); 813 if (stLparen != null) { //changes ( 814 ast.tokencode = TBaseType.rrw_snowflake_changes_parenthesis; 815 } 816 } else if (ast.tokencode == TBaseType.rrw_date) { 817 TSourceToken stLparen = ast.searchToken('(', 1); 818 if (stLparen != null) { //date ( 819 ast.tokencode = TBaseType.rrw_snowflake_date; 820 } else { 821 stLparen = ast.searchToken('.', 1); 822 if (stLparen != null) { //date ( 823 ast.tokencode = TBaseType.ident; 824 } 825 } 826 } else if (ast.tokencode == TBaseType.rrw_time) { 827 TSourceToken stLparen = ast.searchToken('(', 1); 828 if (stLparen != null) { //date ( 829 ast.tokencode = TBaseType.rrw_snowflake_time; 830 } else { 831 stLparen = ast.searchToken('.', 1); 832 if (stLparen != null) { //date ( 833 ast.tokencode = TBaseType.ident; 834 } 835 } 836 } else if (ast.tokencode == TBaseType.rrw_char) { 837 TSourceToken stLparen = ast.searchToken('(', 1); 838 if (stLparen != null) { //date ( 839 ast.tokencode = TBaseType.rrw_snowflake_char; 840 } else { 841 stLparen = ast.searchToken('.', 1); 842 if (stLparen != null) { //date ( 843 ast.tokencode = TBaseType.ident; 844 } 845 } 846 } else if (ast.tokencode == TBaseType.rrw_snowflake_window) { 847 TSourceToken stAs = ast.searchToken(TBaseType.rrw_as, 2); 848 if (stAs != null) { //date ( 849 ast.tokencode = TBaseType.rrw_snowflake_window_as; 850 } else { 851 } 852 } else if ((ast.tokencode == TBaseType.rrw_snowflake_pivot) || (ast.tokencode == TBaseType.rrw_snowflake_unpivot)) { 853 // For UNPIVOT, search range 3 to look past INCLUDE/EXCLUDE NULLS before ( 854 int searchRange = (ast.tokencode == TBaseType.rrw_snowflake_unpivot) ? 3 : 1; 855 TSourceToken stLparen = ast.searchToken('(', searchRange); 856 if (stLparen != null) { //pivot (, unpivot (, unpivot include nulls ( 857 858 } else { 859 ast.tokencode = TBaseType.ident; 860 } 861 } else if (ast.tokencode == TBaseType.rrw_snowflake_flatten) { 862 TSourceToken stLeftParens = ast.searchToken('(', 1); 863 if (stLeftParens != null) { //flatten ( 864 865 } else { 866 ast.tokencode = TBaseType.ident; // change it to an identifier, can be used as db object name. 867 } 868 } else if (ast.tokencode == TBaseType.rrw_snowflake_offset) { 869 TSourceToken stFrom = ast.searchToken(TBaseType.rrw_from, -ast.posinlist, TBaseType.rrw_select, true); 870 if (stFrom == null) { 871 // FORM keyword before OFFSET is not found, then offset must be a column name, 872 // just like this: SELECT column1 offset FROM table2 873 ast.tokencode = TBaseType.ident; // change it to an identifier, can be used as db object name. 874 } 875 } else if (ast.tokencode == TBaseType.rrw_replace) { 876 TSourceToken stStar = ast.prevSolidToken(); 877 if (stStar.tokencode == '*') { 878 ast.tokencode = TBaseType.rrw_snowflake_replace_after_star; 879 } 880 } else if (ast.tokencode == TBaseType.rrw_snowflake_transaction) { 881 TSourceToken stBegin = ast.prevSolidToken(); 882 if ((stBegin != null) && (stBegin.tokencode == TBaseType.rrw_begin)) { 883 stBegin.tokencode = TBaseType.rrw_snowflake_begin_transaction; 884 } 885 } else if (ast.tokencode == TBaseType.rrw_begin) { 886 // begin; 887 // begin work; 888 // begin transaction; 889 TSourceToken stNext = ast.nextSolidToken(); 890 if ((stNext != null) && ((stNext.tokencode == ';') 891 || (stNext.tokencode == TBaseType.rrw_snowflake_work) || (stNext.tokencode == TBaseType.rrw_snowflake_transaction)) 892 ) { 893 ast.tokencode = TBaseType.rrw_snowflake_begin_transaction; 894 } 895 } else if ((ast.tokencode == TBaseType.rrw_snowflake_top) || (ast.tokencode == TBaseType.rrw_text) || (ast.tokencode == TBaseType.rrw_snowflake_default)) { 896 TSourceToken stPeriod = ast.nextSolidToken(); 897 if ((stPeriod != null) && (stPeriod.tokencode == '.')) { 898 ast.tokencode = TBaseType.ident; 899 } 900 } else if (ast.tokencode == TBaseType.rrw_snowflake_limit) { 901 TSourceToken stPrev = ast.prevSolidToken(); 902 if ((stPrev != null) && (stPrev.tokencode == ',')) { 903 ast.tokencode = TBaseType.ident; 904 } 905 } else if (ast.tokencode == TBaseType.ident) { 906 // check whether it is a snowflake parameter name 907 // 这个调用可能会有性能问题,因为每个ident都会调用一次 908 if (TSnowflakeParameterChecker.isSnowflakeParameter(ast.toString())) { 909 ast.tokencode = TBaseType.rrw_snowflake_parameter_name; 910 } 911 } 912 913 914 switch (gst) { 915 case sterror: { 916 if (ast.tokentype == ETokenType.ttsemicolon) { 917 gcurrentsqlstatement.sourcetokenlist.add(ast); 918 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 919 gst = EFindSqlStateType.stnormal; 920 } else { 921 gcurrentsqlstatement.sourcetokenlist.add(ast); 922 } 923 break; 924 } //sterror 925 926 case stnormal: { 927 if ((ast.tokencode == TBaseType.cmtdoublehyphen) 928 || (ast.tokencode == TBaseType.cmtslashstar) 929 || (ast.tokencode == TBaseType.lexspace) 930 || (ast.tokencode == TBaseType.lexnewline) 931 || (ast.tokentype == ETokenType.ttsemicolon)) { 932 if (gcurrentsqlstatement != null) { 933 gcurrentsqlstatement.sourcetokenlist.add(ast); 934 } 935 936 if ((lcprevsolidtoken != null) && (ast.tokentype == ETokenType.ttsemicolon)) { 937 if (lcprevsolidtoken.tokentype == ETokenType.ttsemicolon) { 938 // ;;;; continuous semicolon,treat it as comment 939 ast.tokentype = ETokenType.ttsimplecomment; 940 ast.tokencode = TBaseType.cmtdoublehyphen; 941 } 942 } 943 944 continue; 945 } 946 947 if (ast.tokencode == TBaseType.sqlpluscmd) { 948 gst = EFindSqlStateType.stsqlplus; 949 gcurrentsqlstatement = new TSqlplusCmdStatement(vendor); 950 gcurrentsqlstatement.sourcetokenlist.add(ast); 951 continue; 952 } 953 954 // find a tokentext to start sql or plsql mode 955 gcurrentsqlstatement = sqlcmds.issql(ast, gst, gcurrentsqlstatement); 956 957 if (gcurrentsqlstatement != null) { 958 // WITH...AS PROCEDURE uses stsql mode (not ststoredprocedure) 959 // because the $$ body is handled by TRoutine's dollar delimiter logic in stsql mode, 960 // and the ststoredprocedure state machine misinterprets the early AS token in "WITH name AS PROCEDURE" 961 boolean isWithProcedure = (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstcreateprocedure) 962 && (ast.tokencode == TBaseType.rrw_with); 963 964 if (gcurrentsqlstatement.issnowflakeplsql() && !isWithProcedure) { 965 nestedProcedures = 0; 966 gst = EFindSqlStateType.ststoredprocedure; 967 gcurrentsqlstatement.sourcetokenlist.add(ast); 968 969 switch (gcurrentsqlstatement.sqlstatementtype) { 970 case sstplsql_createprocedure: 971 case sstcreateprocedure: 972 sptype[nestedProcedures] = stored_procedure_type.procedure; 973 break; 974 case sstplsql_createfunction: 975 sptype[nestedProcedures] = stored_procedure_type.function; 976 break; 977 case sstplsql_createpackage: 978 sptype[nestedProcedures] = stored_procedure_type.package_spec; 979 if (ast.searchToken(TBaseType.rrw_body, 5) != null) { 980 sptype[nestedProcedures] = stored_procedure_type.package_body; 981 } 982 break; 983 case sst_plsql_block: 984 sptype[nestedProcedures] = stored_procedure_type.block_with_declare; 985 if (ast.tokencode == TBaseType.rrw_begin) { 986 sptype[nestedProcedures] = stored_procedure_type.block_with_begin; 987 } else if (ast.tokencode == TBaseType.rrw_while 988 || ast.tokencode == TBaseType.rrw_for 989 || ast.tokencode == TBaseType.rrw_loop 990 || ast.tokencode == TBaseType.rrw_if) { 991 sptype[nestedProcedures] = stored_procedure_type.block_with_begin; 992 } 993 break; 994 case sstplsql_createtrigger: 995 sptype[nestedProcedures] = stored_procedure_type.create_trigger; 996 break; 997 case sstoraclecreatelibrary: 998 sptype[nestedProcedures] = stored_procedure_type.create_library; 999 break; 1000 case sstplsql_createtype_placeholder: 1001 gst = EFindSqlStateType.stsql; 1002 break; 1003 default: 1004 sptype[nestedProcedures] = stored_procedure_type.others; 1005 break; 1006 } 1007 1008 if (sptype[0] == stored_procedure_type.block_with_declare) { 1009 // sd 1010 endBySlashOnly = false; 1011 procedure_status[0] = stored_procedure_status.is_as; 1012 } else if (sptype[0] == stored_procedure_type.block_with_begin) { 1013 // sb 1014 endBySlashOnly = false; 1015 procedure_status[0] = stored_procedure_status.body; 1016 } else if (sptype[0] == stored_procedure_type.procedure) { 1017 // ss 1018 endBySlashOnly = false; 1019 procedure_status[0] = stored_procedure_status.start; 1020 } else if (sptype[0] == stored_procedure_type.function) { 1021 // ss 1022 endBySlashOnly = false; 1023 procedure_status[0] = stored_procedure_status.start; 1024 } else if (sptype[0] == stored_procedure_type.package_spec) { 1025 // ss 1026 endBySlashOnly = false; 1027 procedure_status[0] = stored_procedure_status.start; 1028 } else if (sptype[0] == stored_procedure_type.package_body) { 1029 // ss 1030 endBySlashOnly = false; 1031 procedure_status[0] = stored_procedure_status.start; 1032 } else if (sptype[0] == stored_procedure_type.create_trigger) { 1033 // ss 1034 endBySlashOnly = false; 1035 procedure_status[0] = stored_procedure_status.start; 1036 //procedure_status[0] = stored_procedure_status.body; 1037 } else if (sptype[0] == stored_procedure_type.create_library) { 1038 // ss 1039 endBySlashOnly = false; 1040 procedure_status[0] = stored_procedure_status.bodyend; 1041 } else { 1042 // so 1043 endBySlashOnly = true; 1044 procedure_status[0] = stored_procedure_status.bodyend; 1045 } 1046 //foundEnd = false; 1047 if ((ast.tokencode == TBaseType.rrw_begin) 1048 || (ast.tokencode == TBaseType.rrw_package) 1049 //||(ast.tokencode == TBaseType.rrw_procedure) 1050 || (ast.searchToken(TBaseType.rrw_package, 4) != null) 1051 || (ast.tokencode == TBaseType.rrw_while) 1052 || (ast.tokencode == TBaseType.rrw_for) 1053 || (ast.tokencode == TBaseType.rrw_loop) 1054 || (ast.tokencode == TBaseType.rrw_if) 1055 ) { 1056 //waitingEnd = 1; 1057 waitingEnds[nestedProcedures] = 1; 1058 } 1059 1060 } else { 1061 gst = EFindSqlStateType.stsql; 1062 if (isWithProcedure) { 1063 // Mark WITH and first AS tokens so parser skips them 1064 // This avoids S/R conflict with CTE (WITH name AS SELECT) 1065 // Parser skips tokens with tsignorebyyacc status (TCustomParser line 420) 1066 ast.tokenstatus = ETokenStatus.tsignorebyyacc; 1067 // Also mark the first AS (WITH name(...) AS PROCEDURE) so parser sees IDENT (...) PROCEDURE... 1068 TSourceToken nameToken = ast.nextSolidToken(); 1069 if (nameToken != null) { 1070 TSourceToken afterName = nameToken.nextSolidToken(); 1071 // Skip past parenthesized parameter list if present 1072 if (afterName != null && afterName.tokentype == ETokenType.ttleftparenthesis) { 1073 int parenDepth = 1; 1074 TSourceToken t = afterName.nextSolidToken(); 1075 while (t != null && parenDepth > 0) { 1076 if (t.tokentype == ETokenType.ttleftparenthesis) parenDepth++; 1077 else if (t.tokentype == ETokenType.ttrightparenthesis) parenDepth--; 1078 if (parenDepth > 0) t = t.nextSolidToken(); 1079 } 1080 if (t != null) afterName = t.nextSolidToken(); 1081 else afterName = null; 1082 } 1083 if (afterName != null && afterName.tokencode == TBaseType.rrw_as) { 1084 afterName.tokenstatus = ETokenStatus.tsignorebyyacc; 1085 } 1086 } 1087 } 1088 gcurrentsqlstatement.sourcetokenlist.add(ast); 1089 } 1090 } else { 1091 //error tokentext found 1092 1093 this.syntaxErrors.add(new TSyntaxError(ast.getAstext(), ast.lineNo, (ast.columnNo < 0 ? 0 : ast.columnNo) 1094 , "Error when tokenlize", EErrorType.spwarning, TBaseType.MSG_WARNING_ERROR_WHEN_TOKENIZE, null, ast.posinlist)); 1095 1096 ast.tokentype = ETokenType.tttokenlizererrortoken; 1097 gst = EFindSqlStateType.sterror; 1098 1099 gcurrentsqlstatement = new TUnknownSqlStatement(vendor); 1100 gcurrentsqlstatement.sqlstatementtype = ESqlStatementType.sstinvalid; 1101 gcurrentsqlstatement.sourcetokenlist.add(ast); 1102 1103 } 1104 1105 break; 1106 } // stnormal 1107 1108 case stsqlplus: { 1109 if (ast.insqlpluscmd) { 1110 gcurrentsqlstatement.sourcetokenlist.add(ast); 1111 } else { 1112 gst = EFindSqlStateType.stnormal; //this tokentext must be newline, 1113 gcurrentsqlstatement.sourcetokenlist.add(ast); // so add it here 1114 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 1115 } 1116 1117 break; 1118 }//case stsqlplus 1119 1120 case stsql: { 1121 if (gcurrentsqlstatement instanceof TRoutine) { 1122 if (isDollarFunctionDelimiter(ast.tokencode, this.vendor)) { 1123 if (inDollarBody) { 1124 inDollarBody = false; 1125 } else { 1126 inDollarBody = true; 1127 } 1128 } 1129 1130 if (inDollarBody) { 1131 gcurrentsqlstatement.sourcetokenlist.add(ast); 1132 continue; 1133 } 1134 1135 // Handle inline scripting body (AS DECLARE ... BEGIN ... END or AS BEGIN ... END) 1136 // for CREATE FUNCTION with LANGUAGE SQL 1137 if (ast.tokencode == TBaseType.rrw_as) { 1138 TSourceToken tmpNext = ast.nextSolidToken(); 1139 if ((tmpNext != null) && ((tmpNext.tokencode == TBaseType.rrw_begin) || (tmpNext.tokencode == TBaseType.rrw_declare))) { 1140 gst = EFindSqlStateType.ststoredprocedure; 1141 nestedProcedures = 0; 1142 if (tmpNext.tokencode == TBaseType.rrw_begin) { 1143 procedure_status[nestedProcedures] = stored_procedure_status.body; 1144 } else { 1145 procedure_status[nestedProcedures] = stored_procedure_status.is_as; 1146 } 1147 waitingEnds[nestedProcedures] = 0; 1148 gcurrentsqlstatement.sourcetokenlist.add(ast); 1149 continue; 1150 } 1151 } 1152 } else if (gcurrentsqlstatement instanceof TCreateTaskStmt) { 1153 if (ast.tokencode == TBaseType.rrw_as) { 1154 TSourceToken tmpNext = ast.nextSolidToken(); 1155 if ((tmpNext != null) && (tmpNext.tokencode == TBaseType.rrw_begin)) { 1156 // begin ... end block in create task statement, mantisbt/view.php?id=3531 1157 1158 gst = EFindSqlStateType.ststoredprocedure; 1159 nestedProcedures = 0; 1160 procedure_status[nestedProcedures] = stored_procedure_status.body; 1161 waitingEnds[nestedProcedures] = 0; 1162 gcurrentsqlstatement.sourcetokenlist.add(ast); 1163 continue; 1164 } 1165 } 1166 } 1167 1168 if (ast.tokentype == ETokenType.ttsemicolon) { 1169 gst = EFindSqlStateType.stnormal; 1170 gcurrentsqlstatement.sourcetokenlist.add(ast); 1171 gcurrentsqlstatement.semicolonended = ast; 1172 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 1173 continue; 1174 } 1175 1176 if (sourcetokenlist.sqlplusaftercurtoken()) //most probaly is / cmd 1177 { 1178 gst = EFindSqlStateType.stnormal; 1179 gcurrentsqlstatement.sourcetokenlist.add(ast); 1180 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 1181 continue; 1182 } 1183 1184 // Check if a DDL keyword starts a new SQL statement without semicolon separator. 1185 // Guard against false positives: 1186 // - GRANT/REVOKE use CREATE/ALTER/DROP as privilege names 1187 // - CREATE TASK body can contain any DDL 1188 // - CREATE OR ALTER pattern: ALTER follows OR inside a CREATE statement 1189 if (ast.tokencode == TBaseType.rrw_create 1190 || ast.tokencode == TBaseType.rrw_alter 1191 || ast.tokencode == TBaseType.rrw_drop) { 1192 boolean shouldCheckSplit = true; 1193 1194 // Don't split inside GRANT/REVOKE (CREATE/ALTER/DROP are privilege names) 1195 if (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstoraclegrant 1196 || gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstoraclerevoke 1197 || gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstGrant 1198 || gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstRevoke) { 1199 shouldCheckSplit = false; 1200 } 1201 1202 // Don't split inside CREATE TASK (task body can contain DDL) 1203 if (gcurrentsqlstatement instanceof TCreateTaskStmt) { 1204 shouldCheckSplit = false; 1205 } 1206 1207 // Don't split ALTER/DROP when preceded by OR (CREATE OR ALTER pattern) 1208 if (shouldCheckSplit && (ast.tokencode == TBaseType.rrw_alter || ast.tokencode == TBaseType.rrw_drop)) { 1209 TSourceToken prevSolid = ast.prevSolidToken(); 1210 if (prevSolid != null && prevSolid.tokencode == TBaseType.rrw_or) { 1211 shouldCheckSplit = false; 1212 } 1213 } 1214 1215 if (shouldCheckSplit) { 1216 TCustomSqlStatement lcnextsqlstmt = sqlcmds.issql(ast, gst, gcurrentsqlstatement); 1217 if (lcnextsqlstmt != null) { 1218 // Finalize current statement and start the new one 1219 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 1220 gcurrentsqlstatement = lcnextsqlstmt; 1221 gcurrentsqlstatement.sourcetokenlist.add(ast); 1222 if (gcurrentsqlstatement.issnowflakeplsql()) { 1223 nestedProcedures = 0; 1224 gst = EFindSqlStateType.ststoredprocedure; 1225 } 1226 // else stay in stsql 1227 continue; 1228 } 1229 } 1230 } 1231 1232 // Check if SET starts a new statement without semicolon separator. 1233 // Guard against false positives: 1234 // - UPDATE ... SET is the SET clause of an UPDATE statement 1235 // - ALTER SESSION/TABLE ... SET is part of the ALTER statement 1236 // - CREATE TASK body can contain SET 1237 if (ast.tokencode == TBaseType.rrw_set) { 1238 boolean shouldCheckSplit = true; 1239 1240 // Don't split SET inside UPDATE (SET clause) 1241 if (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstupdate) { 1242 shouldCheckSplit = false; 1243 } 1244 1245 // Don't split SET inside any ALTER statement (ALTER SESSION SET, ALTER TABLE SET, 1246 // ALTER VIEW SET, ALTER FUNCTION SET, etc.) 1247 { 1248 String typeName = gcurrentsqlstatement.sqlstatementtype.name().toLowerCase(); 1249 if (typeName.startsWith("sstalter") || typeName.startsWith("sst_alter")) { 1250 shouldCheckSplit = false; 1251 } 1252 } 1253 1254 // Don't split inside CREATE TASK (task body can contain SET) 1255 if (gcurrentsqlstatement instanceof TCreateTaskStmt) { 1256 shouldCheckSplit = false; 1257 } 1258 1259 // Don't split inside MERGE (SET clause in WHEN MATCHED THEN UPDATE SET) 1260 if (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstmerge) { 1261 shouldCheckSplit = false; 1262 } 1263 1264 if (shouldCheckSplit) { 1265 TCustomSqlStatement lcnextsqlstmt = sqlcmds.issql(ast, gst, gcurrentsqlstatement); 1266 if (lcnextsqlstmt != null) { 1267 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 1268 gcurrentsqlstatement = lcnextsqlstmt; 1269 gcurrentsqlstatement.sourcetokenlist.add(ast); 1270 continue; 1271 } 1272 } 1273 } 1274 1275 gcurrentsqlstatement.sourcetokenlist.add(ast); 1276 break; 1277 }//case stsql 1278 1279 case ststoredprocedure: { 1280 if (procedure_status[nestedProcedures] != stored_procedure_status.bodyend) { 1281 gcurrentsqlstatement.sourcetokenlist.add(ast); 1282 } 1283 1284 switch (procedure_status[nestedProcedures]) { 1285 case start: 1286 if ((ast.tokencode == TBaseType.rrw_as) || (ast.tokencode == TBaseType.rrw_is)) { 1287 // s1 1288 if (sptype[nestedProcedures] != stored_procedure_type.create_trigger) { 1289 if ((sptype[0] == stored_procedure_type.package_spec) && (nestedProcedures > 0)) { 1290 //when it's a package specification, only top level accept as/is 1291 } else { 1292 procedure_status[nestedProcedures] = stored_procedure_status.is_as; 1293 if (ast.searchToken("language", 1) != null) { 1294 // if as language is used in create function, then switch state to stored_procedure_status.body directly. 1295// CREATE OR REPLACE FUNCTION THING.addressparse(p_addressline1 VARCHAR2) RETURN VARCHAR2 AUTHID DEFINER 1296// as Language JAVA NAME 'AddressParser.parse(java.lang.String) return java.lang.String'; 1297// / 1298 if (nestedProcedures == 0) { 1299 // procedure_status[nestedProcedures] = stored_procedure_status.bodyend; 1300 gst = EFindSqlStateType.stsql; 1301 } else { 1302 procedure_status[nestedProcedures] = stored_procedure_status.body; 1303 nestedProcedures--; 1304 //if (nestedProcedures > 0){ nestedProcedures--;} 1305 } 1306 1307 } 1308 } 1309 } 1310 } else if (ast.tokencode == TBaseType.rrw_begin) { 1311 // s4 1312 if (sptype[nestedProcedures] == stored_procedure_type.create_trigger) 1313 waitingEnds[nestedProcedures]++; 1314 1315 if (nestedProcedures > 0) { 1316 nestedProcedures--; 1317 } 1318 procedure_status[nestedProcedures] = stored_procedure_status.body; 1319 } else if (ast.tokencode == TBaseType.rrw_end) { 1320 //s10 1321 if ((nestedProcedures > 0) && (waitingEnds[nestedProcedures - 1] == 1) 1322 && ((sptype[nestedProcedures - 1] == stored_procedure_type.package_body) 1323 || (sptype[nestedProcedures - 1] == stored_procedure_type.package_spec))) { 1324 nestedProcedures--; 1325 procedure_status[nestedProcedures] = stored_procedure_status.bodyend; 1326 } 1327 } else if ((ast.tokencode == TBaseType.rrw_procedure) || (ast.tokencode == TBaseType.rrw_function)) { 1328 //s3 1329 if ((nestedProcedures > 0) && (waitingEnds[nestedProcedures] == 0) 1330 && (procedure_status[nestedProcedures - 1] == stored_procedure_status.is_as)) { 1331 nestedProcedures--; 1332 nestedProcedures++; 1333 waitingEnds[nestedProcedures] = 0; 1334 procedure_status[nestedProcedures] = stored_procedure_status.start; 1335 } 1336 } else if ((sptype[nestedProcedures] == stored_procedure_type.create_trigger) && (ast.tokencode == TBaseType.rrw_declare)) { 1337 procedure_status[nestedProcedures] = stored_procedure_status.is_as; 1338 } else if ((sptype[nestedProcedures] == stored_procedure_type.create_trigger) && (ast.tokentype == ETokenType.ttslash) && (ast.tokencode == TBaseType.sqlpluscmd)) { 1339 // TPlsqlStatementParse(asqlstatement).TerminatorToken := ast; 1340 ast.tokenstatus = ETokenStatus.tsignorebyyacc; 1341 gst = EFindSqlStateType.stnormal; 1342 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 1343 1344 //make / a sqlplus cmd 1345 gcurrentsqlstatement = new TSqlplusCmdStatement(vendor); 1346 gcurrentsqlstatement.sourcetokenlist.add(ast); 1347 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 1348 } else if ((sptype[nestedProcedures] == stored_procedure_type.create_trigger)) { 1349 if (ast.tokencode == TBaseType.rrw_trigger) { 1350 TSourceToken compoundSt = ast.searchToken(TBaseType.rrw_oracle_compound, -1); 1351 if (compoundSt != null) { 1352 //it's trigger with compound trigger block 1353 procedure_status[nestedProcedures] = stored_procedure_status.body; 1354 waitingEnds[nestedProcedures]++; 1355 } 1356 } 1357 } else if ((sptype[nestedProcedures] == stored_procedure_type.function) && (ast.tokencode == TBaseType.rrw_teradata_using)) { 1358 if ((ast.searchToken("aggregate", -1) != null) || (ast.searchToken("pipelined", -1) != null)) { 1359 if (nestedProcedures == 0) { 1360 // procedure_status[nestedProcedures] = stored_procedure_status.bodyend; 1361 gst = EFindSqlStateType.stsql; 1362 } else { 1363 procedure_status[nestedProcedures] = stored_procedure_status.body; 1364 nestedProcedures--; 1365 } 1366 } 1367 1368 } else { 1369 //other tokens, do nothing 1370 if (ast.tokencode == TBaseType.rrw_snowflake_language) { 1371 // check next token which is the language used by this stored procedure 1372 TSourceToken nextSt = ast.nextSolidToken(); 1373 if (nextSt != null) { 1374 if (gcurrentsqlstatement instanceof TRoutine) { // can be TCreateProcedureStmt or TCreateFunctionStmt 1375 TRoutine p = (TRoutine) gcurrentsqlstatement; 1376 p.setRoutineLanguage(nextSt.toString()); 1377 // Switch to stsql for all languages (SQL, JavaScript, Python, etc.) 1378 // The stsql state has proper $$ dollar delimiter handling for procedure bodies. 1379 // For inline scripting bodies (AS BEGIN...END), stsql detects AS+BEGIN/DECLARE 1380 // and switches back to ststoredprocedure as needed. 1381 gst = EFindSqlStateType.stsql; 1382 } 1383 } 1384 } 1385 } 1386 break; 1387 case is_as: 1388 if (isDollarFunctionDelimiter(ast.tokencode, this.vendor)) { 1389 // Procedure body is dollar-quoted (AS $$...$$). 1390 // Switch to stsql mode which has proper dollar delimiter tracking. 1391 gst = EFindSqlStateType.stsql; 1392 inDollarBody = true; 1393 continue; 1394 } else if ((ast.tokencode == TBaseType.rrw_procedure) || (ast.tokencode == TBaseType.rrw_function)) { 1395 // s2 1396 nestedProcedures++; 1397 if (nestedProcedures > stored_procedure_nested_level - 1) { 1398 gst = EFindSqlStateType.sterror; 1399 nestedProcedures--; 1400 } else { 1401 waitingEnds[nestedProcedures] = 0; 1402 procedure_status[nestedProcedures] = stored_procedure_status.start; 1403 } 1404 1405 } else if (ast.tokencode == TBaseType.rrw_begin) { 1406 // s5 1407 if ((nestedProcedures == 0) && 1408 ((sptype[nestedProcedures] == stored_procedure_type.package_body) 1409 || (sptype[nestedProcedures] == stored_procedure_type.package_spec))) { 1410 //top level package or package body's BEGIN keyword already count, 1411 // so don't increase waitingEnds[nestedProcedures] here 1412 1413 } else { 1414 waitingEnds[nestedProcedures]++; 1415 } 1416 procedure_status[nestedProcedures] = stored_procedure_status.body; 1417 } else if (ast.tokencode == TBaseType.rrw_end) { 1418 // s6 1419 if ((nestedProcedures == 0) && (waitingEnds[nestedProcedures] == 1) && 1420 ((sptype[nestedProcedures] == stored_procedure_type.package_body) || (sptype[nestedProcedures] == stored_procedure_type.package_spec))) { 1421 procedure_status[nestedProcedures] = stored_procedure_status.bodyend; 1422 waitingEnds[nestedProcedures]--; 1423 } else { 1424 waitingEnds[nestedProcedures]--; 1425 } 1426 } else if (ast.tokencode == TBaseType.rrw_case) { 1427// if (ast.searchToken(TBaseType.rrw_end,-1) == null){ 1428// //this is not case after END 1429// waitingEnds[nestedProcedures]++; 1430// } 1431 if (ast.searchToken(';', 1) == null) { 1432 //this is not case before ; 1433 waitingEnds[nestedProcedures]++; 1434 } 1435 } else { 1436 //other tokens, do nothing 1437 } 1438 break; 1439 case body: 1440 if ((ast.tokencode == TBaseType.rrw_begin)) { 1441 waitingEnds[nestedProcedures]++; 1442 } else if (ast.tokencode == TBaseType.rrw_if) { 1443 if (ast.searchToken(TBaseType.rrw_snowflake_exists, 1) != null) { 1444 //drop table if exists SANDBOX.ANALYSIS_CONTENT.TABLEAU_DATES; 1445 // don't need END for the above if exists clause 1446 } else if (ast.searchToken(';', 2) == null) { 1447 //this is not if before ; 1448 1449 // 2015-02-27, change 1 to 2 make it able to detect label name after case 1450 // like this: END CASE l1; 1451 waitingEnds[nestedProcedures]++; 1452 } 1453 } else if (ast.tokencode == TBaseType.rrw_for) { 1454 if (ast.searchToken(';', 2) == null) { 1455 //this is not for before ; 1456 1457 // 2015-02-27, change 1 to 2 make it able to detect label name after case 1458 // like this: END CASE l1; 1459 waitingEnds[nestedProcedures]++; 1460 } 1461 } else if (ast.tokencode == TBaseType.rrw_case) { 1462// if (ast.searchToken(TBaseType.rrw_end,-1) == null){ 1463// //this is not case after END 1464// waitingEnds[nestedProcedures]++; 1465// } 1466 if (ast.searchToken(';', 2) == null) { 1467 //this is not case before ; 1468 if (ast.searchToken(TBaseType.rrw_end, -1) == null) { 1469 waitingEnds[nestedProcedures]++; 1470 } 1471 } 1472 } else if (ast.tokencode == TBaseType.rrw_while) { 1473 if (!((ast.searchToken(TBaseType.rrw_end, -1) != null) 1474 && (ast.searchToken(';', 2) != null))) { 1475 // exclude while like this: 1476 // end while [labelname]; 1477 waitingEnds[nestedProcedures]++; 1478 } 1479 } else if (ast.tokencode == TBaseType.rrw_loop) { 1480 if (!((ast.searchToken(TBaseType.rrw_end, -1) != null) 1481 && (ast.searchToken(';', 2) != null))) { 1482 // exclude loop like this: 1483 // end loop [labelname]; 1484 1485 // Also exclude LOOP when it follows a WHILE or FOR condition 1486 // (e.g., WHILE condition LOOP, FOR var IN ... LOOP) 1487 // In those cases, WHILE/FOR already incremented waitingEnds. 1488 // Only standalone LOOP (preceded by ; or block start) should increment. 1489 TSourceToken prevSolid = ast.prevSolidToken(); 1490 boolean isStandaloneLoop = (prevSolid == null) 1491 || (prevSolid.tokentype == ETokenType.ttsemicolon) 1492 || (prevSolid.tokencode == TBaseType.rrw_begin) 1493 || (prevSolid.tokencode == TBaseType.rrw_then) 1494 || (prevSolid.tokencode == TBaseType.rrw_else) 1495 || (prevSolid.tokencode == TBaseType.rrw_loop); 1496 if (isStandaloneLoop) { 1497 waitingEnds[nestedProcedures]++; 1498 } 1499 } 1500 1501// if (ast.searchToken(TBaseType.rrw_end,-1) == null){ 1502// //this is not loop after END 1503// waitingEnds[nestedProcedures]++; 1504//// } 1505//// if (ast.searchToken(';',2) == null){ 1506//// //this is no loop before ; 1507//// waitingEnds[nestedProcedures]++; 1508// } else if (ast.searchToken(TBaseType.rrw_null,1) != null){ 1509// // mantis bug tracking system: #65 1510// waitingEnds[nestedProcedures]++; 1511// } 1512 } else if (ast.tokencode == TBaseType.rrw_end) { 1513 //foundEnd = true; 1514 waitingEnds[nestedProcedures]--; 1515 //if (waitingEnd < 0) { waitingEnd = 0;} 1516 if (waitingEnds[nestedProcedures] == 0) { 1517 if (nestedProcedures == 0) { 1518 // s7 1519 procedure_status[nestedProcedures] = stored_procedure_status.bodyend; 1520 } else { 1521 // s71 1522 nestedProcedures--; 1523 procedure_status[nestedProcedures] = stored_procedure_status.is_as; 1524 } 1525 } 1526 } else if ((waitingEnds[nestedProcedures] == 0) && (ast.tokentype == ETokenType.ttslash) && (ast.tokencode == TBaseType.sqlpluscmd)) //and (prevst.NewlineIsLastTokenInTailerToken)) then 1527 { 1528 //sql ref: c:\prg\gsqlparser\Test\TestCases\oracle\createtrigger.sql, line 53 1529 ast.tokenstatus = ETokenStatus.tsignorebyyacc; 1530 gst = EFindSqlStateType.stnormal; 1531 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 1532 1533 //make / a sqlplus cmd 1534 gcurrentsqlstatement = new TSqlplusCmdStatement(vendor); 1535 gcurrentsqlstatement.sourcetokenlist.add(ast); 1536 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 1537 } 1538 break; 1539 case bodyend: 1540 if ((ast.tokentype == ETokenType.ttslash) && (ast.tokencode == TBaseType.sqlpluscmd)) //and (prevst.NewlineIsLastTokenInTailerToken)) then 1541 { 1542 // TPlsqlStatementParse(asqlstatement).TerminatorToken := ast; 1543 ast.tokenstatus = ETokenStatus.tsignorebyyacc; 1544 gst = EFindSqlStateType.stnormal; 1545 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 1546 1547 //make / a sqlplus cmd 1548 gcurrentsqlstatement = new TSqlplusCmdStatement(vendor); 1549 gcurrentsqlstatement.sourcetokenlist.add(ast); 1550 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 1551 } else if ((ast.tokentype == ETokenType.ttperiod) && (sourcetokenlist.returnaftercurtoken(false)) && (sourcetokenlist.returnbeforecurtoken(false))) { // single dot at a seperate line 1552 ast.tokenstatus = ETokenStatus.tsignorebyyacc; 1553 gst = EFindSqlStateType.stnormal; 1554 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 1555 1556 //make ttperiod a sqlplus cmd 1557 gcurrentsqlstatement = new TSqlplusCmdStatement(vendor); 1558 gcurrentsqlstatement.sourcetokenlist.add(ast); 1559 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 1560 } else if ((ast.searchToken(TBaseType.rrw_package, 1) != null) && (!endBySlashOnly)) { 1561 gcurrentsqlstatement.sourcetokenlist.add(ast); 1562 gst = EFindSqlStateType.stnormal; 1563 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 1564 } else if ((ast.searchToken(TBaseType.rrw_procedure, 1) != null) && (!endBySlashOnly)) { 1565 gcurrentsqlstatement.sourcetokenlist.add(ast); 1566 gst = EFindSqlStateType.stnormal; 1567 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 1568 } else if ((ast.searchToken(TBaseType.rrw_function, 1) != null) && (!endBySlashOnly)) { 1569 gcurrentsqlstatement.sourcetokenlist.add(ast); 1570 gst = EFindSqlStateType.stnormal; 1571 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 1572 } else if ((ast.searchToken(TBaseType.rrw_create, 1) != null) && (ast.searchToken(TBaseType.rrw_package, 4) != null) && (!endBySlashOnly)) { 1573 gcurrentsqlstatement.sourcetokenlist.add(ast); 1574 gst = EFindSqlStateType.stnormal; 1575 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 1576 } else if ((ast.searchToken(TBaseType.rrw_create, 1) != null) && (ast.searchToken(TBaseType.rrw_library, 4) != null) && (!endBySlashOnly)) { 1577 gcurrentsqlstatement.sourcetokenlist.add(ast); 1578 gst = EFindSqlStateType.stnormal; 1579 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 1580 } else if ((ast.searchToken(TBaseType.rrw_alter, 1) != null) && (ast.searchToken(TBaseType.rrw_trigger, 2) != null) && (!endBySlashOnly)) { 1581 gcurrentsqlstatement.sourcetokenlist.add(ast); 1582 gst = EFindSqlStateType.stnormal; 1583 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 1584 } else if ((ast.searchToken(TBaseType.rrw_select, 1) != null) && (!endBySlashOnly)) { 1585 gcurrentsqlstatement.sourcetokenlist.add(ast); 1586 gst = EFindSqlStateType.stnormal; 1587 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 1588 } else if ((ast.searchToken(TBaseType.rrw_commit, 1) != null) && (!endBySlashOnly)) { 1589 gcurrentsqlstatement.sourcetokenlist.add(ast); 1590 gst = EFindSqlStateType.stnormal; 1591 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 1592 } else if (((ast.searchToken(TBaseType.rrw_grant, 1) != null) 1593 || (ast.searchToken(TBaseType.rrw_revoke, 1) != null)) && (!endBySlashOnly)) { 1594 gcurrentsqlstatement.sourcetokenlist.add(ast); 1595 gst = EFindSqlStateType.stnormal; 1596 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 1597 } else if ((gcurrentsqlstatement instanceof TCreateTaskStmt) && (ast.tokencode == ';')) { 1598 gcurrentsqlstatement.sourcetokenlist.add(ast); 1599 gst = EFindSqlStateType.stnormal; 1600 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 1601 } else if ((ast.tokentype == ETokenType.ttsemicolon) && (!endBySlashOnly) 1602 && isStandaloneScriptingBlock(gcurrentsqlstatement)) { 1603 // Semicolon terminates standalone scripting blocks (WHILE/FOR/LOOP/IF) 1604 // Wrap with synthetic BEGIN...END so the parser sees pl_block: BEGIN proc_stmt END 1605 gcurrentsqlstatement.sourcetokenlist.add(ast); 1606 1607 TSourceToken synBegin = new TSourceToken("BEGIN"); 1608 synBegin.tokencode = TBaseType.rrw_begin; 1609 synBegin.tokentype = ETokenType.ttkeyword; 1610 gcurrentsqlstatement.sourcetokenlist.add(0, synBegin); 1611 1612 TSourceToken synEnd = new TSourceToken("END"); 1613 synEnd.tokencode = TBaseType.rrw_end; 1614 synEnd.tokentype = ETokenType.ttkeyword; 1615 gcurrentsqlstatement.sourcetokenlist.add(synEnd); 1616 1617 TSourceToken synSemicolon = new TSourceToken(";"); 1618 synSemicolon.tokencode = ';'; 1619 synSemicolon.tokentype = ETokenType.ttsemicolon; 1620 gcurrentsqlstatement.sourcetokenlist.add(synSemicolon); 1621 1622 gst = EFindSqlStateType.stnormal; 1623 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 1624 } else { 1625 gcurrentsqlstatement.sourcetokenlist.add(ast); 1626 } 1627 break; 1628 case end: 1629 break; 1630 default: 1631 break; 1632 } 1633 1634 1635 if (ast.tokencode == TBaseType.sqlpluscmd) { 1636 //change tokencode back to keyword or TBaseType.ident, because sqlplus cmd 1637 //in a sql statement(almost is plsql block) is not really a sqlplus cmd 1638 int m = flexer.getkeywordvalue(ast.getAstext()); 1639 if (m != 0) { 1640 ast.tokencode = m; 1641 } else if (ast.tokentype == ETokenType.ttslash) { 1642 ast.tokencode = '/'; 1643 } else { 1644 ast.tokencode = TBaseType.ident; 1645 } 1646 } 1647 1648 final int wrapped_keyword_max_pos = 20; 1649 if ((ast.tokencode == TBaseType.rrw_wrapped) && (ast.posinlist - gcurrentsqlstatement.sourcetokenlist.get(0).posinlist < wrapped_keyword_max_pos)) { 1650 if (gcurrentsqlstatement instanceof TCommonStoredProcedureSqlStatement) { 1651 ((TCommonStoredProcedureSqlStatement) gcurrentsqlstatement).setWrapped(true); 1652 } 1653 1654 if (gcurrentsqlstatement instanceof TPlsqlCreatePackage) { 1655 if (ast.prevSolidToken() != null) { 1656 ((TPlsqlCreatePackage) gcurrentsqlstatement).setPackageName(fparser.getNf().createObjectNameWithPart(ast.prevSolidToken())); 1657 } 1658 } 1659 } 1660 1661 break; 1662 } //ststoredprocedure 1663 } //switch 1664 }//for 1665 1666 //last statement 1667 if ((gcurrentsqlstatement != null) && 1668 ((gst == EFindSqlStateType.stsqlplus) || (gst == EFindSqlStateType.stsql) || (gst == EFindSqlStateType.ststoredprocedure) || 1669 (gst == EFindSqlStateType.sterror))) { 1670 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, true, builder); 1671 } 1672 1673 return syntaxErrors.size(); 1674 } 1675 1676 // ========== Helper Methods ========== 1677 1678 /** 1679 * Check if a scripting block statement (WHILE/FOR/LOOP/IF) needs synthetic BEGIN...END wrapping. 1680 * Returns true if the first solid token in the token list is a scripting keyword. 1681 */ 1682 private boolean isStandaloneScriptingBlock(TCustomSqlStatement stmt) { 1683 for (int i = 0; i < stmt.sourcetokenlist.size(); i++) { 1684 TSourceToken t = stmt.sourcetokenlist.get(i); 1685 if (t.tokentype == ETokenType.ttwhitespace || t.tokentype == ETokenType.ttreturn) continue; 1686 if (t.tokencode == TBaseType.cmtdoublehyphen || t.tokencode == TBaseType.cmtslashstar) continue; 1687 return t.tokencode == TBaseType.rrw_while 1688 || t.tokencode == TBaseType.rrw_for 1689 || t.tokencode == TBaseType.rrw_loop 1690 || t.tokencode == TBaseType.rrw_if; 1691 } 1692 return false; 1693 } 1694 1695 /** 1696 * Check if a position is valid for treating division operator as SQL*Plus command. 1697 */ 1698 private boolean IsValidPlaceForDivToSqlplusCmd(TSourceTokenList pstlist, int pPos) { 1699 boolean ret = false; 1700 1701 if ((pPos <= 0) || (pPos > pstlist.size() - 1)) return ret; 1702 //tokentext directly before div must be ttreturn without space appending it 1703 TSourceToken lcst = pstlist.get(pPos - 1); 1704 if (lcst.tokencode == TBaseType.lexnewline) { 1705 String astext = lcst.getAstext(); 1706 ret = (astext.length() > 0 && astext.charAt(astext.length() - 1) == '\n'); 1707 } 1708 1709 return ret; 1710 } 1711 1712 /** 1713 * Placeholder function for PostgreSQL-style SQL*Plus commands. 1714 * Always returns false for Snowflake (not applicable). 1715 */ 1716 private boolean isvalidsqlpluscmdInPostgresql(String astr) { 1717 return false; 1718 } 1719 1720 // Note: isDollarFunctionDelimiter() is now inherited from AbstractSqlParser 1721 // The parent implementation handles all PostgreSQL-family databases including Snowflake 1722 1723 @Override 1724 public String toString() { 1725 return "SnowflakeSqlParser{vendor=" + vendor + "}"; 1726 } 1727}