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