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        while (yychar > 0) {
604
605            if (asourcetoken.tokencode == TBaseType.rrw_openrowset) {
606                // openrowset(....)
607                lcinopenrowset = true;
608                lcnested = 0;
609            } else if (asourcetoken.tokentype == ETokenType.ttleftparenthesis) {
610                if ((lcsteps > 0) && TBaseType.assigned(lcprevtoken)) {
611                    if (lcprevtoken.tokencode == TBaseType.rrw_primary) {
612                        lcprevtoken.tokencode = TBaseType.rrw_select - 2;  //rw_primary2
613                        checkconstarinttoken(lcprevtoken);
614                    } else if (lcprevtoken.tokencode == TBaseType.rrw_foreign) {
615                        lcprevtoken.tokencode = TBaseType.rrw_select - 4;  //rw_foreign2
616                        checkconstarinttoken(lcprevtoken);
617                    } else if (lcprevtoken.tokencode == TBaseType.rrw_unique) {
618                        lcprevtoken.tokencode = TBaseType.rrw_select - 1;  //rw_unique2
619                        checkconstarinttoken(lcprevtoken);
620                    }
621                    lcprevtoken = null;
622                    lcsteps = 0;
623                }
624
625                // openrowset(....)
626                if (lcinopenrowset)
627                    lcnested++;
628            } else if (asourcetoken.tokentype == ETokenType.ttrightparenthesis) {
629                // openrowset(....)
630                if (lcinopenrowset) {
631                    if ((lcnested > 0))
632                        lcnested--;
633                    if (lcnested == 0)
634                        lcinopenrowset = false;
635                }
636            } else if (asourcetoken.tokentype == ETokenType.ttsemicolon) {
637                if (lcinopenrowset) {
638                    asourcetoken.tokentype = ETokenType.ttsemicolon2;
639                } else {
640                    lctoken2 = asourcetoken.searchToken(TBaseType.rrw_begin, -1);
641                    if (lctoken2 != null) {
642                        asourcetoken.tokencode = TBaseType.SEMI_COLON_AFTER_BEGIN;
643                        asourcetoken.tokentype = ETokenType.ttsemicolon3;
644                    } else {
645                        lctoken2 = asourcetoken.searchToken(TBaseType.rrw_try, -1);
646                        if (lctoken2 == null) {
647                            lctoken2 = asourcetoken.searchToken(TBaseType.rrw_catch, -1);
648                        }
649                        if (lctoken2 != null) {
650                            lctoken3 = asourcetoken.searchToken(TBaseType.rrw_begin, -2);
651                            if (lctoken3 != null) {
652                                asourcetoken.tokencode = TBaseType.SEMI_COLON_AFTER_BEGIN;
653                                asourcetoken.tokentype = ETokenType.ttsemicolon3;
654                            }
655                        }
656                    }
657                }
658
659                lctoken = getprevtoken(asourcetoken);
660                if ((lctoken != null)) {
661                    if (lctoken.tokentype == ETokenType.ttsemicolon) {
662                        // treat this semicolon as a whitespace
663                        asourcetoken.tokencode = TBaseType.lexspace;
664                    }
665                }
666            } else if (asourcetoken.tokentype == ETokenType.ttperiod) {
667                lctoken = getprevtoken(asourcetoken);
668                if (TBaseType.assigned(lctoken)) {
669                    // go.fieldname, go is a table alias, not a go statement
670                    if (lctoken.tokencode == TBaseType.rrw_go) {
671                        lctoken.tokencode = TBaseType.ident;
672                        lctoken.tokentype = ETokenType.ttidentifier;
673                    }
674                }
675            } else if (asourcetoken.tokencode == TBaseType.rrw_table) {
676                lctoken = getprevtoken(asourcetoken);
677                if (TBaseType.assigned(lctoken)) {
678                    if (lctoken.tokencode == TBaseType.rrw_lock) {
679                        lctoken.tokencode = TBaseType.rw_locktable;
680                    }
681                }
682            } else if (asourcetoken.tokencode == TBaseType.rrw_into) {
683                lctoken = getprevtoken(asourcetoken);
684                if (TBaseType.assigned(lctoken)) {
685                    if (lctoken.tokencode == TBaseType.rrw_sqlserver_copy) {
686                        lctoken.tokencode = TBaseType.rrw_sqlserver_copyinto;
687                    }
688                }
689            } else if (asourcetoken.tokencode == TBaseType.rrw_sqlserver_column) {
690                lctoken = getprevtoken(asourcetoken);
691                if (TBaseType.assigned(lctoken)) {
692                    if (lctoken.tokencode == TBaseType.rrw_drop) {
693                        asourcetoken.tokencode = TBaseType.rrw_sqlserver_drop_column;
694                    }
695                }
696            } else if (asourcetoken.tokencode == TBaseType.rrw_go) {
697                iskeywordgo = true;
698                lctoken = getprevtoken(asourcetoken);
699                if (TBaseType.assigned(lctoken)) {
700                    // go should not at same line as other sql statement
701                    if ((lctoken.lineNo == asourcetoken.lineNo) && (lctoken.tokencode != ';')) {
702                        iskeywordgo = false;
703                    }
704                }
705
706                if (iskeywordgo) {
707                    lcinopenrowset = false;
708                    lcnested = 0;
709                    lcprevtoken = asourcetoken;
710                } else {
711                    asourcetoken.tokencode = TBaseType.ident;
712                    asourcetoken.tokentype = ETokenType.ttidentifier;
713                }
714            } else if (asourcetoken.tokencode == TBaseType.rrw_primary) {
715                // primary key [clustered | nonclustered ] [hash] ( column list)
716                lcsteps = 2;
717                lcprevtoken = asourcetoken;
718            } else if (asourcetoken.tokencode == TBaseType.rrw_foreign) {
719                // foreign key [clustered | nonclustered ] ( column list)
720                lcsteps = 2;
721                lcprevtoken = asourcetoken;
722            } else if (asourcetoken.tokencode == TBaseType.rrw_unique) {
723                // unique [clustered | nonclustered ] [hash] ( column list)
724                lcsteps = 1;
725                lcprevtoken = asourcetoken;
726            } else if (asourcetoken.issolidtoken()) {
727                if (lcsteps > 0) {
728                    if (!(TBaseType.mysametext("clustered", asourcetoken.toString())
729                            || TBaseType.mysametext("nonclustered", asourcetoken.toString())
730                            || TBaseType.mysametext("hash", asourcetoken.toString())
731                    ))
732                        lcsteps--;
733                }
734            }
735
736            sourcetokenlist.add(asourcetoken);
737
738            asourcetoken = getanewsourcetoken();
739            if (asourcetoken == null) break;
740            yychar = asourcetoken.tokencode;
741        }
742    }
743
744    /**
745     * Get previous token in token list.
746     * <p>
747     * Matches TGSqlParser.getprevtoken() implementation exactly to ensure
748     * consistent tokenization behavior.
749     */
750    private TSourceToken getprevtoken(TSourceToken ptoken) {
751        TSourceTokenList lcstlist = ptoken.container;
752        if (TBaseType.assigned(lcstlist)) {
753            if ((ptoken.posinlist > 0) && (lcstlist.size() > ptoken.posinlist - 1)) {
754                if (!((lcstlist.get(ptoken.posinlist - 1).tokentype == ETokenType.ttwhitespace)
755                        || (lcstlist.get(ptoken.posinlist - 1).tokentype == ETokenType.ttreturn)
756                        || (lcstlist.get(ptoken.posinlist - 1).tokentype == ETokenType.ttsimplecomment)
757                        || (lcstlist.get(ptoken.posinlist - 1).tokentype == ETokenType.ttbracketedcomment)
758                )) {
759                    return lcstlist.get(ptoken.posinlist - 1);
760                } else {
761                    return lcstlist.nextsolidtoken(ptoken.posinlist - 1, -1, false);
762                }
763            }
764        }
765        return null;
766    }
767
768    /**
769     * Check and adjust constraint token codes.
770     * <p>
771     * When PRIMARY KEY, FOREIGN KEY, or UNIQUE is followed by '(', we need to
772     * look back to see if there's a CONSTRAINT keyword. If found, change its
773     * token code from rrw_constraint (515) to rw_constraint2 (298).
774     * <p>
775     * Extracted from: TGSqlParser.checkconstarinttoken() (lines 1989-2005)
776     *
777     * @param token The token after which to search (PRIMARY/FOREIGN/UNIQUE)
778     */
779    private void checkconstarinttoken(TSourceToken token) {
780        TSourceTokenList lcStList = token.container;
781        if (TBaseType.assigned(lcStList)) {
782            // Look back 2 solid tokens to find CONSTRAINT keyword
783            TSourceToken lcPPToken = lcStList.nextsolidtoken(token.posinlist, -2, false);
784            if (TBaseType.assigned(lcPPToken)) {
785                if (lcPPToken.tokencode == flexer.getkeywordvalue("constraint")) {
786                    // Change CONSTRAINT to special constraint2 token code
787                    lcPPToken.tokencode = TBaseType.rw_constraint2;
788                }
789            }
790        }
791    }
792
793    // ========== SQL Server-Specific Raw Statement Extraction ==========
794
795    /**
796     * SQL Server-specific raw statement extraction logic.
797     * <p>
798     * Extracted from: TGSqlParser.domssqlgetrawsqlstatements() (lines 13081-13904)
799     */
800    private void domssqlgetrawsqlstatements(SqlParseResult.Builder builder) {
801        int errorcount = 0;
802        int case_end_nest = 0;
803
804        if (TBaseType.assigned(sqlstatements)) sqlstatements.clear();
805        if (!TBaseType.assigned(sourcetokenlist)) {
806            builder.errorCode(-1);
807            builder.errorMessage("Source token list is null");
808            return;
809        }
810
811        gcurrentsqlstatement = null;
812        EFindSqlStateType gst = EFindSqlStateType.stnormal;
813        int lcblocklevel = 0;
814        int lctrycatchlevel = 0;
815        TSourceToken lcprevsolidtoken = null, lcnextsolidtoken, lcnnextsolidtoken;
816        TSourceToken ast = null;
817        int i, lcMergeInSelectNested = 0;
818        boolean lcisendconversation, lcstillinsql, lcMergeInSelect = false;
819
820        for (i = 0; i < sourcetokenlist.size(); i++) {
821
822            if ((ast != null) && (ast.issolidtoken()))
823                lcprevsolidtoken = ast;
824
825            ast = sourcetokenlist.get(i);
826            sourcetokenlist.curpos = i;
827
828            if (ast.tokencode == TBaseType.rrw_for) {
829                TSourceToken st1 = ast.nextSolidToken();
830                if ((st1 != null) && (st1.tokencode == TBaseType.rrw_system_time)) {
831                    ast.tokencode = TBaseType.rw_for_system_time;
832                } else if ((st1 != null) && TBaseType.mycomparetext(st1.getAstext(), "PATH") == 0) {
833                    ast.tokencode = TBaseType.rw_for_path;
834                }
835            }
836
837            if (lcMergeInSelect) {
838                if (ast.tokencode == '(') lcMergeInSelectNested++;
839                if (ast.tokencode == ')') {
840                    lcMergeInSelectNested--;
841                    if (lcMergeInSelectNested == 0) {
842                        lcMergeInSelect = false;
843                    }
844                }
845                gcurrentsqlstatement.sourcetokenlist.add(ast);
846                continue;
847            }
848
849            if (ast.tokenstatus == ETokenStatus.tsignoredbygetrawstatement) {
850                gcurrentsqlstatement.sourcetokenlist.add(ast);
851                continue;
852            }
853
854            if (ast.tokencode == TBaseType.rrw_minus) {
855                TSourceToken st1 = ast.searchToken('(', 1);
856                if (st1 == null) {
857                    st1 = ast.searchToken(TBaseType.rrw_select, 1);
858                    if (st1 == null) {
859                        ast.tokencode = TBaseType.ident;
860                    }
861                }
862            } else if (ast.tokencode == TBaseType.rrw_merge) {
863                TSourceToken st1 = ast.nextSolidToken();
864                if (st1.tokencode == TBaseType.rrw_join) {
865                    ast.tokencode = TBaseType.rrw_merge2_sqlserver;
866                }
867                if ((lcprevsolidtoken != null) && (lcprevsolidtoken.tokencode == '(')) {
868                    lcMergeInSelect = true;
869                    lcMergeInSelectNested++;
870                    gcurrentsqlstatement.sourcetokenlist.add(ast);
871                    continue;
872                }
873            } else if (ast.tokencode == TBaseType.rrw_sqlserver_value) {
874                if ((lcprevsolidtoken != null) && (lcprevsolidtoken.tokencode == '.')) {
875                    TSourceToken st1 = ast.searchToken('(', 1);
876                    if (st1 != null) {
877                        ast.tokencode = TBaseType.rrw_xml_value;
878                    }
879                }
880            } else if (ast.tokencode == TBaseType.rrw_sqlserver_modify) {
881                if ((lcprevsolidtoken != null) && (lcprevsolidtoken.tokencode == '.')) {
882                    TSourceToken st1 = ast.searchToken('(', 1);
883                    if (st1 != null) {
884                        ast.tokencode = TBaseType.rrw_xml_modify;
885                    }
886                }
887            } else if (ast.tokencode == TBaseType.rrw_sqlserver_query) {
888                if ((lcprevsolidtoken != null) && (lcprevsolidtoken.tokencode == '.')) {
889                    TSourceToken st1 = ast.searchToken('(', 1);
890                    if (st1 != null) {
891                        ast.tokencode = TBaseType.rrw_xml_query;
892                    }
893                }
894            } else if (ast.tokencode == TBaseType.rrw_sqlserver_exist) {
895                if ((lcprevsolidtoken != null) && (lcprevsolidtoken.tokencode == '.')) {
896                    TSourceToken st1 = ast.searchToken('(', 1);
897                    if (st1 != null) {
898                        ast.tokencode = TBaseType.rrw_xml_exist;
899                    }
900                }
901            } else if (ast.tokencode == TBaseType.rrw_sqlserver_nodes) {
902                if ((lcprevsolidtoken != null) && (lcprevsolidtoken.tokencode == '.')) {
903                    TSourceToken st1 = ast.searchToken('(', 1);
904                    if (st1 != null) {
905                        ast.tokencode = TBaseType.rrw_xml_nodes;
906                    }
907                }
908            } else if (ast.tokencode == TBaseType.ident && ast.tokentype == ETokenType.ttdbstring) {
909                // Handle bracket-quoted XML method names: .[value](), .[nodes](), etc.
910                if ((lcprevsolidtoken != null) && (lcprevsolidtoken.tokencode == '.')) {
911                    TSourceToken st1 = ast.searchToken('(', 1);
912                    if (st1 != null) {
913                        String text = ast.toString();
914                        if (text.length() > 2) {
915                            String inner = text.substring(1, text.length() - 1).toLowerCase();
916                            if (inner.equals("value")) {
917                                ast.tokencode = TBaseType.rrw_xml_value;
918                            } else if (inner.equals("modify")) {
919                                ast.tokencode = TBaseType.rrw_xml_modify;
920                            } else if (inner.equals("query")) {
921                                ast.tokencode = TBaseType.rrw_xml_query;
922                            } else if (inner.equals("exist")) {
923                                ast.tokencode = TBaseType.rrw_xml_exist;
924                            } else if (inner.equals("nodes")) {
925                                ast.tokencode = TBaseType.rrw_xml_nodes;
926                            }
927                        }
928                    }
929                }
930            } else if (ast.tokencode == TBaseType.rrw_check) {
931                if ((lcprevsolidtoken != null) && (lcprevsolidtoken.tokencode == TBaseType.rrw_with)) {
932                    lcprevsolidtoken.tokencode = TBaseType.rrw_sqlserver_check_with;
933                }
934            } else if (ast.tokencode == TBaseType.rrw_sqlserver_next) {
935                TSourceToken st1 = ast.nextSolidToken();
936                if ((st1 != null) && (st1.tokencode == '.')) {
937                    ast.tokencode = TBaseType.ident;
938                }
939            } else if (ast.tokencode == TBaseType.rrw_fetch) {
940                if ((lcprevsolidtoken != null) && ((lcprevsolidtoken.tokencode == TBaseType.rrw_sqlserver_row) || (lcprevsolidtoken.tokencode == TBaseType.rrw_sqlserver_rows))) {
941                    TSourceToken prev2 = lcprevsolidtoken.searchToken(TBaseType.rrw_open, -1);
942                    if (prev2 == null) {
943                        ast.tokencode = TBaseType.rrw_sqlserver_offset_fetch;
944                    }
945                }
946            } else if ((ast.tokencode == TBaseType.rrw_exec) || (ast.tokencode == TBaseType.rrw_execute)) {
947                // search ;2 after execute
948                // EXECUTE ApxSQL_Test_Triggers_Add;2  @TableName, @AddFlag
949                int searchRange = 4;
950                TSourceToken endTokenInSameLine = ast.searchTokenAtTheEndOfSameLine();
951                if (endTokenInSameLine != null) {
952                    searchRange = endTokenInSameLine.posinlist - ast.posinlist;
953                }
954                TSourceToken st1 = ast.searchToken(';', searchRange);
955                if (st1 != null) {
956                    TSourceToken numSt = st1.nextSolidToken();
957                    if ((numSt != null) && (numSt.tokentype == ETokenType.ttnumber)) {
958                        st1.tokencode = TBaseType.rrw_sqlserver_semicolon_module_number;
959                        st1.tokentype = ETokenType.ttidentifier;
960                    }
961                }
962            } else if (ast.tokencode == TBaseType.rrw_sqlserver_trim) {
963                TSourceToken st1 = ast.nextSolidToken();
964                if (st1 != null) {
965                    if (st1.tokencode == '(') {
966                        // keep as trim keyword
967                    } else {
968                        ast.tokencode = TBaseType.ident;
969                    }
970                }
971            }
972
973            if (vendor == EDbVendor.dbvopenedge){
974                if (ast.tokencode == TBaseType.rrw_order){
975                        TSourceToken st1 = ast.searchToken(TBaseType.rrw_by,1);
976                        if (st1 == null) {
977                            ast.tokencode = TBaseType.ident;
978                        }
979                }else if (ast.tokencode == TBaseType.rrw_with){
980                        TSourceToken st1 = ast.searchToken(TBaseType.rrw_check,1);
981                        if (st1 != null) {
982                            ast.tokencode = TBaseType.rrw_openedge_with_check;
983                        }
984                }
985            }
986
987            if (gst == EFindSqlStateType.ststoredprocedurebody) {
988                if (!((ast.tokencode == TBaseType.rrw_go)
989                        || (ast.tokencode == TBaseType.rrw_create)
990                        || (ast.tokencode == TBaseType.rrw_alter))) {
991                    gcurrentsqlstatement.sourcetokenlist.add(ast);
992                    continue;
993                }
994            }
995
996            TCustomSqlStatement lcnextsqlstatement = sqlcmds.issql(ast, gst, gcurrentsqlstatement);
997
998            switch (gst) {
999                case sterror: {
1000                    if (TBaseType.assigned(lcnextsqlstatement)) {
1001                        onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
1002                        gcurrentsqlstatement = lcnextsqlstatement;
1003                        gcurrentsqlstatement.sourcetokenlist.add(ast);
1004                        gst = EFindSqlStateType.stsql;
1005                    } else if ((ast.tokentype == ETokenType.ttsemicolon)) {
1006                        gcurrentsqlstatement.sourcetokenlist.add(ast);
1007                        onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
1008                        gst = EFindSqlStateType.stnormal;
1009                    } else {
1010                        gcurrentsqlstatement.sourcetokenlist.add(ast);
1011                    }
1012                    break;
1013                }
1014                case stnormal: {
1015                    if ((ast.tokencode == TBaseType.cmtdoublehyphen)
1016                            || (ast.tokencode == TBaseType.cmtslashstar)
1017                            || (ast.tokencode == TBaseType.lexspace)
1018                            || (ast.tokencode == TBaseType.lexnewline)
1019                            || (ast.tokentype == ETokenType.ttsemicolon)) {
1020                        if (TBaseType.assigned(gcurrentsqlstatement)) {
1021                            gcurrentsqlstatement.sourcetokenlist.add(ast);
1022                        }
1023
1024                        if (TBaseType.assigned(lcprevsolidtoken) && (ast.tokentype == ETokenType.ttsemicolon)) {
1025                            if (lcprevsolidtoken.tokentype == ETokenType.ttsemicolon) {
1026                                // ;;;; continuous semicolon,treat it as comment
1027                                ast.tokentype = ETokenType.ttsimplecomment;
1028                                ast.tokencode = TBaseType.cmtdoublehyphen;
1029                            }
1030                        }
1031
1032                        continue;
1033                    }
1034
1035                    gcurrentsqlstatement = lcnextsqlstatement;
1036
1037                    if (TBaseType.assigned(gcurrentsqlstatement)) {
1038                        switch (gcurrentsqlstatement.sqlstatementtype) {
1039                            case sstmssqlcreateprocedure:
1040                            case sstmssqlcreatefunction:
1041                            case sstcreatetrigger:
1042                            case sstmssqlalterprocedure:
1043                            case sstmssqlalterfunction:
1044                            case sstmssqlaltertrigger: {
1045                                gcurrentsqlstatement.sourcetokenlist.add(ast);
1046                                gst = EFindSqlStateType.ststoredprocedure;
1047                                break;
1048                            }
1049                            case sstmssqlbegintry:
1050                            case sstmssqlbegincatch: {
1051                                gcurrentsqlstatement.sourcetokenlist.add(ast);
1052                                gst = EFindSqlStateType.sttrycatch;
1053                                lctrycatchlevel = 0;
1054                                break;
1055                            }
1056                            case sstmssqlgo: {
1057                                gcurrentsqlstatement.sourcetokenlist.add(ast);
1058                                onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
1059                                gst = EFindSqlStateType.stnormal;
1060                                break;
1061                            }
1062                            default: {
1063                                gcurrentsqlstatement.sourcetokenlist.add(ast);
1064                                gst = EFindSqlStateType.stsql;
1065                                break;
1066                            }
1067                        }
1068                    } else {
1069                        if (ast.tokencode == TBaseType.rrw_begin) {
1070                            gcurrentsqlstatement = new TMssqlBlock(vendor);
1071                            gcurrentsqlstatement.sqlstatementtype = ESqlStatementType.sstmssqlblock;
1072                            gcurrentsqlstatement.sourcetokenlist.add(ast);
1073                            gst = EFindSqlStateType.stblock;
1074                        } else {
1075                            if (sqlstatements.size() == 0) {
1076                                //first statement of mssql batch, treat it as exec sp
1077                                gst = EFindSqlStateType.stsql;
1078                                gcurrentsqlstatement = new TMssqlExecute(vendor);
1079                                gcurrentsqlstatement.sourcetokenlist.add(ast);
1080                            } else if (sqlstatements.get(sqlstatements.size() - 1).sqlstatementtype == ESqlStatementType.sstmssqlgo) {
1081                                // prev sql is go, treat it as exec sp
1082                                gst = EFindSqlStateType.stsql;
1083                                gcurrentsqlstatement = new TMssqlExecute(vendor);
1084                                gcurrentsqlstatement.sourcetokenlist.add(ast);
1085                            }
1086                        }
1087                    }
1088
1089                    if (!TBaseType.assigned(gcurrentsqlstatement)) //error tokentext found
1090                    {
1091                        this.syntaxErrors.add(new TSyntaxError(ast.getAstext(), ast.lineNo, (ast.columnNo < 0 ? 0 : ast.columnNo)
1092                                , "Error when tokenlize", EErrorType.spwarning, TBaseType.MSG_WARNING_ERROR_WHEN_TOKENIZE, null, ast.posinlist));
1093
1094                        ast.tokentype = ETokenType.tttokenlizererrortoken;
1095                        gst = EFindSqlStateType.sterror;
1096
1097                        gcurrentsqlstatement = new TUnknownSqlStatement(vendor);
1098                        gcurrentsqlstatement.sqlstatementtype = ESqlStatementType.sstinvalid;
1099                        gcurrentsqlstatement.sourcetokenlist.add(ast);
1100                    }
1101                    break;
1102                }
1103                case stblock: {
1104                    if (TBaseType.assigned(lcnextsqlstatement)) {
1105                        if (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlgo) {
1106                            onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
1107                            gcurrentsqlstatement = lcnextsqlstatement;
1108                            gcurrentsqlstatement.sourcetokenlist.add(ast);
1109                            onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
1110                            gst = EFindSqlStateType.stnormal;
1111                        } else {
1112                            lcnextsqlstatement = null;
1113                        }
1114                    }
1115
1116                    if (gst == EFindSqlStateType.stblock) {
1117                        gcurrentsqlstatement.sourcetokenlist.add(ast);
1118                        if (ast.tokencode == TBaseType.rrw_begin) {
1119                            lcnextsolidtoken = sourcetokenlist.nextsolidtoken(i, 1, false);
1120                            if (TBaseType.assigned(lcnextsolidtoken)) {
1121                                if (!((lcnextsolidtoken.tokencode == TBaseType.rrw_sqlserver_tran)
1122                                        || (lcnextsolidtoken.tokencode == TBaseType.rrw_sqlserver_transaction)
1123                                        || (lcnextsolidtoken.tokencode == TBaseType.rrw_sqlserver_distributed)
1124                                        || (lcnextsolidtoken.tokencode == TBaseType.rrw_sqlserver_dialog)
1125                                        || (lcnextsolidtoken.tokencode == TBaseType.rrw_sqlserver_conversation)
1126                                ))
1127                                    lcblocklevel++;
1128                            } else
1129                                lcblocklevel++;
1130
1131                        } else if (ast.tokencode == TBaseType.rrw_case)
1132                            lcblocklevel++;
1133                        else if (ast.tokencode == TBaseType.rrw_end) {
1134
1135                            lcisendconversation = false;
1136
1137                            lcnextsolidtoken = sourcetokenlist.nextsolidtoken(i, 1, false);
1138                            if (TBaseType.assigned(lcnextsolidtoken)) {
1139                                if (lcnextsolidtoken.tokencode == TBaseType.rrw_sqlserver_conversation)
1140                                    lcisendconversation = true;
1141                            }
1142
1143                            if (!lcisendconversation) {
1144
1145                                if (lcblocklevel == 0) {
1146                                    if (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlif) {
1147                                        if (TBaseType.assigned(lcnextsolidtoken)) {
1148                                            if (lcnextsolidtoken.tokencode == TBaseType.rrw_else) {
1149                                                gst = EFindSqlStateType.stsql;
1150                                            } else if (lcnextsolidtoken.tokentype == ETokenType.ttsemicolon) {
1151                                                lcnnextsolidtoken = sourcetokenlist.nextsolidtoken(lcnextsolidtoken.posinlist, 1, false);
1152                                                if (TBaseType.assigned(lcnnextsolidtoken)) {
1153                                                    if (lcnnextsolidtoken.tokencode == TBaseType.rrw_else) {
1154                                                        gst = EFindSqlStateType.stsql;
1155                                                    }
1156                                                }
1157                                            }
1158                                        }
1159                                    }
1160
1161                                    if (gst != EFindSqlStateType.stsql) {
1162                                        onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
1163                                        gst = EFindSqlStateType.stnormal;
1164                                    }
1165
1166                                } else {
1167                                    lcblocklevel--;
1168                                }
1169
1170                            }
1171                        }
1172                    }
1173                    break;
1174                }
1175                case sttrycatch: {
1176
1177                    if (TBaseType.assigned(lcnextsqlstatement)) {
1178                        if (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlgo) {
1179                            onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
1180                            gcurrentsqlstatement = lcnextsqlstatement;
1181                            gcurrentsqlstatement.sourcetokenlist.add(ast);
1182                            onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
1183                            gst = EFindSqlStateType.stnormal;
1184                        } else {
1185                            if (
1186                                    (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlbegintry)
1187                                            || (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlbegincatch)
1188                            )
1189                                lctrycatchlevel++;
1190                            lcnextsqlstatement = null;
1191                        }
1192                    }
1193
1194                    if (gst == EFindSqlStateType.sttrycatch) {
1195                        gcurrentsqlstatement.sourcetokenlist.add(ast);
1196                        if ((ast.tokencode == TBaseType.rrw_try) ||
1197                                (ast.tokencode == TBaseType.rrw_catch)) {
1198                            if (TBaseType.assigned(lcprevsolidtoken)) {
1199                                if (lcprevsolidtoken.tokencode == TBaseType.rrw_end) {
1200                                    if (lctrycatchlevel == 0) {
1201                                        onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
1202                                        gst = EFindSqlStateType.stnormal;
1203                                    } else
1204                                        lctrycatchlevel--;
1205                                }
1206                            }
1207                        }
1208                    }
1209                    break;
1210                }
1211                case stprocedureWithReturn: {
1212                    // found return statement in create procedure/function
1213
1214                    if (TBaseType.assigned(lcnextsqlstatement)) {
1215                        if (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlgo) {
1216                            onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
1217                            gcurrentsqlstatement = lcnextsqlstatement;
1218                            gcurrentsqlstatement.sourcetokenlist.add(ast);
1219                            onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
1220                            gst = EFindSqlStateType.stnormal;
1221                            break;
1222                        }
1223                    }
1224
1225                    gcurrentsqlstatement.sourcetokenlist.add(ast);
1226                    if ((gst == EFindSqlStateType.stprocedureWithReturn) && (ast.tokentype == ETokenType.ttsemicolon)) {
1227                        onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
1228                        gst = EFindSqlStateType.stnormal;
1229                    }
1230                    break;
1231                }
1232                case stsql: {
1233                    if ((ast.tokentype == ETokenType.ttsemicolon)) {
1234                        lcstillinsql = false;
1235                        if (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlif) {
1236                            lcnextsolidtoken = sourcetokenlist.nextsolidtoken(i, 1, false);
1237                            if (TBaseType.assigned(lcnextsolidtoken)) {
1238                                if (lcnextsolidtoken.tokencode == TBaseType.rrw_else) {
1239                                    // if ( expr stmt; else
1240                                    gcurrentsqlstatement.sourcetokenlist.add(ast);
1241                                    lcstillinsql = true;
1242                                }
1243
1244                            }
1245                        }
1246
1247                        if (!lcstillinsql) {
1248                            gst = EFindSqlStateType.stnormal;
1249                            gcurrentsqlstatement.sourcetokenlist.add(ast);
1250                            gcurrentsqlstatement.semicolonended = ast;
1251                            onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
1252                        }
1253
1254                    } else if (TBaseType.assigned(lcnextsqlstatement)) {
1255
1256                        if (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlgo) {
1257                            onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
1258                            gcurrentsqlstatement = lcnextsqlstatement;
1259                            gcurrentsqlstatement.sourcetokenlist.add(ast);
1260                            onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
1261                            gst = EFindSqlStateType.stnormal;
1262                            continue;
1263                        }
1264
1265                        // A typed TUnknownSqlStatement (admin DDL recognized by
1266                        // the splitter, no deep AST) has no internal statement
1267                        // structure: it runs to its semicolon. Without this,
1268                        // ALTER EXTERNAL LANGUAGE ... SET (...) split at SET
1269                        // because SET is a registered statement head.
1270                        if ((gcurrentsqlstatement instanceof gudusoft.gsqlparser.stmt.TUnknownSqlStatement)
1271                                && (gcurrentsqlstatement.sqlstatementtype != ESqlStatementType.sstinvalid)
1272                                && (gcurrentsqlstatement.sqlstatementtype != ESqlStatementType.sstunknown)) {
1273                            gcurrentsqlstatement.sourcetokenlist.add(ast);
1274                            continue;
1275                        }
1276
1277                        switch (gcurrentsqlstatement.sqlstatementtype) {
1278                            case sstmssqlif:
1279                            case sstmssqlwhile: {
1280                                if ((lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlbegincatch)
1281                                        || (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlbegintry)) {
1282                                    gcurrentsqlstatement.sourcetokenlist.add(ast);
1283                                    gst = EFindSqlStateType.stblock;
1284                                    lcblocklevel = 1;
1285                                    lcnextsqlstatement = null;
1286                                    continue;
1287
1288                                } else if (gcurrentsqlstatement.dummytag == 1) {
1289                                    // if ( cond ^stmt nextstmt (^ stands for current pos)
1290                                    gcurrentsqlstatement.sourcetokenlist.add(ast);
1291
1292                                    if ((lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlif)
1293                                            || (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlwhile))
1294                                        gcurrentsqlstatement.dummytag = 1;
1295                                    else
1296                                        gcurrentsqlstatement.dummytag = 0;
1297
1298                                    lcnextsqlstatement = null;
1299                                    continue;
1300                                } else {
1301                                    if (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlif) {
1302                                        if ((ast.nextSolidToken() != null) && (ast.nextSolidToken().tokencode == TBaseType.rrw_sqlserver_exists)) {
1303                                            gcurrentsqlstatement.sourcetokenlist.add(ast);
1304                                            lcnextsqlstatement = null;
1305                                            continue;
1306                                        }
1307                                    }
1308                                }
1309                                break;
1310                            }
1311                            case sstmssqlalterqueue: {
1312                                if (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlexec) {
1313                                    // execute can't be used to delimite alter queue
1314                                    gcurrentsqlstatement.sourcetokenlist.add(ast);
1315                                    lcnextsqlstatement = null;
1316                                    continue;
1317
1318                                }
1319                                break;
1320                            }
1321                            case sstmssqlcreateschema: {
1322                                gcurrentsqlstatement.sourcetokenlist.add(ast);
1323                                lcnextsqlstatement = null;
1324                                continue;
1325                            }
1326                        }
1327
1328                        // "IF EXISTS <name>" belongs to the statement it
1329                        // follows (DROP DEFAULT d IF EXISTS ...). But
1330                        // "IF EXISTS (SELECT ...)" is control flow starting a
1331                        // new statement - real scripts chain unterminated
1332                        // statements before it - so the '(' after EXISTS
1333                        // decides.
1334                        if (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlif) {
1335                            TSourceToken existsTok = ast.nextSolidToken();
1336                            if ((existsTok != null)
1337                                    && (existsTok.tokencode == TBaseType.rrw_sqlserver_exists)) {
1338                                TSourceToken afterExists = existsTok.nextSolidToken();
1339                                if ((afterExists == null)
1340                                        || (afterExists.tokentype != ETokenType.ttleftparenthesis)) {
1341                                    gcurrentsqlstatement.sourcetokenlist.add(ast);
1342                                    continue;
1343                                }
1344                            }
1345                        }
1346
1347                        // ALTER PARTITION FUNCTION f() MERGE|SPLIT RANGE (...):
1348                        // the MERGE keyword is the function operation, not a
1349                        // MERGE statement.
1350                        if ((gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlAlterPartitionFunction)
1351                                && (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmerge)) {
1352                            gcurrentsqlstatement.sourcetokenlist.add(ast);
1353                            continue;
1354                        }
1355
1356                        onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
1357                        gcurrentsqlstatement = lcnextsqlstatement;
1358                        gcurrentsqlstatement.sourcetokenlist.add(ast);
1359
1360                        switch (gcurrentsqlstatement.sqlstatementtype) {
1361                            case sstmssqlcreateprocedure:
1362                            case sstmssqlcreatefunction:
1363                            case sstcreatetrigger:
1364                            case sstmssqlalterprocedure:
1365                            case sstmssqlalterfunction:
1366                            case sstmssqlaltertrigger: {
1367                                gst = EFindSqlStateType.ststoredprocedure;
1368                                break;
1369                            }
1370                            case sstmssqlbegintry:
1371                            case sstmssqlbegincatch: {
1372                                gst = EFindSqlStateType.sttrycatch;
1373                                lctrycatchlevel = 0;
1374                                break;
1375                            }
1376                            case sstmssqlgo: {
1377                                gst = EFindSqlStateType.stnormal;
1378                                break;
1379                            }
1380                            default: {
1381                                gst = EFindSqlStateType.stsql;
1382                                break;
1383                            }
1384                        }
1385
1386                    } else if ((ast.tokencode == TBaseType.rrw_begin)) {
1387                        if ((gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlif)
1388                                || (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlwhile)) {
1389                            gst = EFindSqlStateType.stblock;
1390                            lcblocklevel = 0;
1391                            gcurrentsqlstatement.sourcetokenlist.add(ast);
1392                        } else if (case_end_nest > 0) {
1393                            // Inside a CASE expression — BEGIN cannot be a top-level block boundary
1394                            gcurrentsqlstatement.sourcetokenlist.add(ast);
1395                        } else {
1396                            // mantisbt 4411: a plain BEGIN ... END after a complete statement
1397                            // (EXEC, SELECT, UPDATE, SET, DECLARE, PRINT, RAISERROR, label, ...)
1398                            // starts a new top-level TMssqlBlock. Previously the else branch
1399                            // silently swallowed BEGIN into the preceding statement, breaking
1400                            // the parse for batches like:
1401                            //   EXEC dbo.p1
1402                            //   BEGIN UPDATE x SET y = 1 END
1403                            onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
1404                            gcurrentsqlstatement = new TMssqlBlock(vendor);
1405                            gcurrentsqlstatement.sqlstatementtype = ESqlStatementType.sstmssqlblock;
1406                            gcurrentsqlstatement.sourcetokenlist.add(ast);
1407                            gst = EFindSqlStateType.stblock;
1408                        }
1409                    } else if ((ast.tokencode == TBaseType.rrw_case)) {
1410                        case_end_nest++;
1411                        gcurrentsqlstatement.sourcetokenlist.add(ast);
1412                    } else if ((ast.tokencode == TBaseType.rrw_end)) {
1413                        if (case_end_nest > 0) {
1414                            case_end_nest--;
1415                        }
1416                        gcurrentsqlstatement.sourcetokenlist.add(ast);
1417                    } else if ((ast.tokencode == TBaseType.rrw_else)) {
1418                        gcurrentsqlstatement.sourcetokenlist.add(ast);
1419                        if (((gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlif)
1420                                || (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlwhile)
1421                        ) && (case_end_nest == 0))
1422                            gcurrentsqlstatement.dummytag = 1;
1423                    } else {
1424                        gcurrentsqlstatement.sourcetokenlist.add(ast);
1425                    }
1426                    break;
1427                }
1428                case ststoredprocedure: {
1429                    if (TBaseType.assigned(lcnextsqlstatement)) {
1430                        if (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlgo) {
1431                            onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
1432                            gcurrentsqlstatement = lcnextsqlstatement;
1433                            gcurrentsqlstatement.sourcetokenlist.add(ast);
1434                            onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
1435                            gst = EFindSqlStateType.stnormal;
1436                        } else if (lcnextsqlstatement.sqlstatementtype == sstmssqlreturn) {
1437                            gst = EFindSqlStateType.stprocedureWithReturn;
1438                            gcurrentsqlstatement.sourcetokenlist.add(ast);
1439                            lcnextsqlstatement = null;
1440                        } else {
1441                            gst = EFindSqlStateType.ststoredprocedurebody;
1442                            gcurrentsqlstatement.sourcetokenlist.add(ast);
1443
1444                            lcnextsqlstatement = null;
1445                        }
1446                    }
1447
1448                    if (gst == EFindSqlStateType.ststoredprocedure) {
1449                        gcurrentsqlstatement.sourcetokenlist.add(ast);
1450                        if (ast.tokencode == TBaseType.rrw_begin) {
1451                            gst = EFindSqlStateType.stblock;
1452                        } else if ((ast.tokentype == ETokenType.ttsemicolon)
1453                                && hasExternalNameBody(gcurrentsqlstatement)) {
1454                            // CLR routine: AS EXTERNAL NAME assembly.class.method has no
1455                            // BEGIN...END body, so the semicolon ends the statement. Without
1456                            // this the splitter keeps waiting for a body and swallows the
1457                            // next statement of the script.
1458                            onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
1459                            gst = EFindSqlStateType.stnormal;
1460                        }
1461                    }
1462                    break;
1463                }
1464                case ststoredprocedurebody: {
1465                    if (TBaseType.assigned(lcnextsqlstatement)) {
1466                        switch (lcnextsqlstatement.sqlstatementtype) {
1467                            case sstmssqlgo: {
1468                                onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
1469                                gcurrentsqlstatement = lcnextsqlstatement;
1470                                gcurrentsqlstatement.sourcetokenlist.add(ast);
1471                                onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
1472                                gst = EFindSqlStateType.stnormal;
1473                                break;
1474                            }
1475                            case sstmssqlcreateprocedure:
1476                            case sstmssqlcreatefunction:
1477                            case sstcreatetrigger:
1478                            case sstmssqlalterprocedure:
1479                            case sstmssqlalterfunction:
1480                            case sstmssqlaltertrigger: {
1481                                onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
1482                                gcurrentsqlstatement = lcnextsqlstatement;
1483                                gcurrentsqlstatement.sourcetokenlist.add(ast);
1484                                gst = EFindSqlStateType.ststoredprocedure;
1485                                break;
1486                            }
1487                            case sstcreateview:
1488                            case sstcreatetable: {
1489
1490                                boolean readForNewStmt = false;
1491                                TSourceToken st1 = ast.searchToken(';', -1);
1492                                if (st1 != null) {
1493                                    TSourceToken st2 = ast.searchToken(TBaseType.rrw_end, -2);
1494                                    if (st2 != null) {
1495                                        // Verify this END is at block level 0 (not inside a nested
1496                                        // BEGIN...END or BEGIN TRY...END TRY block) by counting
1497                                        // BEGINs and ENDs in the current proc's token list.
1498                                        int blockDepth = 0;
1499                                        for (int k = 0; k < gcurrentsqlstatement.sourcetokenlist.size(); k++) {
1500                                            TSourceToken tk = gcurrentsqlstatement.sourcetokenlist.get(k);
1501                                            if (tk.tokencode == TBaseType.rrw_begin) {
1502                                                TSourceToken tkNext = tk.nextSolidToken();
1503                                                if (tkNext == null || (tkNext.tokencode != TBaseType.rrw_sqlserver_tran
1504                                                        && tkNext.tokencode != TBaseType.rrw_sqlserver_transaction
1505                                                        && tkNext.tokencode != TBaseType.rrw_sqlserver_distributed
1506                                                        && tkNext.tokencode != TBaseType.rrw_sqlserver_dialog
1507                                                        && tkNext.tokencode != TBaseType.rrw_sqlserver_conversation)) {
1508                                                    blockDepth++;
1509                                                }
1510                                            } else if (tk.tokencode == TBaseType.rrw_end) {
1511                                                TSourceToken tkNext = tk.nextSolidToken();
1512                                                if (tkNext == null || tkNext.tokencode != TBaseType.rrw_sqlserver_conversation) {
1513                                                    blockDepth--;
1514                                                }
1515                                            }
1516                                        }
1517                                        // blockDepth == 0 means all BEGIN/END pairs are balanced
1518                                        // so the END; is at the top level (proc boundary)
1519                                        if (blockDepth == 0) {
1520                                            readForNewStmt = true;
1521                                        }
1522                                    }
1523                                }
1524
1525                                if (readForNewStmt) {
1526                                    onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
1527                                    gcurrentsqlstatement = lcnextsqlstatement;
1528                                    gcurrentsqlstatement.sourcetokenlist.add(ast);
1529                                    gst = EFindSqlStateType.stsql;
1530                                } else {
1531                                    lcnextsqlstatement = null;
1532                                }
1533                                break;
1534                            }
1535                            case sstmssqlDropSecurityPolicy:
1536                            case sstmssqlAlterSecurityPolicy:
1537                            case sstmssqlCreateSecurityPolicy:
1538                                onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
1539                                gcurrentsqlstatement = lcnextsqlstatement;
1540                                gcurrentsqlstatement.sourcetokenlist.add(ast);
1541                                gst = EFindSqlStateType.stsql;
1542
1543                                break;
1544                            default: {
1545                                lcnextsqlstatement = null;
1546                                break;
1547                            }
1548                        }
1549                    }
1550
1551                    if (gst == EFindSqlStateType.ststoredprocedurebody)
1552                        gcurrentsqlstatement.sourcetokenlist.add(ast);
1553                }
1554                break;
1555            }
1556        }
1557
1558        //last statement
1559        if (TBaseType.assigned(gcurrentsqlstatement) && (gst != EFindSqlStateType.stnormal)) {
1560            onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, true, builder);
1561        }
1562
1563        // Set results in builder
1564        builder.sqlStatements(this.sqlstatements);
1565        builder.errorCode(errorcount);
1566        builder.errorMessage(errorcount == 0 ? "" : String.format("Extraction completed with %d error(s)", errorcount));
1567    }
1568}