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.TLexerSybaseiq;
010import gudusoft.gsqlparser.TParserSybaseiq;
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.BitSet;
038import java.util.List;
039import java.util.Stack;
040
041/**
042 * Sybase IQ SQL parser implementation.
043 *
044 * <p>This parser handles Sybase IQ-specific SQL syntax including:
045 * <ul>
046 *   <li>Sybase stored procedures with GO statement boundaries</li>
047 *   <li>Sybase BEGIN/END blocks</li>
048 *   <li>Sybase TRY/CATCH blocks</li>
049 *   <li>Sybase IQ-specific statement types</li>
050 * </ul>
051 *
052 * <p><b>Design Notes:</b>
053 * <ul>
054 *   <li>Extends {@link AbstractSqlParser} using the template method pattern</li>
055 *   <li>Uses {@link TLexerSybaseiq} for tokenization</li>
056 *   <li>Uses {@link TParserSybaseiq} for parsing</li>
057 *   <li>Tokenization and statement extraction similar to MSSQL</li>
058 *   <li>Delimiter character: ';' for Sybase statements</li>
059 * </ul>
060 *
061 * @see SqlParser
062 * @see AbstractSqlParser
063 * @see TLexerSybaseiq
064 * @see TParserSybaseiq
065 * @since 3.2.0.0
066 */
067public class SybaseIqSqlParser extends AbstractSqlParser {
068
069    /**
070     * Policy layer of the recognize-then-reject design (see
071     * docs/designs/sybase-tsql-vendor-decomposition.md).
072     *
073     * <p>The splitter registers the FULL family command set, so statement
074     * boundaries are always correct even in multi-statement input and in both
075     * splitter modes. Statements recognised as types this product does not
076     * have are then rejected HERE, with the statement type preserved and the
077     * error anchored at the statement's first token.
078     *
079     * <p>De-registration was the previous mechanism and is retired: it made
080     * the splitter mis-attach forbidden statements to their predecessor in
081     * multi-statement input (swallowing them with rc=0), and crashed outright
082     * where the grammar builds a dedicated node.
083     */
084    private static final java.util.EnumSet<ESqlStatementType> FORBIDDEN_STATEMENTS =
085        java.util.EnumSet.of(
086            ESqlStatementType.sstsybasedumpTran,
087            ESqlStatementType.sstmssqlreadtext,
088            ESqlStatementType.sstsybaseupdateindexstatistics,
089            ESqlStatementType.sstsybaseDeleteStatistics);
090
091    private static final java.util.Map<ESqlStatementType, String> FORBIDDEN_MESSAGES =
092        new java.util.EnumMap<>(ESqlStatementType.class);
093    static {
094        FORBIDDEN_MESSAGES.put(ESqlStatementType.sstsybasedumpTran,
095                "DUMP TRANSACTION is Sybase ASE syntax; use BACKUP and is not valid for Sybase IQ");
096        FORBIDDEN_MESSAGES.put(ESqlStatementType.sstmssqlreadtext,
097                "READTEXT is Sybase ASE syntax and is not valid for Sybase IQ");
098        FORBIDDEN_MESSAGES.put(ESqlStatementType.sstsybaseupdateindexstatistics,
099                "UPDATE INDEX/ALL/TABLE STATISTICS is Sybase ASE syntax and is not valid for Sybase IQ");
100        FORBIDDEN_MESSAGES.put(ESqlStatementType.sstsybaseDeleteStatistics,
101                "DELETE STATISTICS is Sybase ASE syntax; use DROP STATISTICS and is not valid for Sybase IQ");
102    }
103
104    /**
105     * Post-parse policy validation ("recognize then reject"). Called by
106     * TGSqlParser after every statement is parsed. Attaches a vendor error to
107     * each recognised-but-foreign statement and returns them.
108     *
109     * <p>Not hooked at raw-extraction time: parsing REPLACES some statement
110     * objects (the refined statement is rebuilt from the parse-tree node — e.g.
111     * DELETE STATISTICS and READTEXT), and an error attached to the raw object
112     * vanishes with it. Post-parse, the statement list is final. This was
113     * measured, not assumed: raw-attached errors survived for DUMP TRAN but
114     * disappeared for DELETE STATISTICS and READTEXT.
115     */
116    @Override
117    public java.util.List<TCustomSqlStatement> validateRecognizedStatements(TStatementList stmts) {
118        java.util.List<TCustomSqlStatement> rejected = new java.util.ArrayList<>();
119        if (stmts == null) return rejected;
120        for (int i = 0; i < stmts.size(); i++) {
121            TCustomSqlStatement st = stmts.get(i);
122            if (st == null || !FORBIDDEN_STATEMENTS.contains(st.sqlstatementtype)) continue;
123            rejected.add(st);
124            TSourceToken tok = (st.sourcetokenlist != null && st.sourcetokenlist.size() > 0)
125                    ? st.sourcetokenlist.get(0) : null;
126            String msg = FORBIDDEN_MESSAGES.get(st.sqlstatementtype);
127            if (tok != null) {
128                st.parseerrormessagehandle(new TSyntaxError(tok.toString(), tok.lineNo,
129                        tok.columnNo, msg, EErrorType.sperror, 10101, st, tok.posinlist));
130            } else {
131                st.parseerrormessagehandle(new TSyntaxError(msg, 0, 0, msg,
132                        EErrorType.sperror, 10101, st, -1));
133            }
134        }
135        return rejected;
136    }
137
138
139    /**
140     * Construct Sybase IQ SQL parser.
141     * <p>
142     * Configures the parser for Sybase with default delimiter (;).
143     */
144    public SybaseIqSqlParser() {
145        super(EDbVendor.dbvsybaseiq);
146        this.delimiterChar = ';';
147        this.defaultDelimiterStr = ";";
148
149        // Create lexer once - will be reused for all parsing operations
150        this.flexer = new TLexerSybaseiq();
151        this.flexer.delimiterchar = this.delimiterChar;
152        this.flexer.defaultDelimiterStr = this.defaultDelimiterStr;
153
154        // Set parent's lexer reference for shared tokenization logic
155        this.lexer = this.flexer;
156
157        // Create parser once - will be reused for all parsing operations
158        this.fparser = new TParserSybaseiq(null);
159        this.fparser.lexer = this.flexer;
160    }
161
162    // ========== Parser Components ==========
163
164    /** The Sybase lexer used for tokenization */
165    public TLexerSybaseiq flexer;
166
167    /** Sybase parser (for Sybase statements) */
168    private TParserSybaseiq fparser;
169
170    /** Current statement being built during extraction */
171    private TCustomSqlStatement gcurrentsqlstatement;
172
173    // ========== AbstractSqlParser Abstract Methods Implementation ==========
174
175    /**
176     * Return the Sybase lexer instance.
177     */
178    @Override
179    protected TCustomLexer getLexer(ParserContext context) {
180        return this.flexer;
181    }
182
183    /**
184     * Return the Sybase parser instance with updated token list.
185     */
186    @Override
187    protected TCustomParser getParser(ParserContext context, TSourceTokenList tokens) {
188        this.fparser.sourcetokenlist = tokens;
189        return this.fparser;
190    }
191
192    /**
193     * Call Sybase IQ-specific tokenization logic.
194     * <p>
195     * Sybase uses MSSQL-like tokenization.
196     */
197    @Override
198    protected void tokenizeVendorSql() {
199        dosybasesqltexttotokenlist();
200    }
201
202    /**
203     * Setup Sybase parser for raw statement extraction.
204     */
205    @Override
206    protected void setupVendorParsersForExtraction() {
207        this.fparser.sqlcmds = this.sqlcmds;
208        this.fparser.sourcetokenlist = this.sourcetokenlist;
209    }
210
211    /**
212     * Call Sybase IQ-specific raw statement extraction logic.
213     */
214    @Override
215    protected void extractVendorRawStatements(SqlParseResult.Builder builder) {
216        dosybasegetrawsqlstatements(builder);
217    }
218
219    /**
220     * Perform full parsing of statements with syntax checking.
221     */
222    @Override
223    protected TStatementList performParsing(ParserContext context,
224                                           TCustomParser parser,
225                                           TCustomParser secondaryParser,
226                                           TSourceTokenList tokens,
227                                           TStatementList rawStatements) {
228        this.fparser = (TParserSybaseiq) parser;
229        this.sourcetokenlist = tokens;
230        this.parserContext = context;
231        this.sqlstatements = rawStatements;
232
233        this.sqlcmds = SqlCmdsFactory.get(vendor);
234        this.fparser.sqlcmds = this.sqlcmds;
235
236        if (context != null && context.getGsqlparser() != null) {
237            TGSqlParser gsqlparser = (TGSqlParser) context.getGsqlparser();
238            this.frameStack = gsqlparser.getFrameStack();
239            this.fparser.getNf().setGsqlParser(gsqlparser);
240            this.globalContext = new TContext();
241            this.sqlEnv = new TSQLEnv(this.vendor) {
242                @Override
243                public void initSQLEnv() {
244                }
245            };
246            this.globalContext.setSqlEnv(this.sqlEnv, this.sqlstatements);
247        } else {
248            initializeGlobalContext();
249        }
250
251        for (int i = 0; i < sqlstatements.size(); i++) {
252            TCustomSqlStatement stmt = sqlstatements.getRawSql(i);
253
254            try {
255                stmt.setFrameStack(frameStack);
256                int parseResult = stmt.parsestatement(null, false, context.isOnlyNeedRawParseTree());
257
258                boolean doRecover = TBaseType.ENABLE_ERROR_RECOVER_IN_CREATE_TABLE;
259                if (doRecover && ((parseResult != 0) || (stmt.getErrorCount() > 0))) {
260                    handleCreateTableErrorRecovery(stmt);
261                }
262
263                if ((parseResult != 0) || (stmt.getErrorCount() > 0)) {
264                    copyErrorsFromStatement(stmt);
265                }
266
267            } catch (Exception ex) {
268                handleStatementParsingException(stmt, i, ex);
269                continue;
270            }
271        }
272
273        if (globalFrame != null) {
274            globalFrame.popMeFromStack(frameStack);
275        }
276
277        return this.sqlstatements;
278    }
279
280    private void handleCreateTableErrorRecovery(TCustomSqlStatement stmt) {
281        if (((stmt.sqlstatementtype == ESqlStatementType.sstcreatetable)
282                || (stmt.sqlstatementtype == ESqlStatementType.sstcreateindex))
283                && (!TBaseType.c_createTableStrictParsing)) {
284
285            int nested = 0;
286            boolean isIgnore = false, isFoundIgnoreToken = false;
287            TSourceToken firstIgnoreToken = null;
288
289            for (int k = 0; k < stmt.sourcetokenlist.size(); k++) {
290                TSourceToken st = stmt.sourcetokenlist.get(k);
291                if (isIgnore) {
292                    if (st.issolidtoken() && (st.tokencode != ';')) {
293                        isFoundIgnoreToken = true;
294                        if (firstIgnoreToken == null) {
295                            firstIgnoreToken = st;
296                        }
297                    }
298                    if (st.tokencode != ';') {
299                        st.tokencode = TBaseType.sqlpluscmd;
300                    }
301                    continue;
302                }
303                if (st.tokencode == (int) ')') {
304                    nested--;
305                    if (nested == 0) {
306                        boolean isSelect = false;
307                        TSourceToken st1 = st.searchToken(TBaseType.rrw_as, 1);
308                        if (st1 != null) {
309                            TSourceToken st2 = st.searchToken((int) '(', 2);
310                            if (st2 != null) {
311                                TSourceToken st3 = st.searchToken(TBaseType.rrw_select, 3);
312                                isSelect = (st3 != null);
313                            }
314                        }
315                        if (!isSelect) isIgnore = true;
316                    }
317                } else if (st.tokencode == (int) '(') {
318                    nested++;
319                }
320            }
321
322            if (isFoundIgnoreToken) {
323                stmt.clearError();
324                stmt.parsestatement(null, false);
325            }
326        }
327    }
328
329    @Override
330    protected void performSemanticAnalysis(ParserContext context, TStatementList statements) {
331        if (context != null && context.getGsqlparser() != null) {
332            return;
333        }
334
335        if (getSyntaxErrors().isEmpty()) {
336            if (TBaseType.isEnableResolver2()) {
337                TSQLResolverConfig config = new TSQLResolverConfig();
338                config.setVendor(vendor);
339                TSQLResolver2 resolver2 = new TSQLResolver2(null, statements, config);
340                if (this.sqlEnv != null) {
341                    resolver2.setSqlEnv(this.sqlEnv);
342                }
343                resolver2.resolve();
344            } else if (TBaseType.isEnableResolver()) {
345                TSQLResolver resolver = new TSQLResolver(globalContext, statements);
346                resolver.resolve();
347            }
348        }
349    }
350
351    @Override
352    protected void performInterpreter(ParserContext context, TStatementList statements) {
353        if (TBaseType.ENABLE_INTERPRETER && getSyntaxErrors().isEmpty()) {
354            TLog.clearLogs();
355            TGlobalScope interpreterScope = new TGlobalScope(sqlEnv);
356            TLog.enableInterpreterLogOnly();
357            TASTEvaluator astEvaluator = new TASTEvaluator(statements, interpreterScope);
358            astEvaluator.eval();
359        }
360    }
361
362    // ========== Helper Methods ==========
363
364    /**
365     * Add token to current statement with proper statement linkage.
366     * This replicates TCustomSqlStatement.addtokentolist() behavior
367     * which sets st.stmt = this before adding.
368     */
369    private void addTokenToStatement(TSourceToken st) {
370        st.stmt = gcurrentsqlstatement;
371        gcurrentsqlstatement.sourcetokenlist.add(st);
372    }
373
374    // ========== Sybase-Specific Tokenization ==========
375
376    /**
377     * Sybase IQ-specific tokenization logic.
378     * <p>
379     * Sybase uses MSSQL-like tokenization.
380     * Migrated from TGSqlParser.dosybasesqltexttotokenlist()
381     */
382    private void dosybasesqltexttotokenlist() {
383        TSourceToken lcprevtoken = null;
384        int lcsteps = 0;
385        TSourceToken asourcetoken, lctoken, lctoken2, lctoken3;
386        int yychar;
387        boolean iskeywordgo;
388
389        asourcetoken = getanewsourcetoken();
390
391        if (asourcetoken == null) return;
392
393        yychar = asourcetoken.tokencode;
394
395        boolean lcinopenrowset = false;
396        int lcnested = 0;
397
398        while (yychar > 0) {
399
400            if (asourcetoken.tokencode == TBaseType.rrw_openrowset) {
401                lcinopenrowset = true;
402                lcnested = 0;
403            } else if (asourcetoken.tokentype == ETokenType.ttleftparenthesis) {
404                if ((lcsteps > 0) && TBaseType.assigned(lcprevtoken)) {
405                    if (lcprevtoken.tokencode == TBaseType.rrw_primary) {
406                        lcprevtoken.tokencode = TBaseType.rrw_select - 2;
407                        checkconstarinttoken(lcprevtoken);
408                    } else if (lcprevtoken.tokencode == TBaseType.rrw_foreign) {
409                        lcprevtoken.tokencode = TBaseType.rrw_select - 4;
410                        checkconstarinttoken(lcprevtoken);
411                    } else if (lcprevtoken.tokencode == TBaseType.rrw_unique) {
412                        lcprevtoken.tokencode = TBaseType.rrw_select - 1;
413                        checkconstarinttoken(lcprevtoken);
414                    }
415                    lcprevtoken = null;
416                    lcsteps = 0;
417                }
418
419                if (lcinopenrowset)
420                    lcnested++;
421            } else if (asourcetoken.tokentype == ETokenType.ttrightparenthesis) {
422                if (lcinopenrowset) {
423                    if ((lcnested > 0))
424                        lcnested--;
425                    if (lcnested == 0)
426                        lcinopenrowset = false;
427                }
428            } else if (asourcetoken.tokentype == ETokenType.ttsemicolon) {
429                if (lcinopenrowset) {
430                    asourcetoken.tokentype = ETokenType.ttsemicolon2;
431                } else {
432                    lctoken2 = asourcetoken.searchToken(TBaseType.rrw_begin, -1);
433                    if (lctoken2 != null) {
434                        asourcetoken.tokencode = TBaseType.SEMI_COLON_AFTER_BEGIN;
435                        asourcetoken.tokentype = ETokenType.ttsemicolon3;
436                    } else {
437                        lctoken2 = asourcetoken.searchToken(TBaseType.rrw_try, -1);
438                        if (lctoken2 == null) {
439                            lctoken2 = asourcetoken.searchToken(TBaseType.rrw_catch, -1);
440                        }
441                        if (lctoken2 != null) {
442                            lctoken3 = asourcetoken.searchToken(TBaseType.rrw_begin, -2);
443                            if (lctoken3 != null) {
444                                asourcetoken.tokencode = TBaseType.SEMI_COLON_AFTER_BEGIN;
445                                asourcetoken.tokentype = ETokenType.ttsemicolon3;
446                            }
447                        }
448                    }
449                }
450            } else if (asourcetoken.tokentype == ETokenType.ttperiod) {
451                lctoken = getprevtoken(asourcetoken);
452                if (TBaseType.assigned(lctoken)) {
453                    if (lctoken.tokencode == TBaseType.rrw_go) {
454                        lctoken.tokencode = TBaseType.ident;
455                        lctoken.tokentype = ETokenType.ttidentifier;
456                    }
457                }
458            } else if (asourcetoken.tokencode == TBaseType.rrw_table) {
459                lctoken = getprevtoken(asourcetoken);
460                if (TBaseType.assigned(lctoken)) {
461                    if (lctoken.tokencode == TBaseType.rrw_lock) {
462                        lctoken.tokencode = TBaseType.rw_locktable;
463                    }
464                }
465            } else if (asourcetoken.tokencode == TBaseType.rrw_sybase_isolation) {
466                // AT ISOLATION clause - change 'at' token code for grammar recognition
467                lctoken = getprevtoken(asourcetoken);
468                if (TBaseType.assigned(lctoken)) {
469                    if (lctoken.tokencode == TBaseType.rrw_sybase_at) {
470                        lctoken.tokencode = TBaseType.rw_sybase_at1;
471                    }
472                }
473            } else if (asourcetoken.tokencode == TBaseType.rrw_update) {
474                // UPDATE in create trigger context
475                lctoken = getprevtoken(asourcetoken);
476                if (TBaseType.assigned(lctoken)) {
477                    if ((lctoken.tokencode == TBaseType.rrw_not)
478                            || (lctoken.tokencode == TBaseType.rrw_and)
479                            || (lctoken.tokencode == TBaseType.rrw_or)
480                            || (lctoken.tokencode == TBaseType.rrw_if)
481                    ) {
482                        // this is update(column) in create trigger
483                        asourcetoken.tokencode = TBaseType.rw_sybase_update1;
484                    }
485                }
486            } else if (asourcetoken.tokencode == TBaseType.rrw_go) {
487                iskeywordgo = true;
488                lctoken = getprevtoken(asourcetoken);
489                if (TBaseType.assigned(lctoken)) {
490                    if ((lctoken.lineNo == asourcetoken.lineNo) && (lctoken.tokencode != ';')) {
491                        iskeywordgo = false;
492                    }
493                }
494
495                if (iskeywordgo) {
496                    lcinopenrowset = false;
497                    lcnested = 0;
498                    lcprevtoken = asourcetoken;
499                } else {
500                    asourcetoken.tokencode = TBaseType.ident;
501                    asourcetoken.tokentype = ETokenType.ttidentifier;
502                }
503            } else if (asourcetoken.tokencode == TBaseType.rrw_primary) {
504                lcsteps = 2;
505                lcprevtoken = asourcetoken;
506            } else if (asourcetoken.tokencode == TBaseType.rrw_foreign) {
507                lcsteps = 2;
508                lcprevtoken = asourcetoken;
509            } else if (asourcetoken.tokencode == TBaseType.rrw_unique) {
510                lcsteps = 1;
511                lcprevtoken = asourcetoken;
512            } else if (asourcetoken.issolidtoken()) {
513                if (lcsteps > 0) {
514                    if (!(TBaseType.mysametext("clustered", asourcetoken.toString())
515                            || TBaseType.mysametext("nonclustered", asourcetoken.toString())
516                            || TBaseType.mysametext("hash", asourcetoken.toString())
517                    ))
518                        lcsteps--;
519                }
520            }
521
522            sourcetokenlist.add(asourcetoken);
523
524            asourcetoken = getanewsourcetoken();
525            if (asourcetoken == null) break;
526            yychar = asourcetoken.tokencode;
527        }
528    }
529
530    private TSourceToken getprevtoken(TSourceToken ptoken) {
531        TSourceTokenList lcstlist = ptoken.container;
532        if (TBaseType.assigned(lcstlist)) {
533            if ((ptoken.posinlist > 0) && (lcstlist.size() > ptoken.posinlist - 1)) {
534                if (!((lcstlist.get(ptoken.posinlist - 1).tokentype == ETokenType.ttwhitespace)
535                        || (lcstlist.get(ptoken.posinlist - 1).tokentype == ETokenType.ttreturn)
536                        || (lcstlist.get(ptoken.posinlist - 1).tokentype == ETokenType.ttsimplecomment)
537                        || (lcstlist.get(ptoken.posinlist - 1).tokentype == ETokenType.ttbracketedcomment)
538                )) {
539                    return lcstlist.get(ptoken.posinlist - 1);
540                } else {
541                    return lcstlist.nextsolidtoken(ptoken.posinlist - 1, -1, false);
542                }
543            }
544        }
545        return null;
546    }
547
548    private void checkconstarinttoken(TSourceToken token) {
549        TSourceTokenList lcStList = token.container;
550        if (TBaseType.assigned(lcStList)) {
551            TSourceToken lcPPToken = lcStList.nextsolidtoken(token.posinlist, -2, false);
552            if (TBaseType.assigned(lcPPToken)) {
553                if (lcPPToken.tokencode == flexer.getkeywordvalue("constraint")) {
554                    lcPPToken.tokencode = TBaseType.rw_constraint2;
555                }
556            }
557        }
558    }
559
560    // ========== Sybase-Specific Raw Statement Extraction ==========
561
562    /**
563     * Sybase IQ-specific raw statement extraction logic.
564     * <p>
565     * Migrated from TGSqlParser.dosybasegetrawsqlstatements()
566     */
567    private void dosybasegetrawsqlstatements(SqlParseResult.Builder builder) {
568        int errorcount = 0;
569        int case_end_nest = 0;
570        // Watcom / SAP IQ / SQL Anywhere `IF cond THEN ... END IF` tracking.
571        // `lcwatcomIfNest` counts open IF...THEN blocks awaiting END IF or
572        // ENDIF inside a BEGIN/END block. `lcwhenPending` counts WHEN tokens
573        // awaiting their matching THEN so that CASE WHEN ... THEN, MERGE
574        // WHEN ... THEN and EXCEPTION WHEN ... THEN do not look like Watcom
575        // IF openers. The counters are only used in the stblock state — at
576        // top level we still rely on the standard statement-splitter path.
577        int lcwatcomIfNest = 0;
578        int lcwhenPending = 0;
579        // Mantis 4717: the block levels of the stblock state that a CASE opened, and the last END that
580        // closed a CASE (in stblock or stsql). The CASE of `END CASE` follows exactly that END; a CASE after
581        // the END of a BEGIN block starts a new CASE, even with no semicolon between them.
582        BitSet lccaseopenedlevels = new BitSet();
583        TSourceToken lcendofcase = null;
584
585        if (TBaseType.assigned(sqlstatements)) sqlstatements.clear();
586        if (!TBaseType.assigned(sourcetokenlist)) {
587            builder.errorCode(-1);
588            builder.errorMessage("Source token list is null");
589            return;
590        }
591
592        gcurrentsqlstatement = null;
593        EFindSqlStateType gst = EFindSqlStateType.stnormal;
594        int lcblocklevel = 0;
595        int lctrycatchlevel = 0;
596        TSourceToken lcprevsolidtoken = null, lcnextsolidtoken, lcnnextsolidtoken;
597        TSourceToken ast = null;
598        int i;
599        boolean lcisendconversation, lcstillinsql;
600
601        for (i = 0; i < sourcetokenlist.size(); i++) {
602
603            if ((ast != null) && (ast.issolidtoken()))
604                lcprevsolidtoken = ast;
605
606            ast = sourcetokenlist.get(i);
607            sourcetokenlist.curpos = i;
608            if (ast.tokenstatus == ETokenStatus.tsignoredbygetrawstatement) {
609                addTokenToStatement(ast);
610                continue;
611            }
612
613            if (gst == EFindSqlStateType.ststoredprocedurebody) {
614                if (!((ast.tokencode == TBaseType.rrw_go)
615                        || (ast.tokencode == TBaseType.rrw_create)
616                        || (ast.tokencode == TBaseType.rrw_alter))) {
617                    addTokenToStatement(ast);
618                    continue;
619                }
620            }
621
622            TCustomSqlStatement lcnextsqlstatement = sqlcmds.issql(ast, gst, gcurrentsqlstatement);
623
624            switch (gst) {
625                case sterror: {
626                    if (TBaseType.assigned(lcnextsqlstatement)) {
627                        onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
628                        gcurrentsqlstatement = lcnextsqlstatement;
629                        addTokenToStatement(ast);
630                        gst = EFindSqlStateType.stsql;
631                    } else if ((ast.tokentype == ETokenType.ttsemicolon)) {
632                        addTokenToStatement(ast);
633                        onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
634                        gst = EFindSqlStateType.stnormal;
635                    } else {
636                        addTokenToStatement(ast);
637                    }
638                    break;
639                }
640                case stnormal: {
641                    if ((ast.tokencode == TBaseType.cmtdoublehyphen)
642                            || (ast.tokencode == TBaseType.cmtslashstar)
643                            || (ast.tokencode == TBaseType.lexspace)
644                            || (ast.tokencode == TBaseType.lexnewline)
645                            || (ast.tokentype == ETokenType.ttsemicolon)) {
646                        if (TBaseType.assigned(gcurrentsqlstatement)) {
647                            addTokenToStatement(ast);
648                        }
649
650                        if (TBaseType.assigned(lcprevsolidtoken) && (ast.tokentype == ETokenType.ttsemicolon)) {
651                            if (lcprevsolidtoken.tokentype == ETokenType.ttsemicolon) {
652                                ast.tokentype = ETokenType.ttsimplecomment;
653                                ast.tokencode = TBaseType.cmtdoublehyphen;
654                            }
655                        }
656
657                        continue;
658                    }
659
660                    gcurrentsqlstatement = lcnextsqlstatement;
661
662                    if (TBaseType.assigned(gcurrentsqlstatement)) {
663                        switch (gcurrentsqlstatement.sqlstatementtype) {
664                            case sstmssqlcreateprocedure:
665                            case sstmssqlcreatefunction:
666                            case sstcreatetrigger:
667                            case sstmssqlalterprocedure:
668                            case sstmssqlalterfunction:
669                            case sstmssqlaltertrigger: {
670                                addTokenToStatement(ast);
671                                gst = EFindSqlStateType.ststoredprocedure;
672                                break;
673                            }
674                            case sstmssqlbegintry:
675                            case sstmssqlbegincatch: {
676                                addTokenToStatement(ast);
677                                gst = EFindSqlStateType.sttrycatch;
678                                lctrycatchlevel = 0;
679                                break;
680                            }
681                            case sstmssqlgo: {
682                                addTokenToStatement(ast);
683                                onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
684                                gst = EFindSqlStateType.stnormal;
685                                break;
686                            }
687                            default: {
688                                addTokenToStatement(ast);
689                                if (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqllabel) {
690                                    onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
691                                    gst = EFindSqlStateType.stnormal;
692                                } else {
693                                    gst = EFindSqlStateType.stsql;
694                                }
695                                break;
696                            }
697                        }
698                    } else {
699                        if (ast.tokencode == TBaseType.rrw_begin) {
700                            gcurrentsqlstatement = new TMssqlBlock(vendor);
701                            gcurrentsqlstatement.sqlstatementtype = ESqlStatementType.sstmssqlblock;
702                            addTokenToStatement(ast);
703                            gst = EFindSqlStateType.stblock;
704                        } else {
705                            if (sqlstatements.size() == 0) {
706                                gst = EFindSqlStateType.stsql;
707                                gcurrentsqlstatement = new TMssqlExecute(vendor);
708                                ast.tokencode = TBaseType.rrw_sybase_exce_proc_name;
709                                addTokenToStatement(ast);
710                            } else if (sqlstatements.get(sqlstatements.size() - 1).sqlstatementtype == ESqlStatementType.sstmssqlgo) {
711                                gst = EFindSqlStateType.stsql;
712                                gcurrentsqlstatement = new TMssqlExecute(vendor);
713                                ast.tokencode = TBaseType.rrw_sybase_exce_proc_name;
714                                addTokenToStatement(ast);
715                            }
716                        }
717                    }
718
719                    if (!TBaseType.assigned(gcurrentsqlstatement)) {
720                        this.syntaxErrors.add(new TSyntaxError(ast.getAstext(), ast.lineNo, (ast.columnNo < 0 ? 0 : ast.columnNo)
721                                , "Error when tokenlize", EErrorType.spwarning, TBaseType.MSG_WARNING_ERROR_WHEN_TOKENIZE, null, ast.posinlist));
722
723                        ast.tokentype = ETokenType.tttokenlizererrortoken;
724                        gst = EFindSqlStateType.sterror;
725
726                        gcurrentsqlstatement = new TUnknownSqlStatement(vendor);
727                        gcurrentsqlstatement.sqlstatementtype = ESqlStatementType.sstinvalid;
728                        addTokenToStatement(ast);
729                    }
730                    break;
731                }
732                case stblock: {
733                    if (TBaseType.assigned(lcnextsqlstatement)) {
734                        if (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlgo) {
735                            onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
736                            gcurrentsqlstatement = lcnextsqlstatement;
737                            addTokenToStatement(ast);
738                            onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
739                            gst = EFindSqlStateType.stnormal;
740                        } else {
741                            lcnextsqlstatement = null;
742                        }
743                    }
744
745                    if (gst == EFindSqlStateType.stblock) {
746                        addTokenToStatement(ast);
747                        if (ast.tokencode == TBaseType.rrw_begin) {
748                            lcnextsolidtoken = sourcetokenlist.nextsolidtoken(i, 1, false);
749                            if (TBaseType.assigned(lcnextsolidtoken)) {
750                                if (!(TBaseType.mysametext(lcnextsolidtoken.getAstext(), "tran")
751                                        || TBaseType.mysametext(lcnextsolidtoken.getAstext(), "transaction")
752                                        || TBaseType.mysametext(lcnextsolidtoken.getAstext(), "distributed")
753                                        || TBaseType.mysametext(lcnextsolidtoken.getAstext(), "dialog")
754                                        || TBaseType.mysametext(lcnextsolidtoken.getAstext(), "conversation")
755                                ))
756                                    lcblocklevel++;
757                            } else
758                                lcblocklevel++;
759
760                        } else if (ast.tokencode == TBaseType.rrw_case) {
761                            // The CASE of `END CASE` ends a Watcom CASE statement (Mantis 4717): its END already
762                            // closed the level that statement's CASE opened, so it opens nothing. Only an END
763                            // that closed a CASE counts; after the END of a BEGIN block, CASE opens a level.
764                            if (!(TBaseType.assigned(lcprevsolidtoken) && lcprevsolidtoken == lcendofcase)) {
765                                lcblocklevel++;
766                                lccaseopenedlevels.set(lcblocklevel);
767                            }
768                        } else if (ast.tokencode == TBaseType.rrw_when) {
769                            // Each WHEN expects a matching THEN (CASE, MERGE,
770                            // EXCEPTION). Track so that THEN does not get
771                            // confused with the Watcom IF...THEN opener.
772                            lcwhenPending++;
773                        } else if (ast.tokencode == TBaseType.rrw_then) {
774                            if (lcwhenPending > 0)
775                                lcwhenPending--;
776                            else
777                                lcwatcomIfNest++;
778                        } else if (ast.tokencode == flexer.getkeywordvalue("ENDIF")) {
779                            // One-word ENDIF closes a Watcom IF if one is open.
780                            if (lcwatcomIfNest > 0)
781                                lcwatcomIfNest--;
782                        } else if (ast.tokencode == TBaseType.rrw_end) {
783
784                            lcisendconversation = false;
785                            // Watcom/SQL Anywhere/Sybase IQ: `END IF` closes a
786                            // Watcom IF statement only when there is an open
787                            // IF...THEN to match. Without that match, `END`
788                            // followed by `IF` is just a block-close followed
789                            // by a new T-SQL `IF` statement (semicolons are
790                            // optional in Sybase/T-SQL), so we must still
791                            // decrement the block level normally.
792                            boolean lcisendif = false;
793
794                            lcnextsolidtoken = sourcetokenlist.nextsolidtoken(i, 1, false);
795                            if (TBaseType.assigned(lcnextsolidtoken)) {
796                                if (lcnextsolidtoken.tokencode == flexer.getkeywordvalue("CONVERSATION"))
797                                    lcisendconversation = true;
798                                else if (lcnextsolidtoken.tokencode == TBaseType.rrw_if
799                                        && lcwatcomIfNest > 0) {
800                                    lcisendif = true;
801                                    lcwatcomIfNest--;
802                                }
803                            }
804
805                            if (!lcisendconversation && !lcisendif) {
806
807                                if (lcblocklevel == 0) {
808                                    if (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlif) {
809                                        if (TBaseType.assigned(lcnextsolidtoken)) {
810                                            if (lcnextsolidtoken.tokencode == TBaseType.rrw_else) {
811                                                gst = EFindSqlStateType.stsql;
812                                            } else if (lcnextsolidtoken.tokentype == ETokenType.ttsemicolon) {
813                                                lcnnextsolidtoken = sourcetokenlist.nextsolidtoken(lcnextsolidtoken.posinlist, 1, false);
814                                                if (TBaseType.assigned(lcnnextsolidtoken)) {
815                                                    if (lcnnextsolidtoken.tokencode == TBaseType.rrw_else) {
816                                                        gst = EFindSqlStateType.stsql;
817                                                    }
818                                                }
819                                            }
820                                        }
821                                    }
822
823                                    if (gst != EFindSqlStateType.stsql) {
824                                        onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
825                                        gst = EFindSqlStateType.stnormal;
826                                    }
827
828                                } else {
829                                    if (lccaseopenedlevels.get(lcblocklevel)) {
830                                        lccaseopenedlevels.clear(lcblocklevel);
831                                        lcendofcase = ast;
832                                    }
833                                    lcblocklevel--;
834                                }
835
836                            }
837                        }
838                    }
839                    break;
840                }
841                case sttrycatch: {
842
843                    if (TBaseType.assigned(lcnextsqlstatement)) {
844                        if (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlgo) {
845                            onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
846                            gcurrentsqlstatement = lcnextsqlstatement;
847                            addTokenToStatement(ast);
848                            onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
849                            gst = EFindSqlStateType.stnormal;
850                        } else {
851                            if (
852                                    (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlbegintry)
853                                            || (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlbegincatch)
854                            )
855                                lctrycatchlevel++;
856                            lcnextsqlstatement = null;
857                        }
858                    }
859
860                    if (gst == EFindSqlStateType.sttrycatch) {
861                        addTokenToStatement(ast);
862                        if ((ast.tokencode == TBaseType.rrw_try) ||
863                                (ast.tokencode == TBaseType.rrw_catch)) {
864                            if (TBaseType.assigned(lcprevsolidtoken)) {
865                                if (lcprevsolidtoken.tokencode == TBaseType.rrw_end) {
866                                    if (lctrycatchlevel == 0) {
867                                        onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
868                                        gst = EFindSqlStateType.stnormal;
869                                    } else
870                                        lctrycatchlevel--;
871                                }
872                            }
873                        }
874                    }
875                    break;
876                }
877                case stsql: {
878                    if ((ast.tokentype == ETokenType.ttsemicolon)) {
879                        lcstillinsql = false;
880                        if (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlif) {
881                            lcnextsolidtoken = sourcetokenlist.nextsolidtoken(i, 1, false);
882                            if (TBaseType.assigned(lcnextsolidtoken)) {
883                                if (lcnextsolidtoken.tokencode == TBaseType.rrw_else) {
884                                    addTokenToStatement(ast);
885                                    lcstillinsql = true;
886                                }
887
888                            }
889                        }
890
891                        if (!lcstillinsql) {
892                            gst = EFindSqlStateType.stnormal;
893                            addTokenToStatement(ast);
894                            gcurrentsqlstatement.semicolonended = ast;
895                            onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
896                        }
897
898                    } else if (TBaseType.assigned(lcnextsqlstatement)) {
899
900                        if (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlgo) {
901                            onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
902                            gcurrentsqlstatement = lcnextsqlstatement;
903                            addTokenToStatement(ast);
904                            onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
905                            gst = EFindSqlStateType.stnormal;
906                            continue;
907                        }
908
909                        switch (gcurrentsqlstatement.sqlstatementtype) {
910                            case sstmssqlif:
911                            case sstmssqlwhile: {
912                                if ((lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlbegincatch)
913                                        || (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlbegintry)) {
914                                    addTokenToStatement(ast);
915                                    gst = EFindSqlStateType.stblock;
916                                    lcblocklevel = 1;
917                                    lccaseopenedlevels.clear();
918                                    lcnextsqlstatement = null;
919                                    continue;
920
921                                } else if (gcurrentsqlstatement.dummytag == 1) {
922                                    addTokenToStatement(ast);
923
924                                    if ((lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlif)
925                                            || (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlwhile))
926                                        gcurrentsqlstatement.dummytag = 1;
927                                    else
928                                        gcurrentsqlstatement.dummytag = 0;
929
930
931                                    lcnextsqlstatement = null;
932                                    continue;
933                                }
934                                break;
935                            }
936                            case sstmssqlalterqueue: {
937                                if (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlexec) {
938                                    addTokenToStatement(ast);
939
940                                    lcnextsqlstatement = null;
941                                    continue;
942
943                                }
944                                break;
945                            }
946                            case sstmssqlcreateschema: {
947                                addTokenToStatement(ast);
948                                lcnextsqlstatement = null;
949                                continue;
950                            }
951                        }
952
953                        onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
954                        gcurrentsqlstatement = lcnextsqlstatement;
955                        addTokenToStatement(ast);
956
957                        switch (gcurrentsqlstatement.sqlstatementtype) {
958                            case sstmssqlcreateprocedure:
959                            case sstmssqlcreatefunction:
960                            case sstcreatetrigger:
961                            case sstmssqlalterprocedure:
962                            case sstmssqlalterfunction:
963                            case sstmssqlaltertrigger: {
964                                gst = EFindSqlStateType.ststoredprocedure;
965                                break;
966                            }
967                            case sstmssqlbegintry:
968                            case sstmssqlbegincatch: {
969                                gst = EFindSqlStateType.sttrycatch;
970                                lctrycatchlevel = 0;
971                                break;
972                            }
973                            case sstmssqlgo: {
974                                gst = EFindSqlStateType.stnormal;
975                                break;
976                            }
977                            default: {
978                                gst = EFindSqlStateType.stsql;
979                                break;
980                            }
981                        }
982
983                    } else if ((ast.tokencode == TBaseType.rrw_begin)) {
984                        if ((gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlif)
985                                || (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlwhile)) {
986                            gst = EFindSqlStateType.stblock;
987                            lcblocklevel = 0;
988                            lccaseopenedlevels.clear();
989                            addTokenToStatement(ast);
990                        } else if (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqldeclare) {
991                            onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
992                            gcurrentsqlstatement = new TMssqlBlock(vendor);
993                            gcurrentsqlstatement.sqlstatementtype = ESqlStatementType.sstmssqlblock;
994                            addTokenToStatement(ast);
995                            gst = EFindSqlStateType.stblock;
996                        } else {
997                            addTokenToStatement(ast);
998                        }
999                    } else if ((ast.tokencode == TBaseType.rrw_case)) {
1000                        // The CASE of `END CASE` ends a Watcom CASE statement (Mantis 4717); it opens nothing.
1001                        if (!(TBaseType.assigned(lcprevsolidtoken) && lcprevsolidtoken == lcendofcase))
1002                            case_end_nest++;
1003                        addTokenToStatement(ast);
1004                    } else if ((ast.tokencode == TBaseType.rrw_end)) {
1005                        if (case_end_nest > 0) {
1006                            case_end_nest--;
1007                            lcendofcase = ast;
1008                        }
1009                        addTokenToStatement(ast);
1010                    } else if ((ast.tokencode == TBaseType.rrw_else)) {
1011                        addTokenToStatement(ast);
1012                        if (((gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlif)
1013                                || (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlwhile)
1014                        ) && (case_end_nest == 0))
1015                            gcurrentsqlstatement.dummytag = 1;
1016                    } else {
1017                        addTokenToStatement(ast);
1018                        if (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlset) {
1019                            if (ast.tokencode == TBaseType.rrw_on) {
1020                                gst = EFindSqlStateType.stnormal;
1021                                onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
1022                            }
1023                        }
1024                    }
1025                    break;
1026                }
1027                case ststoredprocedure: {
1028                    if (TBaseType.assigned(lcnextsqlstatement)) {
1029                        if (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlgo) {
1030                            onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
1031                            gcurrentsqlstatement = lcnextsqlstatement;
1032                            addTokenToStatement(ast);
1033                            onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
1034                            gst = EFindSqlStateType.stnormal;
1035                        } else {
1036                            gst = EFindSqlStateType.ststoredprocedurebody;
1037                            addTokenToStatement(ast);
1038
1039                            lcnextsqlstatement = null;
1040                        }
1041                    }
1042
1043                    if (gst == EFindSqlStateType.ststoredprocedure) {
1044                        addTokenToStatement(ast);
1045                        if (ast.tokencode == TBaseType.rrw_begin) {
1046                            gst = EFindSqlStateType.stblock;
1047                        }
1048                    }
1049                    break;
1050                }
1051                case ststoredprocedurebody: {
1052                    if (TBaseType.assigned(lcnextsqlstatement)) {
1053                        switch (lcnextsqlstatement.sqlstatementtype) {
1054                            case sstmssqlgo: {
1055                                onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
1056                                gcurrentsqlstatement = lcnextsqlstatement;
1057                                addTokenToStatement(ast);
1058                                onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
1059                                gst = EFindSqlStateType.stnormal;
1060                                break;
1061                            }
1062                            case sstmssqlcreateprocedure:
1063                            case sstmssqlcreatefunction:
1064                            case sstcreatetrigger:
1065                            case sstmssqlalterprocedure:
1066                            case sstmssqlalterfunction:
1067                            case sstmssqlaltertrigger: {
1068                                onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
1069                                gcurrentsqlstatement = lcnextsqlstatement;
1070                                addTokenToStatement(ast);
1071                                gst = EFindSqlStateType.ststoredprocedure;
1072                                break;
1073                            }
1074                            default: {
1075                                lcnextsqlstatement = null;
1076                                break;
1077                            }
1078                        }
1079                    }
1080
1081                    if (gst == EFindSqlStateType.ststoredprocedurebody)
1082                        addTokenToStatement(ast);
1083                }
1084                break;
1085            }
1086        }
1087
1088        // last statement
1089        if (TBaseType.assigned(gcurrentsqlstatement) && (gst != EFindSqlStateType.stnormal)) {
1090            onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, true, builder);
1091        }
1092
1093        builder.sqlStatements(this.sqlstatements);
1094        builder.errorCode(errorcount);
1095        builder.errorMessage(errorcount == 0 ? "" : String.format("Extraction completed with %d error(s)", errorcount));
1096    }
1097}