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