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