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