001package gudusoft.gsqlparser.parser;
002
003import gudusoft.gsqlparser.EDbVendor;
004import gudusoft.gsqlparser.TBaseType;
005import gudusoft.gsqlparser.TGSqlParser;
006import gudusoft.gsqlparser.TCustomLexer;
007import gudusoft.gsqlparser.TCustomParser;
008import gudusoft.gsqlparser.TCustomSqlStatement;
009import gudusoft.gsqlparser.TLexerMssql;
010import gudusoft.gsqlparser.TParserMssqlSql;
011import gudusoft.gsqlparser.TSourceToken;
012import gudusoft.gsqlparser.TSourceTokenList;
013import gudusoft.gsqlparser.TStatementList;
014import gudusoft.gsqlparser.TSyntaxError;
015import gudusoft.gsqlparser.EFindSqlStateType;
016import gudusoft.gsqlparser.ETokenType;
017import gudusoft.gsqlparser.ETokenStatus;
018import gudusoft.gsqlparser.ESqlStatementType;
019import gudusoft.gsqlparser.EErrorType;
020import gudusoft.gsqlparser.stmt.TUnknownSqlStatement;
021import gudusoft.gsqlparser.stmt.mssql.TMssqlBlock;
022import gudusoft.gsqlparser.stmt.mssql.TMssqlExecute;
023import gudusoft.gsqlparser.sqlcmds.ISqlCmds;
024import gudusoft.gsqlparser.sqlcmds.SqlCmdsFactory;
025import gudusoft.gsqlparser.compiler.TContext;
026import gudusoft.gsqlparser.sqlenv.TSQLEnv;
027import gudusoft.gsqlparser.compiler.TGlobalScope;
028import gudusoft.gsqlparser.compiler.TFrame;
029import gudusoft.gsqlparser.resolver.TSQLResolver;
030import gudusoft.gsqlparser.resolver2.TSQLResolver2;
031import gudusoft.gsqlparser.resolver2.TSQLResolverConfig;
032import gudusoft.gsqlparser.TLog;
033import gudusoft.gsqlparser.compiler.TASTEvaluator;
034
035import java.io.BufferedReader;
036import java.util.ArrayList;
037import java.util.List;
038import java.util.Stack;
039
040import static gudusoft.gsqlparser.ESqlStatementType.sstmssqlreturn;
041
042/**
043 * Microsoft SQL Server database SQL parser implementation.
044 *
045 * <p>This parser handles SQL Server-specific SQL syntax including:
046 * <ul>
047 *   <li>T-SQL blocks (stored procedures, functions, triggers)</li>
048 *   <li>BEGIN/END blocks</li>
049 *   <li>TRY/CATCH error handling</li>
050 *   <li>GO batch separator</li>
051 *   <li>SQL Server-specific DML/DDL (MERGE, OPENROWSET, etc.)</li>
052 *   <li>Special token handling (LOCK TABLE, COPY INTO, etc.)</li>
053 * </ul>
054 *
055 * <p><b>Design Notes:</b>
056 * <ul>
057 *   <li>Extends {@link AbstractSqlParser} using the template method pattern</li>
058 *   <li>Uses {@link TLexerMssql} for tokenization</li>
059 *   <li>Uses {@link TParserMssqlSql} for parsing</li>
060 *   <li>Delimiter character: ';' for SQL statements</li>
061 * </ul>
062 *
063 * <p><b>Usage Example:</b>
064 * <pre>
065 * // Get SQL Server parser from factory
066 * SqlParser parser = SqlParserFactory.get(EDbVendor.dbvmssql);
067 *
068 * // Build context
069 * ParserContext context = new ParserContext.Builder(EDbVendor.dbvmssql)
070 *     .sqlText("SELECT * FROM Employees WHERE DepartmentID = 10")
071 *     .build();
072 *
073 * // Parse
074 * SqlParseResult result = parser.parse(context);
075 *
076 * // Access statements
077 * TStatementList statements = result.getSqlStatements();
078 * </pre>
079 *
080 * @see SqlParser
081 * @see AbstractSqlParser
082 * @see TLexerMssql
083 * @see TParserMssqlSql
084 * @since 3.2.0.0
085 */
086public class MssqlSqlParser extends AbstractSqlParser {
087
088    /**
089     * Construct SQL Server SQL parser.
090     * <p>
091     * Configures the parser for SQL Server database with default delimiter (;).
092     * <p>
093     * Following the original TGSqlParser pattern, the lexer and parser are
094     * created once in the constructor and reused for all parsing operations.
095     */
096    public MssqlSqlParser() {
097        super(EDbVendor.dbvmssql);
098        this.delimiterChar = ';';
099        this.defaultDelimiterStr = ";";
100
101        // Create lexer once - will be reused for all parsing operations
102        this.flexer = new TLexerMssql();
103        this.flexer.delimiterchar = this.delimiterChar;
104        this.flexer.defaultDelimiterStr = this.defaultDelimiterStr;
105
106        // Set parent's lexer reference for shared tokenization logic
107        this.lexer = this.flexer;
108
109        // Create parser once - will be reused for all parsing operations
110        this.fparser = new TParserMssqlSql(null);
111        this.fparser.lexer = this.flexer;
112    }
113
114    // ========== Parser Components ==========
115
116    /** The SQL Server lexer used for tokenization */
117    public TLexerMssql flexer;
118
119    /** SQL parser (for SQL Server statements) */
120    private TParserMssqlSql fparser;
121
122    /** Current statement being built during extraction */
123    private TCustomSqlStatement gcurrentsqlstatement;
124
125    // Note: Global context and frame stack fields inherited from AbstractSqlParser:
126    // - protected TContext globalContext
127    // - protected TSQLEnv sqlEnv
128    // - protected Stack<TFrame> frameStack
129    // - protected TFrame globalFrame
130
131    // ========== AbstractSqlParser Abstract Methods Implementation ==========
132
133    /**
134     * Return the SQL Server lexer instance.
135     */
136    @Override
137    protected TCustomLexer getLexer(ParserContext context) {
138        return this.flexer;
139    }
140
141    /**
142     * Return the SQL Server SQL parser instance with updated token list.
143     */
144    @Override
145    protected TCustomParser getParser(ParserContext context, TSourceTokenList tokens) {
146        this.fparser.sourcetokenlist = tokens;
147        return this.fparser;
148    }
149
150    /**
151     * Call MSSQL-specific tokenization logic.
152     * <p>
153     * Delegates to domssqlsqltexttotokenlist which handles SQL Server's
154     * specific keyword recognition, bracket identifiers, and token generation.
155     */
156    @Override
157    protected void tokenizeVendorSql() {
158        domssqlsqltexttotokenlist();
159    }
160
161    /**
162     * Setup MSSQL parser for raw statement extraction.
163     * <p>
164     * MSSQL uses a single parser, so we inject sqlcmds and update
165     * the token list for the main parser only.
166     */
167    @Override
168    protected void setupVendorParsersForExtraction() {
169        // Inject sqlcmds into parser (required for make_stmt)
170        this.fparser.sqlcmds = this.sqlcmds;
171
172        // Update token list for parser
173        this.fparser.sourcetokenlist = this.sourcetokenlist;
174    }
175
176    /**
177     * Call MSSQL-specific raw statement extraction logic.
178     * <p>
179     * Delegates to domssqlgetrawsqlstatements which handles SQL Server's
180     * statement delimiters (semicolon and GO command).
181     */
182    @Override
183    protected void extractVendorRawStatements(SqlParseResult.Builder builder) {
184        domssqlgetrawsqlstatements(builder);
185    }
186
187    /**
188     * Perform full parsing of statements with syntax checking.
189     * <p>
190     * This method orchestrates the parsing of all statements.
191     */
192    @Override
193    protected TStatementList performParsing(ParserContext context,
194                                           TCustomParser parser,
195                                           TCustomParser secondaryParser,
196                                           TSourceTokenList tokens,
197                                           TStatementList rawStatements) {
198        // Store references
199        this.fparser = (TParserMssqlSql) parser;
200        this.sourcetokenlist = tokens;
201        this.parserContext = context;
202
203        // Use the raw statements passed from AbstractSqlParser.parse()
204        this.sqlstatements = rawStatements;
205
206        // Initialize statement parsing infrastructure. Use the context vendor
207        // so delegated dialects (e.g. dbvexasol) get their own cmds resolver.
208        this.sqlcmds = SqlCmdsFactory.get(context != null ? context.getVendor() : vendor);
209
210        // Inject sqlcmds into parser (required for make_stmt and other methods)
211        this.fparser.sqlcmds = this.sqlcmds;
212
213        // Initialize global context for semantic analysis
214        // CRITICAL: When delegated from TGSqlParser, use TGSqlParser's frameStack
215        // so that variables set in SET statements can be found by EXECUTE statements
216        if (context != null && context.getGsqlparser() != null) {
217            TGSqlParser gsqlparser = (TGSqlParser) context.getGsqlparser();
218            this.frameStack = gsqlparser.getFrameStack();
219
220            // CRITICAL: Set gsqlparser on the NodeFactory - matches TGSqlParser behavior
221            // This is needed for proper AST node creation during parsing
222            this.fparser.getNf().setGsqlParser(gsqlparser);
223
224            // Create global context if needed
225            this.globalContext = new TContext();
226            this.sqlEnv = new TSQLEnv(this.vendor) {
227                @Override
228                public void initSQLEnv() {
229                }
230            };
231            this.globalContext.setSqlEnv(this.sqlEnv, this.sqlstatements);
232        } else {
233            initializeGlobalContext();
234        }
235
236        // Parse each statement with exception handling for robustness
237        for (int i = 0; i < sqlstatements.size(); i++) {
238            TCustomSqlStatement stmt = sqlstatements.getRawSql(i);
239
240            try {
241                stmt.setFrameStack(frameStack);
242
243                // Parse the statement
244                int parseResult = stmt.parsestatement(null, false, context.isOnlyNeedRawParseTree());
245
246                // Handle error recovery for CREATE TABLE/INDEX
247                boolean doRecover = TBaseType.ENABLE_ERROR_RECOVER_IN_CREATE_TABLE;
248                if (doRecover && ((parseResult != 0) || (stmt.getErrorCount() > 0))) {
249                    handleCreateTableErrorRecovery(stmt);
250                }
251
252                // Collect syntax errors
253                if ((parseResult != 0) || (stmt.getErrorCount() > 0)) {
254                    copyErrorsFromStatement(stmt);
255                }
256
257            } catch (Exception ex) {
258                // Use inherited exception handler from AbstractSqlParser
259                // This provides consistent error handling across all database parsers
260               // ex.printStackTrace();
261                handleStatementParsingException(stmt, i, ex);
262                continue;
263            }
264        }
265
266        // Clean up frame stack
267        if (globalFrame != null) {
268            globalFrame.popMeFromStack(frameStack);
269        }
270
271        return this.sqlstatements;
272    }
273
274    // Note: initializeGlobalContext() inherited from AbstractSqlParser
275    // Note: No override of afterStatementParsed() needed - default (no-op) is appropriate for MSSQL
276
277    /**
278     * Exasol pre-pass: the Exasol dialect shares this parser, and its extra
279     * syntax is keyed on synthetic tokens (TBaseType.rrw_exasol_*) that the
280     * MSSQL lexer never emits. When the requested dialect is dbvexasol, this
281     * pass rewrites the Exasol-only words to those token codes (and hides a
282     * few tokens the shared grammar cannot carry, e.g. OR REPLACE after
283     * CREATE). Real T-SQL input never reaches the Exasol grammar rules.
284     */
285    @Override
286    protected void processTokensBeforeParse(ParserContext context, TSourceTokenList tokens) {
287        super.processTokensBeforeParse(context, tokens);
288        if (context != null && context.getVendor() == EDbVendor.dbvmssql
289                && context.isMssqlColonBindVariablesEnabled()) {
290            mergeMssqlColonBindVariables(tokens);
291        }
292        if (context == null || context.getVendor() != EDbVendor.dbvexasol) {
293            return;
294        }
295        boolean stmtHasInvalid = false;
296        TSourceToken stmtFirst = null;
297        TSourceToken prevSolid = null;
298        for (int i = 0; i < tokens.size(); i++) {
299            TSourceToken st = tokens.get(i);
300            if (st.tokentype == ETokenType.ttwhitespace || st.tokentype == ETokenType.ttreturn
301                    || st.tokentype == ETokenType.ttsimplecomment || st.tokentype == ETokenType.ttbracketedcomment) {
302                continue;
303            }
304            if (st.tokentype == ETokenType.ttsemicolon) {
305                stmtHasInvalid = false;
306                stmtFirst = null;
307                prevSolid = null;
308                continue;
309            }
310            if (stmtFirst == null) {
311                stmtFirst = st;
312            }
313            TSourceToken next = st.nextSolidToken();
314            if (st.tokentype == ETokenType.ttidentifier || st.tokentype == ETokenType.ttkeyword) {
315                // CREATE OR REPLACE <obj>: hide OR REPLACE (T-SQL grammar has
316                // CREATE [OR ALTER] only; Exasol semantics identical for parse).
317                if (st.tokencode == TBaseType.rrw_or && prevSolid == stmtFirst
318                        && wordIs(stmtFirst, "create") && wordIs(next, "replace")) {
319                    hideToken(st);
320                    hideToken(next);
321                    continue;
322                }
323                // (EXPLAIN VIRTUAL <select>) as a derived table: hide the
324                // EXPLAIN VIRTUAL introducer so the inner select parses as a
325                // plain subquery.
326                if (wordIs(st, "explain")
327                        && prevSolid != null && prevSolid.tokentype == ETokenType.ttleftparenthesis
328                        && wordIs(next, "virtual")) {
329                    hideToken(st);
330                    hideToken(next);
331                    continue;
332                }
333                if (wordIs(st, "qualify") && st.tokentype == ETokenType.ttidentifier) {
334                    st.tokencode = TBaseType.rrw_exasol_qualify;
335                } else if (wordIs(st, "limit") && st.tokentype == ETokenType.ttidentifier) {
336                    st.tokencode = TBaseType.rrw_mssql_limit;
337                } else if (wordIs(st, "invalid") && st.tokentype == ETokenType.ttidentifier
338                        && startsVerificationKind(next)) {
339                    st.tokencode = TBaseType.rrw_exasol_invalid;
340                    stmtHasInvalid = true;
341                    // hide an optional preceding WITH (SELECT cols WITH INVALID ...)
342                    if (prevSolid != null && prevSolid.tokencode == TBaseType.rrw_with) {
343                        hideToken(prevSolid);
344                    }
345                } else if (wordIs(st, "referencing") && st.tokentype == ETokenType.ttidentifier && stmtHasInvalid) {
346                    st.tokencode = TBaseType.rrw_exasol_referencing;
347                } else if (wordIs(st, "distribute") && next != null && next.tokencode == TBaseType.rrw_by) {
348                    st.tokencode = TBaseType.rrw_exasol_distribute;
349                } else if (wordIs(st, "distribution") && prevSolid != null
350                        && prevSolid.tokencode == TBaseType.rrw_drop && wordIs(next, "keys")) {
351                    st.tokencode = TBaseType.rrw_exasol_distribute;
352                } else if (wordIs(st, "including")
353                        && (wordIs(next, "defaults") || wordIs(next, "identity") || wordIs(next, "comments"))) {
354                    st.tokencode = TBaseType.rrw_exasol_including;
355                } else if (st.tokencode == TBaseType.rrw_mssql_partition
356                        && next != null && next.tokencode == TBaseType.rrw_by
357                        && prevSolid != null && prevSolid.tokentype == ETokenType.ttcomma
358                        && (wordIs(stmtFirst, "create") || wordIs(stmtFirst, "alter"))) {
359                    // PARTITION BY as a CREATE/ALTER TABLE element (never after
360                    // '(' — that is a window function's OVER(PARTITION BY ...)).
361                    st.tokencode = TBaseType.rrw_exasol_partition;
362                } else if (st.tokencode == TBaseType.rrw_mssql_prior
363                        && (prevSolid == null || prevSolid.tokencode != TBaseType.rrw_fetch)) {
364                    st.tokencode = TBaseType.rrw_exasol_prior;
365                } else if ((wordIs(st, "disable") || wordIs(st, "enable"))
366                        && prevSolid != null && prevSolid.tokentype == ETokenType.ttrightparenthesis) {
367                    // constraint state after REFERENCES t (cols)
368                    st.tokencode = TBaseType.rrw_exasol_disable;
369                } else if (wordIs(st, "admin") && st.tokentype == ETokenType.ttidentifier
370                        && prevSolid != null && prevSolid.tokencode == TBaseType.rrw_with
371                        && wordIs(next, "option")) {
372                    st.tokencode = TBaseType.rrw_mssql_admin;
373                } else if (wordIs(st, "access") && st.tokentype == ETokenType.ttidentifier
374                        && (wordIs(stmtFirst, "grant") || wordIs(stmtFirst, "revoke"))
375                        && wordIs(next, "on")) {
376                    st.tokencode = TBaseType.rrw_mssql_access;
377                }
378            }
379            prevSolid = st;
380        }
381    }
382
383    /**
384     * Merge client-style colon placeholders after ordinary SQL Server
385     * tokenization. Keeping this opt-in transformation out of the lexer leaves
386     * the default token stream, including token coordinates, byte-for-byte
387     * compatible with earlier releases.
388     */
389    private void mergeMssqlColonBindVariables(TSourceTokenList tokens) {
390        for (int i = 0; i + 1 < tokens.size(); i++) {
391            TSourceToken colon = tokens.get(i);
392            TSourceToken suffix = tokens.get(i + 1);
393            if (colon.tokenstatus == ETokenStatus.tsdeleted
394                    || suffix.tokenstatus == ETokenStatus.tsdeleted
395                    || colon.tokencode != ':'
396                    || colon.offset + colon.getAstext().length() != suffix.offset
397                    || !isMssqlColonBindSuffix(suffix.getAstext())) {
398                continue;
399            }
400            colon.setString(colon.getAstext() + suffix.getAstext());
401            colon.tokencode = TBaseType.bind_v;
402            colon.tokentype = ETokenType.ttbindvar;
403            suffix.tokenstatus = ETokenStatus.tsdeleted;
404        }
405    }
406
407    private boolean isMssqlColonBindSuffix(String text) {
408        if (text == null || text.isEmpty()) {
409            return false;
410        }
411        int start = text.charAt(0) == '@' ? 1 : 0;
412        if (start == text.length()) {
413            return false;
414        }
415        for (int i = start; i < text.length(); i++) {
416            char ch = text.charAt(i);
417            if (!(ch == '_' || ch >= 'A' && ch <= 'Z'
418                    || ch >= 'a' && ch <= 'z' || ch >= '0' && ch <= '9')) {
419                return false;
420            }
421        }
422        return true;
423    }
424
425    private static boolean startsVerificationKind(TSourceToken t) {
426        if (t == null) return false;
427        TSourceToken t2 = t.nextSolidToken();
428        if (wordIs(t, "unique")) {
429            return t2 != null && t2.tokentype == ETokenType.ttleftparenthesis;
430        }
431        if (wordIs(t, "primary") || wordIs(t, "foreign")) {
432            TSourceToken t3 = t2 == null ? null : t2.nextSolidToken();
433            return wordIs(t2, "key") && t3 != null && t3.tokentype == ETokenType.ttleftparenthesis;
434        }
435        return false;
436    }
437
438    /** Case-insensitive command-word comparison (keywords, not identifiers). */
439    private static boolean wordIs(TSourceToken t, String w) {
440        return t != null && t.toString().equalsIgnoreCase(w);
441    }
442
443    /**
444     * True when the routine collected so far is a CLR routine, i.e. its body is
445     * {@code AS EXTERNAL NAME assembly.class.method} rather than a BEGIN...END
446     * block. Such a statement ends at its semicolon; there is no body to wait for.
447     */
448    private static boolean hasExternalNameBody(TCustomSqlStatement stmt) {
449        if (stmt == null) return false;
450        TSourceTokenList tokens = stmt.sourcetokenlist;
451        if (tokens == null) return false;
452        TSourceToken prevSolid = null;
453        for (int i = 0; i < tokens.size(); i++) {
454            TSourceToken t = tokens.get(i);
455            if (t.isnonsolidtoken()) continue;
456            // non-identifier-compare: keyword text. The splitter runs before the
457            // vendor parser, so the vendor-specific RW_EXTERNAL / RW_AS token codes
458            // are not available here; NAME reaches the grammar as a plain ColId.
459            if (wordIs(t, "external") && wordIs(prevSolid, "as")) {
460                return wordIs(t.nextSolidToken(), "name");
461            }
462            prevSolid = t;
463        }
464        return false;
465    }
466
467    /** Neutralize a token for both the splitter and the parser. */
468    private static void hideToken(TSourceToken t) {
469        t.tokentype = ETokenType.ttsimplecomment;
470        t.tokencode = TBaseType.cmtdoublehyphen;
471    }
472
473    /**
474     * Handle error recovery for CREATE TABLE/INDEX statements.
475     */
476    private void handleCreateTableErrorRecovery(TCustomSqlStatement stmt) {
477        if (((stmt.sqlstatementtype == ESqlStatementType.sstcreatetable)
478                || (stmt.sqlstatementtype == ESqlStatementType.sstcreateindex))
479                && (!TBaseType.c_createTableStrictParsing)) {
480
481            int nested = 0;
482            boolean isIgnore = false, isFoundIgnoreToken = false;
483            TSourceToken firstIgnoreToken = null;
484
485            for (int k = 0; k < stmt.sourcetokenlist.size(); k++) {
486                TSourceToken st = stmt.sourcetokenlist.get(k);
487                if (isIgnore) {
488                    if (st.issolidtoken() && (st.tokencode != ';')) {
489                        isFoundIgnoreToken = true;
490                        if (firstIgnoreToken == null) {
491                            firstIgnoreToken = st;
492                        }
493                    }
494                    if (st.tokencode != ';') {
495                        st.tokencode = TBaseType.sqlpluscmd;
496                    }
497                    continue;
498                }
499                if (st.tokencode == (int) ')') {
500                    nested--;
501                    if (nested == 0) {
502                        boolean isSelect = false;
503                        TSourceToken st1 = st.searchToken(TBaseType.rrw_as, 1);
504                        if (st1 != null) {
505                            TSourceToken st2 = st.searchToken((int) '(', 2);
506                            if (st2 != null) {
507                                TSourceToken st3 = st.searchToken(TBaseType.rrw_select, 3);
508                                isSelect = (st3 != null);
509                            }
510                        }
511                        if (!isSelect) isIgnore = true;
512                    }
513                } else if (st.tokencode == (int) '(') {
514                    nested++;
515                }
516            }
517
518            if (isFoundIgnoreToken) {
519                stmt.clearError();
520                stmt.parsestatement(null, false);
521            }
522        }
523    }
524
525    /**
526     * Perform SQL Server-specific semantic analysis.
527     * <p>
528     * When called via delegation from TGSqlParser (context.getGsqlparser() != null),
529     * semantic analysis is skipped here and handled by TGSqlParser.doDelegatedParse()
530     * to ensure resolver2 is stored in TGSqlParser where tests expect to find it.
531     * <p>
532     * When called directly on MssqlSqlParser, uses TBaseType flags to determine
533     * which resolver to use:
534     * <ul>
535     *   <li>TBaseType.isEnableResolver2() -> use TSQLResolver2</li>
536     *   <li>TBaseType.isEnableResolver() -> use TSQLResolver</li>
537     * </ul>
538     */
539    @Override
540    protected void performSemanticAnalysis(ParserContext context, TStatementList statements) {
541        // Skip semantic analysis when delegated from TGSqlParser
542        // TGSqlParser.doDelegatedParse() will handle it to store resolver2 properly
543        if (context != null && context.getGsqlparser() != null) {
544            return;
545        }
546
547        if (getSyntaxErrors().isEmpty()) {
548            if (TBaseType.isEnableResolver2()) {
549                // Use TSQLResolver2
550                TSQLResolverConfig config = new TSQLResolverConfig();
551                config.setVendor(vendor);
552                TSQLResolver2 resolver2 = new TSQLResolver2(null, statements, config);
553                if (this.sqlEnv != null) {
554                    resolver2.setSqlEnv(this.sqlEnv);
555                }
556                resolver2.resolve();
557            } else if (TBaseType.isEnableResolver()) {
558                // Use TSQLResolver
559                TSQLResolver resolver = new TSQLResolver(globalContext, statements);
560                resolver.resolve();
561            }
562            // If neither resolver is enabled, skip semantic analysis
563        }
564    }
565
566    /**
567     * Perform interpretation/evaluation on parsed statements.
568     */
569    @Override
570    protected void performInterpreter(ParserContext context, TStatementList statements) {
571        if (TBaseType.ENABLE_INTERPRETER && getSyntaxErrors().isEmpty()) {
572            TLog.clearLogs();
573            TGlobalScope interpreterScope = new TGlobalScope(sqlEnv);
574            TLog.enableInterpreterLogOnly();
575            TASTEvaluator astEvaluator = new TASTEvaluator(statements, interpreterScope);
576            astEvaluator.eval();
577        }
578    }
579
580    // ========== SQL Server-Specific Tokenization ==========
581
582    /**
583     * SQL Server-specific tokenization logic.
584     * <p>
585     * Extracted from: TGSqlParser.domssqlsqltexttotokenlist() (lines 2225-2425)
586     */
587    private void domssqlsqltexttotokenlist() {
588        TSourceToken lcprevtoken = null;
589        int lcsteps = 0;
590        TSourceToken asourcetoken, lctoken, lctoken2, lctoken3;
591        int yychar;
592        boolean iskeywordgo;
593
594        asourcetoken = getanewsourcetoken();
595
596        if (asourcetoken == null) return;
597
598        yychar = asourcetoken.tokencode;
599
600        boolean lcinopenrowset = false;
601        int lcnested = 0;
602
603        // RENAME is a keyword only where a statement or clause starts with it:
604        // RENAME OBJECT / RENAME DATABASE (Synapse, PDW) and ALTER TABLE ...
605        // RENAME COLUMN / CONSTRAINT. SQL Server does not reserve the word, so
606        // anywhere else it is an identifier, e.g. a table alias (Mantis 4959).
607        // The grammar cannot fall back to an identifier there: RENAME OBJECT may
608        // start the next statement, so the parser reduces on RENAME instead of
609        // shifting it as an alias. Decided when the next solid token arrives.
610        TSourceToken lcpendingrename = null;
611        boolean lcrenamecanbeident = (parserContext != null)
612                && ((parserContext.getVendor() == EDbVendor.dbvmssql)
613                    || (parserContext.getVendor() == EDbVendor.dbvazuresql));
614
615        while (yychar > 0) {
616
617            if ((lcpendingrename != null) && asourcetoken.issolidtoken()) {
618                if (!isRenameStatementWord(asourcetoken)) {
619                    lcpendingrename.tokencode = TBaseType.ident;
620                    lcpendingrename.tokentype = ETokenType.ttidentifier;
621                }
622                lcpendingrename = null;
623            }
624            if (lcrenamecanbeident && (asourcetoken.tokencode == TBaseType.rrw_rename)) {
625                lcpendingrename = asourcetoken;
626            }
627
628            if (asourcetoken.tokencode == TBaseType.rrw_openrowset) {
629                // openrowset(....)
630                lcinopenrowset = true;
631                lcnested = 0;
632            } else if (asourcetoken.tokentype == ETokenType.ttleftparenthesis) {
633                if ((lcsteps > 0) && TBaseType.assigned(lcprevtoken)) {
634                    if (lcprevtoken.tokencode == TBaseType.rrw_primary) {
635                        lcprevtoken.tokencode = TBaseType.rrw_select - 2;  //rw_primary2
636                        checkconstarinttoken(lcprevtoken);
637                    } else if (lcprevtoken.tokencode == TBaseType.rrw_foreign) {
638                        lcprevtoken.tokencode = TBaseType.rrw_select - 4;  //rw_foreign2
639                        checkconstarinttoken(lcprevtoken);
640                    } else if (lcprevtoken.tokencode == TBaseType.rrw_unique) {
641                        lcprevtoken.tokencode = TBaseType.rrw_select - 1;  //rw_unique2
642                        checkconstarinttoken(lcprevtoken);
643                    }
644                    lcprevtoken = null;
645                    lcsteps = 0;
646                }
647
648                // openrowset(....)
649                if (lcinopenrowset)
650                    lcnested++;
651            } else if (asourcetoken.tokentype == ETokenType.ttrightparenthesis) {
652                // openrowset(....)
653                if (lcinopenrowset) {
654                    if ((lcnested > 0))
655                        lcnested--;
656                    if (lcnested == 0)
657                        lcinopenrowset = false;
658                }
659            } else if (asourcetoken.tokentype == ETokenType.ttsemicolon) {
660                if (lcinopenrowset) {
661                    asourcetoken.tokentype = ETokenType.ttsemicolon2;
662                } else {
663                    lctoken2 = asourcetoken.searchToken(TBaseType.rrw_begin, -1);
664                    if (lctoken2 != null) {
665                        asourcetoken.tokencode = TBaseType.SEMI_COLON_AFTER_BEGIN;
666                        asourcetoken.tokentype = ETokenType.ttsemicolon3;
667                    } else {
668                        lctoken2 = asourcetoken.searchToken(TBaseType.rrw_try, -1);
669                        if (lctoken2 == null) {
670                            lctoken2 = asourcetoken.searchToken(TBaseType.rrw_catch, -1);
671                        }
672                        if (lctoken2 != null) {
673                            lctoken3 = asourcetoken.searchToken(TBaseType.rrw_begin, -2);
674                            if (lctoken3 != null) {
675                                asourcetoken.tokencode = TBaseType.SEMI_COLON_AFTER_BEGIN;
676                                asourcetoken.tokentype = ETokenType.ttsemicolon3;
677                            }
678                        }
679                    }
680                }
681
682                lctoken = getprevtoken(asourcetoken);
683                if ((lctoken != null)) {
684                    if (lctoken.tokentype == ETokenType.ttsemicolon) {
685                        // treat this semicolon as a whitespace
686                        asourcetoken.tokencode = TBaseType.lexspace;
687                    }
688                }
689            } else if (asourcetoken.tokentype == ETokenType.ttperiod) {
690                lctoken = getprevtoken(asourcetoken);
691                if (TBaseType.assigned(lctoken)) {
692                    // go.fieldname, go is a table alias, not a go statement
693                    if (lctoken.tokencode == TBaseType.rrw_go) {
694                        lctoken.tokencode = TBaseType.ident;
695                        lctoken.tokentype = ETokenType.ttidentifier;
696                    }
697                }
698            } else if (asourcetoken.tokencode == TBaseType.rrw_table) {
699                lctoken = getprevtoken(asourcetoken);
700                if (TBaseType.assigned(lctoken)) {
701                    if (lctoken.tokencode == TBaseType.rrw_lock) {
702                        lctoken.tokencode = TBaseType.rw_locktable;
703                    }
704                }
705            } else if (asourcetoken.tokencode == TBaseType.rrw_into) {
706                lctoken = getprevtoken(asourcetoken);
707                if (TBaseType.assigned(lctoken)) {
708                    if (lctoken.tokencode == TBaseType.rrw_sqlserver_copy) {
709                        lctoken.tokencode = TBaseType.rrw_sqlserver_copyinto;
710                    }
711                }
712            } else if (asourcetoken.tokencode == TBaseType.rrw_sqlserver_column) {
713                lctoken = getprevtoken(asourcetoken);
714                if (TBaseType.assigned(lctoken)) {
715                    if (lctoken.tokencode == TBaseType.rrw_drop) {
716                        asourcetoken.tokencode = TBaseType.rrw_sqlserver_drop_column;
717                    }
718                }
719            } else if (asourcetoken.tokencode == TBaseType.rrw_go) {
720                iskeywordgo = true;
721                lctoken = getprevtoken(asourcetoken);
722                if (TBaseType.assigned(lctoken)) {
723                    // go should not at same line as other sql statement
724                    if ((lctoken.lineNo == asourcetoken.lineNo) && (lctoken.tokencode != ';')) {
725                        iskeywordgo = false;
726                    }
727                }
728
729                if (iskeywordgo) {
730                    lcinopenrowset = false;
731                    lcnested = 0;
732                    lcprevtoken = asourcetoken;
733                } else {
734                    asourcetoken.tokencode = TBaseType.ident;
735                    asourcetoken.tokentype = ETokenType.ttidentifier;
736                }
737            } else if (asourcetoken.tokencode == TBaseType.rrw_primary) {
738                // primary key [clustered | nonclustered ] [hash] ( column list)
739                lcsteps = 2;
740                lcprevtoken = asourcetoken;
741            } else if (asourcetoken.tokencode == TBaseType.rrw_foreign) {
742                // foreign key [clustered | nonclustered ] ( column list)
743                lcsteps = 2;
744                lcprevtoken = asourcetoken;
745            } else if (asourcetoken.tokencode == TBaseType.rrw_unique) {
746                // unique [clustered | nonclustered ] [hash] ( column list)
747                lcsteps = 1;
748                lcprevtoken = asourcetoken;
749            } else if (asourcetoken.issolidtoken()) {
750                if (lcsteps > 0) {
751                    if (!(TBaseType.mysametext("clustered", asourcetoken.toString())
752                            || TBaseType.mysametext("nonclustered", asourcetoken.toString())
753                            || TBaseType.mysametext("hash", asourcetoken.toString())
754                    ))
755                        lcsteps--;
756                }
757            }
758
759            sourcetokenlist.add(asourcetoken);
760
761            asourcetoken = getanewsourcetoken();
762            if (asourcetoken == null) break;
763            yychar = asourcetoken.tokencode;
764        }
765
766        // RENAME as the last solid token of the input: an identifier
767        if (lcpendingrename != null) {
768            lcpendingrename.tokencode = TBaseType.ident;
769            lcpendingrename.tokentype = ETokenType.ttidentifier;
770        }
771    }
772
773    /**
774     * True for the word after RENAME that makes RENAME a keyword: RENAME OBJECT,
775     * RENAME DATABASE, and ALTER TABLE ... RENAME COLUMN / CONSTRAINT.
776     */
777    private static boolean isRenameStatementWord(TSourceToken next) {
778        return wordIs(next, "object") || wordIs(next, "database")
779                || wordIs(next, "column") || wordIs(next, "constraint");
780    }
781
782    /**
783     * Get previous token in token list.
784     * <p>
785     * Matches TGSqlParser.getprevtoken() implementation exactly to ensure
786     * consistent tokenization behavior.
787     */
788    private TSourceToken getprevtoken(TSourceToken ptoken) {
789        TSourceTokenList lcstlist = ptoken.container;
790        if (TBaseType.assigned(lcstlist)) {
791            if ((ptoken.posinlist > 0) && (lcstlist.size() > ptoken.posinlist - 1)) {
792                if (!((lcstlist.get(ptoken.posinlist - 1).tokentype == ETokenType.ttwhitespace)
793                        || (lcstlist.get(ptoken.posinlist - 1).tokentype == ETokenType.ttreturn)
794                        || (lcstlist.get(ptoken.posinlist - 1).tokentype == ETokenType.ttsimplecomment)
795                        || (lcstlist.get(ptoken.posinlist - 1).tokentype == ETokenType.ttbracketedcomment)
796                )) {
797                    return lcstlist.get(ptoken.posinlist - 1);
798                } else {
799                    return lcstlist.nextsolidtoken(ptoken.posinlist - 1, -1, false);
800                }
801            }
802        }
803        return null;
804    }
805
806    /**
807     * Check and adjust constraint token codes.
808     * <p>
809     * When PRIMARY KEY, FOREIGN KEY, or UNIQUE is followed by '(', we need to
810     * look back to see if there's a CONSTRAINT keyword. If found, change its
811     * token code from rrw_constraint (515) to rw_constraint2 (298).
812     * <p>
813     * Extracted from: TGSqlParser.checkconstarinttoken() (lines 1989-2005)
814     *
815     * @param token The token after which to search (PRIMARY/FOREIGN/UNIQUE)
816     */
817    private void checkconstarinttoken(TSourceToken token) {
818        TSourceTokenList lcStList = token.container;
819        if (TBaseType.assigned(lcStList)) {
820            // Look back 2 solid tokens to find CONSTRAINT keyword
821            TSourceToken lcPPToken = lcStList.nextsolidtoken(token.posinlist, -2, false);
822            if (TBaseType.assigned(lcPPToken)) {
823                if (lcPPToken.tokencode == flexer.getkeywordvalue("constraint")) {
824                    // Change CONSTRAINT to special constraint2 token code
825                    lcPPToken.tokencode = TBaseType.rw_constraint2;
826                }
827            }
828        }
829    }
830
831    // ========== SQL Server-Specific Raw Statement Extraction ==========
832
833    /**
834     * SQL Server-specific raw statement extraction logic.
835     * <p>
836     * Extracted from: TGSqlParser.domssqlgetrawsqlstatements() (lines 13081-13904)
837     */
838    private void domssqlgetrawsqlstatements(SqlParseResult.Builder builder) {
839        int errorcount = 0;
840        int case_end_nest = 0;
841
842        if (TBaseType.assigned(sqlstatements)) sqlstatements.clear();
843        if (!TBaseType.assigned(sourcetokenlist)) {
844            builder.errorCode(-1);
845            builder.errorMessage("Source token list is null");
846            return;
847        }
848
849        gcurrentsqlstatement = null;
850        EFindSqlStateType gst = EFindSqlStateType.stnormal;
851        int lcblocklevel = 0;
852        int lctrycatchlevel = 0;
853        TSourceToken lcprevsolidtoken = null, lcnextsolidtoken, lcnnextsolidtoken;
854        TSourceToken ast = null;
855        int i, lcMergeInSelectNested = 0;
856        boolean lcisendconversation, lcstillinsql, lcMergeInSelect = false;
857
858        for (i = 0; i < sourcetokenlist.size(); i++) {
859
860            if ((ast != null) && (ast.issolidtoken()))
861                lcprevsolidtoken = ast;
862
863            ast = sourcetokenlist.get(i);
864            sourcetokenlist.curpos = i;
865
866            if (ast.tokencode == TBaseType.rrw_for) {
867                TSourceToken st1 = ast.nextSolidToken();
868                if ((st1 != null) && (st1.tokencode == TBaseType.rrw_system_time)) {
869                    ast.tokencode = TBaseType.rw_for_system_time;
870                } else if ((st1 != null) && TBaseType.mycomparetext(st1.getAstext(), "PATH") == 0) {
871                    ast.tokencode = TBaseType.rw_for_path;
872                }
873            }
874
875            if (lcMergeInSelect) {
876                if (ast.tokencode == '(') lcMergeInSelectNested++;
877                if (ast.tokencode == ')') {
878                    lcMergeInSelectNested--;
879                    if (lcMergeInSelectNested == 0) {
880                        lcMergeInSelect = false;
881                    }
882                }
883                gcurrentsqlstatement.sourcetokenlist.add(ast);
884                continue;
885            }
886
887            if (ast.tokenstatus == ETokenStatus.tsignoredbygetrawstatement) {
888                gcurrentsqlstatement.sourcetokenlist.add(ast);
889                continue;
890            }
891
892            if (ast.tokencode == TBaseType.rrw_minus) {
893                TSourceToken st1 = ast.searchToken('(', 1);
894                if (st1 == null) {
895                    st1 = ast.searchToken(TBaseType.rrw_select, 1);
896                    if (st1 == null) {
897                        ast.tokencode = TBaseType.ident;
898                    }
899                }
900            } else if (ast.tokencode == TBaseType.rrw_merge) {
901                TSourceToken st1 = ast.nextSolidToken();
902                if (st1.tokencode == TBaseType.rrw_join) {
903                    ast.tokencode = TBaseType.rrw_merge2_sqlserver;
904                }
905                if ((lcprevsolidtoken != null) && (lcprevsolidtoken.tokencode == '(')) {
906                    lcMergeInSelect = true;
907                    lcMergeInSelectNested++;
908                    gcurrentsqlstatement.sourcetokenlist.add(ast);
909                    continue;
910                }
911            } else if (ast.tokencode == TBaseType.rrw_sqlserver_value) {
912                if ((lcprevsolidtoken != null) && (lcprevsolidtoken.tokencode == '.')) {
913                    TSourceToken st1 = ast.searchToken('(', 1);
914                    if (st1 != null) {
915                        ast.tokencode = TBaseType.rrw_xml_value;
916                    }
917                }
918            } else if (ast.tokencode == TBaseType.rrw_sqlserver_modify) {
919                if ((lcprevsolidtoken != null) && (lcprevsolidtoken.tokencode == '.')) {
920                    TSourceToken st1 = ast.searchToken('(', 1);
921                    if (st1 != null) {
922                        ast.tokencode = TBaseType.rrw_xml_modify;
923                    }
924                }
925            } else if (ast.tokencode == TBaseType.rrw_sqlserver_query) {
926                if ((lcprevsolidtoken != null) && (lcprevsolidtoken.tokencode == '.')) {
927                    TSourceToken st1 = ast.searchToken('(', 1);
928                    if (st1 != null) {
929                        ast.tokencode = TBaseType.rrw_xml_query;
930                    }
931                }
932            } else if (ast.tokencode == TBaseType.rrw_sqlserver_exist) {
933                if ((lcprevsolidtoken != null) && (lcprevsolidtoken.tokencode == '.')) {
934                    TSourceToken st1 = ast.searchToken('(', 1);
935                    if (st1 != null) {
936                        ast.tokencode = TBaseType.rrw_xml_exist;
937                    }
938                }
939            } else if (ast.tokencode == TBaseType.rrw_sqlserver_nodes) {
940                if ((lcprevsolidtoken != null) && (lcprevsolidtoken.tokencode == '.')) {
941                    TSourceToken st1 = ast.searchToken('(', 1);
942                    if (st1 != null) {
943                        ast.tokencode = TBaseType.rrw_xml_nodes;
944                    }
945                }
946            } else if (ast.tokencode == TBaseType.ident && ast.tokentype == ETokenType.ttdbstring) {
947                // Handle bracket-quoted XML method names: .[value](), .[nodes](), etc.
948                if ((lcprevsolidtoken != null) && (lcprevsolidtoken.tokencode == '.')) {
949                    TSourceToken st1 = ast.searchToken('(', 1);
950                    if (st1 != null) {
951                        String text = ast.toString();
952                        if (text.length() > 2) {
953                            String inner = text.substring(1, text.length() - 1).toLowerCase(java.util.Locale.ROOT);
954                            if (inner.equals("value")) {
955                                ast.tokencode = TBaseType.rrw_xml_value;
956                            } else if (inner.equals("modify")) {
957                                ast.tokencode = TBaseType.rrw_xml_modify;
958                            } else if (inner.equals("query")) {
959                                ast.tokencode = TBaseType.rrw_xml_query;
960                            } else if (inner.equals("exist")) {
961                                ast.tokencode = TBaseType.rrw_xml_exist;
962                            } else if (inner.equals("nodes")) {
963                                ast.tokencode = TBaseType.rrw_xml_nodes;
964                            }
965                        }
966                    }
967                }
968            } else if (ast.tokencode == TBaseType.rrw_check) {
969                if ((lcprevsolidtoken != null) && (lcprevsolidtoken.tokencode == TBaseType.rrw_with)) {
970                    lcprevsolidtoken.tokencode = TBaseType.rrw_sqlserver_check_with;
971                }
972            } else if (ast.tokencode == TBaseType.rrw_sqlserver_next) {
973                TSourceToken st1 = ast.nextSolidToken();
974                if ((st1 != null) && (st1.tokencode == '.')) {
975                    ast.tokencode = TBaseType.ident;
976                }
977            } else if (ast.tokencode == TBaseType.rrw_fetch) {
978                if ((lcprevsolidtoken != null) && ((lcprevsolidtoken.tokencode == TBaseType.rrw_sqlserver_row) || (lcprevsolidtoken.tokencode == TBaseType.rrw_sqlserver_rows))) {
979                    TSourceToken prev2 = lcprevsolidtoken.searchToken(TBaseType.rrw_open, -1);
980                    if (prev2 == null) {
981                        ast.tokencode = TBaseType.rrw_sqlserver_offset_fetch;
982                    }
983                }
984            } else if ((ast.tokencode == TBaseType.rrw_exec) || (ast.tokencode == TBaseType.rrw_execute)) {
985                // search ;2 after execute
986                // EXECUTE ApxSQL_Test_Triggers_Add;2  @TableName, @AddFlag
987                int searchRange = 4;
988                TSourceToken endTokenInSameLine = ast.searchTokenAtTheEndOfSameLine();
989                if (endTokenInSameLine != null) {
990                    searchRange = endTokenInSameLine.posinlist - ast.posinlist;
991                }
992                TSourceToken st1 = ast.searchToken(';', searchRange);
993                if (st1 != null) {
994                    TSourceToken numSt = st1.nextSolidToken();
995                    if ((numSt != null) && (numSt.tokentype == ETokenType.ttnumber)) {
996                        st1.tokencode = TBaseType.rrw_sqlserver_semicolon_module_number;
997                        st1.tokentype = ETokenType.ttidentifier;
998                    }
999                }
1000            } else if (ast.tokencode == TBaseType.rrw_sqlserver_trim) {
1001                TSourceToken st1 = ast.nextSolidToken();
1002                if (st1 != null) {
1003                    if (st1.tokencode == '(') {
1004                        // keep as trim keyword
1005                    } else {
1006                        ast.tokencode = TBaseType.ident;
1007                    }
1008                }
1009            }
1010
1011            if (vendor == EDbVendor.dbvopenedge){
1012                if (ast.tokencode == TBaseType.rrw_order){
1013                        TSourceToken st1 = ast.searchToken(TBaseType.rrw_by,1);
1014                        if (st1 == null) {
1015                            ast.tokencode = TBaseType.ident;
1016                        }
1017                }else if (ast.tokencode == TBaseType.rrw_with){
1018                        TSourceToken st1 = ast.searchToken(TBaseType.rrw_check,1);
1019                        if (st1 != null) {
1020                            ast.tokencode = TBaseType.rrw_openedge_with_check;
1021                        }
1022                }
1023            }
1024
1025            if (gst == EFindSqlStateType.ststoredprocedurebody) {
1026                if (!((ast.tokencode == TBaseType.rrw_go)
1027                        || (ast.tokencode == TBaseType.rrw_create)
1028                        || (ast.tokencode == TBaseType.rrw_alter))) {
1029                    gcurrentsqlstatement.sourcetokenlist.add(ast);
1030                    continue;
1031                }
1032            }
1033
1034            TCustomSqlStatement lcnextsqlstatement = sqlcmds.issql(ast, gst, gcurrentsqlstatement);
1035
1036            switch (gst) {
1037                case sterror: {
1038                    if (TBaseType.assigned(lcnextsqlstatement)) {
1039                        onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
1040                        gcurrentsqlstatement = lcnextsqlstatement;
1041                        gcurrentsqlstatement.sourcetokenlist.add(ast);
1042                        gst = EFindSqlStateType.stsql;
1043                    } else if ((ast.tokentype == ETokenType.ttsemicolon)) {
1044                        gcurrentsqlstatement.sourcetokenlist.add(ast);
1045                        onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
1046                        gst = EFindSqlStateType.stnormal;
1047                    } else {
1048                        gcurrentsqlstatement.sourcetokenlist.add(ast);
1049                    }
1050                    break;
1051                }
1052                case stnormal: {
1053                    if ((ast.tokencode == TBaseType.cmtdoublehyphen)
1054                            || (ast.tokencode == TBaseType.cmtslashstar)
1055                            || (ast.tokencode == TBaseType.lexspace)
1056                            || (ast.tokencode == TBaseType.lexnewline)
1057                            || (ast.tokentype == ETokenType.ttsemicolon)) {
1058                        if (TBaseType.assigned(gcurrentsqlstatement)) {
1059                            gcurrentsqlstatement.sourcetokenlist.add(ast);
1060                        }
1061
1062                        if (TBaseType.assigned(lcprevsolidtoken) && (ast.tokentype == ETokenType.ttsemicolon)) {
1063                            if (lcprevsolidtoken.tokentype == ETokenType.ttsemicolon) {
1064                                // ;;;; continuous semicolon,treat it as comment
1065                                ast.tokentype = ETokenType.ttsimplecomment;
1066                                ast.tokencode = TBaseType.cmtdoublehyphen;
1067                            }
1068                        }
1069
1070                        continue;
1071                    }
1072
1073                    gcurrentsqlstatement = lcnextsqlstatement;
1074
1075                    if (TBaseType.assigned(gcurrentsqlstatement)) {
1076                        switch (gcurrentsqlstatement.sqlstatementtype) {
1077                            case sstmssqlcreateprocedure:
1078                            case sstmssqlcreatefunction:
1079                            case sstcreatetrigger:
1080                            case sstmssqlalterprocedure:
1081                            case sstmssqlalterfunction:
1082                            case sstmssqlaltertrigger: {
1083                                gcurrentsqlstatement.sourcetokenlist.add(ast);
1084                                gst = EFindSqlStateType.ststoredprocedure;
1085                                break;
1086                            }
1087                            case sstmssqlbegintry:
1088                            case sstmssqlbegincatch: {
1089                                gcurrentsqlstatement.sourcetokenlist.add(ast);
1090                                gst = EFindSqlStateType.sttrycatch;
1091                                lctrycatchlevel = 0;
1092                                break;
1093                            }
1094                            case sstmssqlgo: {
1095                                gcurrentsqlstatement.sourcetokenlist.add(ast);
1096                                onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
1097                                gst = EFindSqlStateType.stnormal;
1098                                break;
1099                            }
1100                            default: {
1101                                gcurrentsqlstatement.sourcetokenlist.add(ast);
1102                                gst = EFindSqlStateType.stsql;
1103                                break;
1104                            }
1105                        }
1106                    } else {
1107                        if (ast.tokencode == TBaseType.rrw_begin) {
1108                            gcurrentsqlstatement = new TMssqlBlock(vendor);
1109                            gcurrentsqlstatement.sqlstatementtype = ESqlStatementType.sstmssqlblock;
1110                            gcurrentsqlstatement.sourcetokenlist.add(ast);
1111                            gst = EFindSqlStateType.stblock;
1112                        } else {
1113                            if (sqlstatements.size() == 0) {
1114                                //first statement of mssql batch, treat it as exec sp
1115                                gst = EFindSqlStateType.stsql;
1116                                gcurrentsqlstatement = new TMssqlExecute(vendor);
1117                                gcurrentsqlstatement.sourcetokenlist.add(ast);
1118                            } else if (sqlstatements.get(sqlstatements.size() - 1).sqlstatementtype == ESqlStatementType.sstmssqlgo) {
1119                                // prev sql is go, treat it as exec sp
1120                                gst = EFindSqlStateType.stsql;
1121                                gcurrentsqlstatement = new TMssqlExecute(vendor);
1122                                gcurrentsqlstatement.sourcetokenlist.add(ast);
1123                            }
1124                        }
1125                    }
1126
1127                    if (!TBaseType.assigned(gcurrentsqlstatement)) //error tokentext found
1128                    {
1129                        this.syntaxErrors.add(new TSyntaxError(ast.getAstext(), ast.lineNo, (ast.columnNo < 0 ? 0 : ast.columnNo)
1130                                , "Error when tokenlize", EErrorType.spwarning, TBaseType.MSG_WARNING_ERROR_WHEN_TOKENIZE, null, ast.posinlist));
1131
1132                        ast.tokentype = ETokenType.tttokenlizererrortoken;
1133                        gst = EFindSqlStateType.sterror;
1134
1135                        gcurrentsqlstatement = new TUnknownSqlStatement(vendor);
1136                        gcurrentsqlstatement.sqlstatementtype = ESqlStatementType.sstinvalid;
1137                        gcurrentsqlstatement.sourcetokenlist.add(ast);
1138                    }
1139                    break;
1140                }
1141                case stblock: {
1142                    if (TBaseType.assigned(lcnextsqlstatement)) {
1143                        if (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlgo) {
1144                            onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
1145                            gcurrentsqlstatement = lcnextsqlstatement;
1146                            gcurrentsqlstatement.sourcetokenlist.add(ast);
1147                            onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
1148                            gst = EFindSqlStateType.stnormal;
1149                        } else {
1150                            lcnextsqlstatement = null;
1151                        }
1152                    }
1153
1154                    if (gst == EFindSqlStateType.stblock) {
1155                        gcurrentsqlstatement.sourcetokenlist.add(ast);
1156                        if (ast.tokencode == TBaseType.rrw_begin) {
1157                            lcnextsolidtoken = sourcetokenlist.nextsolidtoken(i, 1, false);
1158                            if (TBaseType.assigned(lcnextsolidtoken)) {
1159                                if (!((lcnextsolidtoken.tokencode == TBaseType.rrw_sqlserver_tran)
1160                                        || (lcnextsolidtoken.tokencode == TBaseType.rrw_sqlserver_transaction)
1161                                        || (lcnextsolidtoken.tokencode == TBaseType.rrw_sqlserver_distributed)
1162                                        || (lcnextsolidtoken.tokencode == TBaseType.rrw_sqlserver_dialog)
1163                                        || (lcnextsolidtoken.tokencode == TBaseType.rrw_sqlserver_conversation)
1164                                ))
1165                                    lcblocklevel++;
1166                            } else
1167                                lcblocklevel++;
1168
1169                        } else if (ast.tokencode == TBaseType.rrw_case)
1170                            lcblocklevel++;
1171                        else if (ast.tokencode == TBaseType.rrw_end) {
1172
1173                            lcisendconversation = false;
1174
1175                            lcnextsolidtoken = sourcetokenlist.nextsolidtoken(i, 1, false);
1176                            if (TBaseType.assigned(lcnextsolidtoken)) {
1177                                if (lcnextsolidtoken.tokencode == TBaseType.rrw_sqlserver_conversation)
1178                                    lcisendconversation = true;
1179                            }
1180
1181                            if (!lcisendconversation) {
1182
1183                                if (lcblocklevel == 0) {
1184                                    if (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlif) {
1185                                        if (TBaseType.assigned(lcnextsolidtoken)) {
1186                                            if (lcnextsolidtoken.tokencode == TBaseType.rrw_else) {
1187                                                gst = EFindSqlStateType.stsql;
1188                                            } else if (lcnextsolidtoken.tokentype == ETokenType.ttsemicolon) {
1189                                                lcnnextsolidtoken = sourcetokenlist.nextsolidtoken(lcnextsolidtoken.posinlist, 1, false);
1190                                                if (TBaseType.assigned(lcnnextsolidtoken)) {
1191                                                    if (lcnnextsolidtoken.tokencode == TBaseType.rrw_else) {
1192                                                        gst = EFindSqlStateType.stsql;
1193                                                    }
1194                                                }
1195                                            }
1196                                        }
1197                                    }
1198
1199                                    if (gst != EFindSqlStateType.stsql) {
1200                                        onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
1201                                        gst = EFindSqlStateType.stnormal;
1202                                    }
1203
1204                                } else {
1205                                    lcblocklevel--;
1206                                }
1207
1208                            }
1209                        }
1210                    }
1211                    break;
1212                }
1213                case sttrycatch: {
1214
1215                    if (TBaseType.assigned(lcnextsqlstatement)) {
1216                        if (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlgo) {
1217                            onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
1218                            gcurrentsqlstatement = lcnextsqlstatement;
1219                            gcurrentsqlstatement.sourcetokenlist.add(ast);
1220                            onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
1221                            gst = EFindSqlStateType.stnormal;
1222                        } else {
1223                            if (
1224                                    (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlbegintry)
1225                                            || (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlbegincatch)
1226                            )
1227                                lctrycatchlevel++;
1228                            lcnextsqlstatement = null;
1229                        }
1230                    }
1231
1232                    if (gst == EFindSqlStateType.sttrycatch) {
1233                        gcurrentsqlstatement.sourcetokenlist.add(ast);
1234                        if ((ast.tokencode == TBaseType.rrw_try) ||
1235                                (ast.tokencode == TBaseType.rrw_catch)) {
1236                            if (TBaseType.assigned(lcprevsolidtoken)) {
1237                                if (lcprevsolidtoken.tokencode == TBaseType.rrw_end) {
1238                                    if (lctrycatchlevel == 0) {
1239                                        onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
1240                                        gst = EFindSqlStateType.stnormal;
1241                                    } else
1242                                        lctrycatchlevel--;
1243                                }
1244                            }
1245                        }
1246                    }
1247                    break;
1248                }
1249                case stprocedureWithReturn: {
1250                    // found return statement in create procedure/function
1251
1252                    if (TBaseType.assigned(lcnextsqlstatement)) {
1253                        if (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlgo) {
1254                            onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
1255                            gcurrentsqlstatement = lcnextsqlstatement;
1256                            gcurrentsqlstatement.sourcetokenlist.add(ast);
1257                            onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
1258                            gst = EFindSqlStateType.stnormal;
1259                            break;
1260                        }
1261                    }
1262
1263                    gcurrentsqlstatement.sourcetokenlist.add(ast);
1264                    if ((gst == EFindSqlStateType.stprocedureWithReturn) && (ast.tokentype == ETokenType.ttsemicolon)) {
1265                        onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
1266                        gst = EFindSqlStateType.stnormal;
1267                    }
1268                    break;
1269                }
1270                case stsql: {
1271                    if ((ast.tokentype == ETokenType.ttsemicolon)) {
1272                        lcstillinsql = false;
1273                        if (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlif) {
1274                            lcnextsolidtoken = sourcetokenlist.nextsolidtoken(i, 1, false);
1275                            if (TBaseType.assigned(lcnextsolidtoken)) {
1276                                if (lcnextsolidtoken.tokencode == TBaseType.rrw_else) {
1277                                    // if ( expr stmt; else
1278                                    gcurrentsqlstatement.sourcetokenlist.add(ast);
1279                                    lcstillinsql = true;
1280                                }
1281
1282                            }
1283                        }
1284
1285                        if (!lcstillinsql) {
1286                            gst = EFindSqlStateType.stnormal;
1287                            gcurrentsqlstatement.sourcetokenlist.add(ast);
1288                            gcurrentsqlstatement.semicolonended = ast;
1289                            onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
1290                        }
1291
1292                    } else if (TBaseType.assigned(lcnextsqlstatement)) {
1293
1294                        if (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlgo) {
1295                            onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
1296                            gcurrentsqlstatement = lcnextsqlstatement;
1297                            gcurrentsqlstatement.sourcetokenlist.add(ast);
1298                            onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
1299                            gst = EFindSqlStateType.stnormal;
1300                            continue;
1301                        }
1302
1303                        // A typed TUnknownSqlStatement (admin DDL recognized by
1304                        // the splitter, no deep AST) has no internal statement
1305                        // structure: it runs to its semicolon. Without this,
1306                        // ALTER EXTERNAL LANGUAGE ... SET (...) split at SET
1307                        // because SET is a registered statement head.
1308                        if ((gcurrentsqlstatement instanceof gudusoft.gsqlparser.stmt.TUnknownSqlStatement)
1309                                && (gcurrentsqlstatement.sqlstatementtype != ESqlStatementType.sstinvalid)
1310                                && (gcurrentsqlstatement.sqlstatementtype != ESqlStatementType.sstunknown)) {
1311                            gcurrentsqlstatement.sourcetokenlist.add(ast);
1312                            continue;
1313                        }
1314
1315                        switch (gcurrentsqlstatement.sqlstatementtype) {
1316                            case sstmssqlif:
1317                            case sstmssqlwhile: {
1318                                if ((lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlbegincatch)
1319                                        || (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlbegintry)) {
1320                                    gcurrentsqlstatement.sourcetokenlist.add(ast);
1321                                    gst = EFindSqlStateType.stblock;
1322                                    lcblocklevel = 1;
1323                                    lcnextsqlstatement = null;
1324                                    continue;
1325
1326                                } else if (gcurrentsqlstatement.dummytag == 1) {
1327                                    // if ( cond ^stmt nextstmt (^ stands for current pos)
1328                                    gcurrentsqlstatement.sourcetokenlist.add(ast);
1329
1330                                    if ((lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlif)
1331                                            || (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlwhile))
1332                                        gcurrentsqlstatement.dummytag = 1;
1333                                    else
1334                                        gcurrentsqlstatement.dummytag = 0;
1335
1336                                    lcnextsqlstatement = null;
1337                                    continue;
1338                                } else {
1339                                    if (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlif) {
1340                                        if ((ast.nextSolidToken() != null) && (ast.nextSolidToken().tokencode == TBaseType.rrw_sqlserver_exists)) {
1341                                            gcurrentsqlstatement.sourcetokenlist.add(ast);
1342                                            lcnextsqlstatement = null;
1343                                            continue;
1344                                        }
1345                                    }
1346                                }
1347                                break;
1348                            }
1349                            case sstmssqlalterqueue: {
1350                                if (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlexec) {
1351                                    // execute can't be used to delimite alter queue
1352                                    gcurrentsqlstatement.sourcetokenlist.add(ast);
1353                                    lcnextsqlstatement = null;
1354                                    continue;
1355
1356                                }
1357                                break;
1358                            }
1359                            case sstmssqlcreateschema: {
1360                                gcurrentsqlstatement.sourcetokenlist.add(ast);
1361                                lcnextsqlstatement = null;
1362                                continue;
1363                            }
1364                        }
1365
1366                        // "IF EXISTS <name>" belongs to the statement it
1367                        // follows (DROP DEFAULT d IF EXISTS ...). But
1368                        // "IF EXISTS (SELECT ...)" is control flow starting a
1369                        // new statement - real scripts chain unterminated
1370                        // statements before it - so the '(' after EXISTS
1371                        // decides.
1372                        if (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlif) {
1373                            TSourceToken existsTok = ast.nextSolidToken();
1374                            if ((existsTok != null)
1375                                    && (existsTok.tokencode == TBaseType.rrw_sqlserver_exists)) {
1376                                TSourceToken afterExists = existsTok.nextSolidToken();
1377                                if ((afterExists == null)
1378                                        || (afterExists.tokentype != ETokenType.ttleftparenthesis)) {
1379                                    gcurrentsqlstatement.sourcetokenlist.add(ast);
1380                                    continue;
1381                                }
1382                            }
1383                        }
1384
1385                        // ALTER PARTITION FUNCTION f() MERGE|SPLIT RANGE (...):
1386                        // the MERGE keyword is the function operation, not a
1387                        // MERGE statement.
1388                        if ((gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlAlterPartitionFunction)
1389                                && (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmerge)) {
1390                            gcurrentsqlstatement.sourcetokenlist.add(ast);
1391                            continue;
1392                        }
1393
1394                        onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
1395                        gcurrentsqlstatement = lcnextsqlstatement;
1396                        gcurrentsqlstatement.sourcetokenlist.add(ast);
1397
1398                        switch (gcurrentsqlstatement.sqlstatementtype) {
1399                            case sstmssqlcreateprocedure:
1400                            case sstmssqlcreatefunction:
1401                            case sstcreatetrigger:
1402                            case sstmssqlalterprocedure:
1403                            case sstmssqlalterfunction:
1404                            case sstmssqlaltertrigger: {
1405                                gst = EFindSqlStateType.ststoredprocedure;
1406                                break;
1407                            }
1408                            case sstmssqlbegintry:
1409                            case sstmssqlbegincatch: {
1410                                gst = EFindSqlStateType.sttrycatch;
1411                                lctrycatchlevel = 0;
1412                                break;
1413                            }
1414                            case sstmssqlgo: {
1415                                gst = EFindSqlStateType.stnormal;
1416                                break;
1417                            }
1418                            default: {
1419                                gst = EFindSqlStateType.stsql;
1420                                break;
1421                            }
1422                        }
1423
1424                    } else if ((ast.tokencode == TBaseType.rrw_begin)) {
1425                        if ((gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlif)
1426                                || (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlwhile)) {
1427                            gst = EFindSqlStateType.stblock;
1428                            lcblocklevel = 0;
1429                            gcurrentsqlstatement.sourcetokenlist.add(ast);
1430                        } else if (case_end_nest > 0) {
1431                            // Inside a CASE expression — BEGIN cannot be a top-level block boundary
1432                            gcurrentsqlstatement.sourcetokenlist.add(ast);
1433                        } else {
1434                            // mantisbt 4411: a plain BEGIN ... END after a complete statement
1435                            // (EXEC, SELECT, UPDATE, SET, DECLARE, PRINT, RAISERROR, label, ...)
1436                            // starts a new top-level TMssqlBlock. Previously the else branch
1437                            // silently swallowed BEGIN into the preceding statement, breaking
1438                            // the parse for batches like:
1439                            //   EXEC dbo.p1
1440                            //   BEGIN UPDATE x SET y = 1 END
1441                            onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
1442                            gcurrentsqlstatement = new TMssqlBlock(vendor);
1443                            gcurrentsqlstatement.sqlstatementtype = ESqlStatementType.sstmssqlblock;
1444                            gcurrentsqlstatement.sourcetokenlist.add(ast);
1445                            gst = EFindSqlStateType.stblock;
1446                        }
1447                    } else if ((ast.tokencode == TBaseType.rrw_case)) {
1448                        case_end_nest++;
1449                        gcurrentsqlstatement.sourcetokenlist.add(ast);
1450                    } else if ((ast.tokencode == TBaseType.rrw_end)) {
1451                        if (case_end_nest > 0) {
1452                            case_end_nest--;
1453                        }
1454                        gcurrentsqlstatement.sourcetokenlist.add(ast);
1455                    } else if ((ast.tokencode == TBaseType.rrw_else)) {
1456                        gcurrentsqlstatement.sourcetokenlist.add(ast);
1457                        if (((gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlif)
1458                                || (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlwhile)
1459                        ) && (case_end_nest == 0))
1460                            gcurrentsqlstatement.dummytag = 1;
1461                    } else {
1462                        gcurrentsqlstatement.sourcetokenlist.add(ast);
1463                    }
1464                    break;
1465                }
1466                case ststoredprocedure: {
1467                    if (TBaseType.assigned(lcnextsqlstatement)) {
1468                        if (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlgo) {
1469                            onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
1470                            gcurrentsqlstatement = lcnextsqlstatement;
1471                            gcurrentsqlstatement.sourcetokenlist.add(ast);
1472                            onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
1473                            gst = EFindSqlStateType.stnormal;
1474                        } else if (lcnextsqlstatement.sqlstatementtype == sstmssqlreturn) {
1475                            gst = EFindSqlStateType.stprocedureWithReturn;
1476                            gcurrentsqlstatement.sourcetokenlist.add(ast);
1477                            lcnextsqlstatement = null;
1478                        } else {
1479                            gst = EFindSqlStateType.ststoredprocedurebody;
1480                            gcurrentsqlstatement.sourcetokenlist.add(ast);
1481
1482                            lcnextsqlstatement = null;
1483                        }
1484                    }
1485
1486                    if (gst == EFindSqlStateType.ststoredprocedure) {
1487                        gcurrentsqlstatement.sourcetokenlist.add(ast);
1488                        if (ast.tokencode == TBaseType.rrw_begin) {
1489                            gst = EFindSqlStateType.stblock;
1490                        } else if ((ast.tokentype == ETokenType.ttsemicolon)
1491                                && hasExternalNameBody(gcurrentsqlstatement)) {
1492                            // CLR routine: AS EXTERNAL NAME assembly.class.method has no
1493                            // BEGIN...END body, so the semicolon ends the statement. Without
1494                            // this the splitter keeps waiting for a body and swallows the
1495                            // next statement of the script.
1496                            onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
1497                            gst = EFindSqlStateType.stnormal;
1498                        }
1499                    }
1500                    break;
1501                }
1502                case ststoredprocedurebody: {
1503                    if (TBaseType.assigned(lcnextsqlstatement)) {
1504                        switch (lcnextsqlstatement.sqlstatementtype) {
1505                            case sstmssqlgo: {
1506                                onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
1507                                gcurrentsqlstatement = lcnextsqlstatement;
1508                                gcurrentsqlstatement.sourcetokenlist.add(ast);
1509                                onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
1510                                gst = EFindSqlStateType.stnormal;
1511                                break;
1512                            }
1513                            case sstmssqlcreateprocedure:
1514                            case sstmssqlcreatefunction:
1515                            case sstcreatetrigger:
1516                            case sstmssqlalterprocedure:
1517                            case sstmssqlalterfunction:
1518                            case sstmssqlaltertrigger: {
1519                                onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
1520                                gcurrentsqlstatement = lcnextsqlstatement;
1521                                gcurrentsqlstatement.sourcetokenlist.add(ast);
1522                                gst = EFindSqlStateType.ststoredprocedure;
1523                                break;
1524                            }
1525                            case sstcreateview:
1526                            case sstcreatetable: {
1527
1528                                boolean readForNewStmt = false;
1529                                TSourceToken st1 = ast.searchToken(';', -1);
1530                                if (st1 != null) {
1531                                    TSourceToken st2 = ast.searchToken(TBaseType.rrw_end, -2);
1532                                    if (st2 != null) {
1533                                        // Verify this END is at block level 0 (not inside a nested
1534                                        // BEGIN...END or BEGIN TRY...END TRY block) by counting
1535                                        // BEGINs and ENDs in the current proc's token list.
1536                                        int blockDepth = 0;
1537                                        for (int k = 0; k < gcurrentsqlstatement.sourcetokenlist.size(); k++) {
1538                                            TSourceToken tk = gcurrentsqlstatement.sourcetokenlist.get(k);
1539                                            if (tk.tokencode == TBaseType.rrw_begin) {
1540                                                TSourceToken tkNext = tk.nextSolidToken();
1541                                                if (tkNext == null || (tkNext.tokencode != TBaseType.rrw_sqlserver_tran
1542                                                        && tkNext.tokencode != TBaseType.rrw_sqlserver_transaction
1543                                                        && tkNext.tokencode != TBaseType.rrw_sqlserver_distributed
1544                                                        && tkNext.tokencode != TBaseType.rrw_sqlserver_dialog
1545                                                        && tkNext.tokencode != TBaseType.rrw_sqlserver_conversation)) {
1546                                                    blockDepth++;
1547                                                }
1548                                            } else if (tk.tokencode == TBaseType.rrw_end) {
1549                                                TSourceToken tkNext = tk.nextSolidToken();
1550                                                if (tkNext == null || tkNext.tokencode != TBaseType.rrw_sqlserver_conversation) {
1551                                                    blockDepth--;
1552                                                }
1553                                            }
1554                                        }
1555                                        // blockDepth == 0 means all BEGIN/END pairs are balanced
1556                                        // so the END; is at the top level (proc boundary)
1557                                        if (blockDepth == 0) {
1558                                            readForNewStmt = true;
1559                                        }
1560                                    }
1561                                }
1562
1563                                if (readForNewStmt) {
1564                                    onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
1565                                    gcurrentsqlstatement = lcnextsqlstatement;
1566                                    gcurrentsqlstatement.sourcetokenlist.add(ast);
1567                                    gst = EFindSqlStateType.stsql;
1568                                } else {
1569                                    lcnextsqlstatement = null;
1570                                }
1571                                break;
1572                            }
1573                            case sstmssqlDropSecurityPolicy:
1574                            case sstmssqlAlterSecurityPolicy:
1575                            case sstmssqlCreateSecurityPolicy:
1576                                onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
1577                                gcurrentsqlstatement = lcnextsqlstatement;
1578                                gcurrentsqlstatement.sourcetokenlist.add(ast);
1579                                gst = EFindSqlStateType.stsql;
1580
1581                                break;
1582                            default: {
1583                                lcnextsqlstatement = null;
1584                                break;
1585                            }
1586                        }
1587                    }
1588
1589                    if (gst == EFindSqlStateType.ststoredprocedurebody)
1590                        gcurrentsqlstatement.sourcetokenlist.add(ast);
1591                }
1592                break;
1593            }
1594        }
1595
1596        //last statement
1597        if (TBaseType.assigned(gcurrentsqlstatement) && (gst != EFindSqlStateType.stnormal)) {
1598            onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, true, builder);
1599        }
1600
1601        // Set results in builder
1602        builder.sqlStatements(this.sqlstatements);
1603        builder.errorCode(errorcount);
1604        builder.errorMessage(errorcount == 0 ? "" : String.format("Extraction completed with %d error(s)", errorcount));
1605    }
1606}