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