001package gudusoft.gsqlparser.parser;
002
003import gudusoft.gsqlparser.EDbVendor;
004import gudusoft.gsqlparser.TBaseType;
005import gudusoft.gsqlparser.TCustomLexer;
006import gudusoft.gsqlparser.TCustomParser;
007import gudusoft.gsqlparser.TCustomSqlStatement;
008import gudusoft.gsqlparser.TLexerInformix;
009import gudusoft.gsqlparser.TParserInformix;
010import gudusoft.gsqlparser.TSourceToken;
011import gudusoft.gsqlparser.TSourceTokenList;
012import gudusoft.gsqlparser.TStatementList;
013import gudusoft.gsqlparser.TSyntaxError;
014import gudusoft.gsqlparser.EFindSqlStateType;
015import gudusoft.gsqlparser.ETokenType;
016import gudusoft.gsqlparser.ETokenStatus;
017import gudusoft.gsqlparser.ESqlStatementType;
018import gudusoft.gsqlparser.EErrorType;
019import gudusoft.gsqlparser.stmt.TUnknownSqlStatement;
020import gudusoft.gsqlparser.stmt.mssql.TMssqlBlock;
021import gudusoft.gsqlparser.stmt.mssql.TMssqlExecute;
022import gudusoft.gsqlparser.sqlcmds.ISqlCmds;
023import gudusoft.gsqlparser.sqlcmds.SqlCmdsFactory;
024import gudusoft.gsqlparser.compiler.TContext;
025import gudusoft.gsqlparser.sqlenv.TSQLEnv;
026import gudusoft.gsqlparser.compiler.TGlobalScope;
027import gudusoft.gsqlparser.compiler.TFrame;
028import gudusoft.gsqlparser.resolver.TSQLResolver;
029import gudusoft.gsqlparser.TLog;
030import gudusoft.gsqlparser.compiler.TASTEvaluator;
031
032import java.io.BufferedReader;
033import java.util.ArrayList;
034import java.util.List;
035import java.util.Stack;
036
037/**
038 * IBM Informix SQL parser implementation.
039 *
040 * <p>This parser handles Informix-specific SQL syntax including:
041 * <ul>
042 *   <li>Informix stored procedures and functions</li>
043 *   <li>Informix triggers</li>
044 *   <li>Informix execute statements</li>
045 *   <li>Special token handling (CONNECT TO, LOCK TABLE, etc.)</li>
046 * </ul>
047 *
048 * <p><b>Design Notes:</b>
049 * <ul>
050 *   <li>Extends {@link AbstractSqlParser} using the template method pattern</li>
051 *   <li>Uses {@link TLexerInformix} for tokenization</li>
052 *   <li>Uses {@link TParserInformix} for parsing</li>
053 *   <li>Delimiter character: ';' for SQL statements</li>
054 * </ul>
055 *
056 * <p><b>Usage Example:</b>
057 * <pre>
058 * // Get Informix parser from factory
059 * SqlParser parser = SqlParserFactory.get(EDbVendor.dbvinformix);
060 *
061 * // Build context
062 * ParserContext context = new ParserContext.Builder(EDbVendor.dbvinformix)
063 *     .sqlText("SELECT * FROM customer WHERE customer_num = 101")
064 *     .build();
065 *
066 * // Parse
067 * SqlParseResult result = parser.parse(context);
068 *
069 * // Access statements
070 * TStatementList statements = result.getSqlStatements();
071 * </pre>
072 *
073 * @see SqlParser
074 * @see AbstractSqlParser
075 * @see TLexerInformix
076 * @see TParserInformix
077 * @since 3.2.0.0
078 */
079public class InformixSqlParser extends AbstractSqlParser {
080
081    /**
082     * Construct Informix SQL parser.
083     * <p>
084     * Configures the parser for Informix database with default delimiter (;).
085     * <p>
086     * Following the original TGSqlParser pattern, the lexer and parser are
087     * created once in the constructor and reused for all parsing operations.
088     */
089    public InformixSqlParser() {
090        super(EDbVendor.dbvinformix);
091        this.delimiterChar = ';';
092        this.defaultDelimiterStr = ";";
093
094        // Create lexer once - will be reused for all parsing operations
095        this.flexer = new TLexerInformix();
096        this.flexer.delimiterchar = this.delimiterChar;
097        this.flexer.defaultDelimiterStr = this.defaultDelimiterStr;
098
099        // Set parent's lexer reference for shared tokenization logic
100        this.lexer = this.flexer;
101
102        // Create parser once - will be reused for all parsing operations
103        this.fparser = new TParserInformix(null);
104        this.fparser.lexer = this.flexer;
105    }
106
107    // ========== Parser Components ==========
108
109    /** The Informix lexer used for tokenization */
110    public TLexerInformix flexer;
111
112    /** Informix SQL parser */
113    private TParserInformix fparser;
114
115    /** Current statement being built during extraction */
116    private TCustomSqlStatement gcurrentsqlstatement;
117
118    // Note: Global context and frame stack fields inherited from AbstractSqlParser:
119    // - protected TContext globalContext
120    // - protected TSQLEnv sqlEnv
121    // - protected Stack<TFrame> frameStack
122    // - protected TFrame globalFrame
123
124    // ========== AbstractSqlParser Abstract Methods Implementation ==========
125
126    /**
127     * Return the Informix lexer instance.
128     */
129    @Override
130    protected TCustomLexer getLexer(ParserContext context) {
131        return this.flexer;
132    }
133
134    /**
135     * Return the Informix SQL parser instance with updated token list.
136     */
137    @Override
138    protected TCustomParser getParser(ParserContext context, TSourceTokenList tokens) {
139        this.fparser.sourcetokenlist = tokens;
140        return this.fparser;
141    }
142
143    /**
144     * Call Informix-specific tokenization logic.
145     * <p>
146     * Delegates to doinformixtexttotokenlist which handles Informix's
147     * special token transformations and statement recognition.
148     */
149    @Override
150    protected void tokenizeVendorSql() {
151        doinformixtexttotokenlist();
152    }
153
154    /**
155     * Setup parsers for raw statement extraction.
156     * <p>
157     * Informix uses a single parser (no secondary parser like Oracle).
158     */
159    @Override
160    protected void setupVendorParsersForExtraction() {
161        this.fparser.sqlcmds = this.sqlcmds;
162        this.fparser.sourcetokenlist = this.sourcetokenlist;
163    }
164
165    /**
166     * Call Informix-specific raw statement extraction logic.
167     * <p>
168     * Delegates to doinformixgetrawsqlstatements which handles Informix's
169     * statement boundary detection and stored procedure recognition.
170     */
171    @Override
172    protected void extractVendorRawStatements(SqlParseResult.Builder builder) {
173        doinformixgetrawsqlstatements(builder);
174    }
175
176    /**
177     * No secondary parser for Informix (only Oracle has dual parsers).
178     */
179    @Override
180    protected TCustomParser getSecondaryParser(ParserContext context, TSourceTokenList tokens) {
181        return null;
182    }
183
184    // ========== Informix-Specific Tokenization ==========
185
186    /**
187     * Tokenize Informix SQL text to token list.
188     * <p>
189     * Migrated from TGSqlParser.doinformixtexttotokenlist().
190     * <p>
191     * Handles Informix-specific token transformations:
192     * <ul>
193     *   <li>OPENROWSET special handling</li>
194     *   <li>Keyword token adjustments (PRIMARY, FOREIGN, UNIQUE, DISTINCT)</li>
195     *   <li>Semicolon type detection (after BEGIN, in OPENROWSET)</li>
196     *   <li>GO command recognition</li>
197     *   <li>LOCK TABLE handling</li>
198     *   <li>CONNECT TO statement recognition</li>
199     * </ul>
200     */
201    private void doinformixtexttotokenlist() {
202        TSourceToken lcprevtoken = null;
203        int lcsteps = 0;
204        TSourceToken asourcetoken, lctoken, lctoken2, lctoken3;
205        int yychar;
206        boolean iskeywordgo;
207
208        asourcetoken = getanewsourcetoken();
209
210        if (asourcetoken == null) return;
211
212        yychar = asourcetoken.tokencode;
213
214        boolean lcinopenrowset = false;
215        int lcnested = 0;
216
217        while (yychar > 0) {
218
219            if (asourcetoken.tokencode == TBaseType.rrw_openrowset) {
220                // openrowset(....)
221                lcinopenrowset = true;
222                lcnested = 0;
223            } else if (asourcetoken.tokentype == ETokenType.ttleftparenthesis) {
224                if ((lcsteps > 0) && TBaseType.assigned(lcprevtoken)) {
225                    if (lcprevtoken.tokencode == TBaseType.rrw_primary) {
226                        lcprevtoken.tokencode = TBaseType.rrw_select - 2;  //rw_primary2
227                        //checkconstarinttoken(lcprevtoken);
228                    } else if (lcprevtoken.tokencode == TBaseType.rrw_foreign) {
229                        lcprevtoken.tokencode = TBaseType.rrw_select - 4;  //rw_foreign2
230                        //checkconstarinttoken(lcprevtoken);
231                    } else if (lcprevtoken.tokencode == TBaseType.rrw_unique) {
232                        lcprevtoken.tokencode = TBaseType.rrw_select - 1;  //rw_unique2
233                        // checkconstarinttoken(lcprevtoken);
234                    } else if (lcprevtoken.tokencode == TBaseType.rrw_distinct) {
235                        lcprevtoken.tokencode = TBaseType.rrw_select - 6;  //rw_distinct2
236                        //checkconstarinttoken(lcprevtoken);
237                    }
238                    lcprevtoken = null;
239                    lcsteps = 0;
240                }
241
242                // openrowset(....)
243                if (lcinopenrowset)
244                    lcnested++;
245            } else if (asourcetoken.tokentype == ETokenType.ttrightparenthesis) {
246                // openrowset(....)
247                if (lcinopenrowset) {
248                    if ((lcnested > 0))
249                        lcnested--;
250                    if (lcnested == 0)
251                        lcinopenrowset = false;
252                }
253            } else if (asourcetoken.tokentype == ETokenType.ttsemicolon) {
254                if (lcinopenrowset) {
255                    asourcetoken.tokentype = ETokenType.ttsemicolon2;
256                } else {
257                    lctoken2 = asourcetoken.searchToken(TBaseType.rrw_begin, -1);
258                    if (lctoken2 != null) {
259                        asourcetoken.tokencode = TBaseType.SEMI_COLON_AFTER_BEGIN;
260                        asourcetoken.tokentype = ETokenType.ttsemicolon3;
261                    } else {
262                        lctoken2 = asourcetoken.searchToken(TBaseType.rrw_try, -1);
263                        if (lctoken2 == null) {
264                            lctoken2 = asourcetoken.searchToken(TBaseType.rrw_catch, -1);
265                        }
266                        if (lctoken2 != null) {
267                            lctoken3 = asourcetoken.searchToken(TBaseType.rrw_begin, -2);
268                            if (lctoken3 != null) {
269                                asourcetoken.tokencode = TBaseType.SEMI_COLON_AFTER_BEGIN;
270                                asourcetoken.tokentype = ETokenType.ttsemicolon3;
271                            }
272                        }
273                    }
274                }
275            } else if (asourcetoken.tokentype == ETokenType.ttperiod) {
276                lctoken = getprevtoken(asourcetoken);
277                //  System.out.println(lctoken);
278                if (TBaseType.assigned(lctoken)) {
279                    // go.fieldname, go is a table alias, not a go statement
280                    if (lctoken.tokencode == TBaseType.rrw_go) {
281                        lctoken.tokencode = TBaseType.ident;
282                        lctoken.tokentype = ETokenType.ttidentifier;
283                    }
284                }
285            } else if (asourcetoken.tokencode == TBaseType.rrw_table) {
286                lctoken = getprevtoken(asourcetoken);
287                if (TBaseType.assigned(lctoken)) {
288                    if (lctoken.tokencode == TBaseType.rrw_lock) {
289                        lctoken.tokencode = TBaseType.rw_locktable; //TBaseType.rw_locktable
290                    }
291                }
292            } else if (asourcetoken.tokencode == TBaseType.rrw_to) {
293                lctoken = getprevtoken(asourcetoken);
294                if (TBaseType.assigned(lctoken)) {
295                    if (lctoken.tokencode == TBaseType.rrw_connect) {
296                        lctoken.tokencode = TBaseType.rrw_informix_connect_to;//connect to statement
297                    }
298                }
299            } else if (asourcetoken.tokencode == TBaseType.rrw_go) {
300                iskeywordgo = true;
301                lctoken = getprevtoken(asourcetoken);
302                if (TBaseType.assigned(lctoken)) {
303                    // go should not at same line as other sql statement
304                    if (lctoken.lineNo == asourcetoken.lineNo) {
305                        iskeywordgo = false;
306                    }
307                }
308
309                if (iskeywordgo) {
310                    lcinopenrowset = false;
311                    lcnested = 0;
312                    lcprevtoken = asourcetoken;
313                } else {
314                    //  System.out.println(asourcetoken);
315                    asourcetoken.tokencode = TBaseType.ident;
316                    asourcetoken.tokentype = ETokenType.ttidentifier;
317                }
318            } else if (asourcetoken.tokencode == TBaseType.rrw_primary) {
319                // primary key [clustered | nonclustered ] ( column list)
320                lcsteps = 2;
321                lcprevtoken = asourcetoken;
322            } else if (asourcetoken.tokencode == TBaseType.rrw_foreign) {
323                // foreign key [clustered | nonclustered ] ( column list)
324                lcsteps = 2;
325                lcprevtoken = asourcetoken;
326            } else if (asourcetoken.tokencode == TBaseType.rrw_unique) {
327                // unique [clustered | nonclustered ] ( column list)
328                lcsteps = 1;
329                lcprevtoken = asourcetoken;
330            } else if (asourcetoken.issolidtoken()) {
331                if (lcsteps > 0) {
332                    if (!(TBaseType.mysametext("clustered", asourcetoken.toString())
333                            || TBaseType.mysametext("nonclustered", asourcetoken.toString())))
334                        lcsteps--;
335                }
336            }
337
338            //System.out.println(asourcetoken);
339            sourcetokenlist.add(asourcetoken);
340
341            //flexer.yylexwrap(asourcetoken);
342            asourcetoken = getanewsourcetoken();
343            if (asourcetoken != null) {
344                yychar = asourcetoken.tokencode;
345            } else {
346                yychar = 0;
347            }
348
349        }
350
351    }
352
353    /**
354     * Get previous solid token (non-whitespace, non-comment).
355     * <p>
356     * Helper method migrated from TGSqlParser for token navigation.
357     */
358    private TSourceToken getprevtoken(TSourceToken ast) {
359        int i = ast.posinlist;
360        TSourceToken lctoken;
361
362        for (int j = i - 1; j >= 0; j--) {
363            lctoken = sourcetokenlist.get(j);
364            if (lctoken.issolidtoken())
365                return lctoken;
366        }
367        return null;
368    }
369
370    // ========== Informix-Specific Raw Statement Extraction ==========
371
372    /**
373     * Extract raw SQL statements for Informix.
374     * <p>
375     * Migrated from TGSqlParser.doinformixgetrawsqlstatements().
376     * <p>
377     * Handles Informix-specific statement boundaries and nested constructs:
378     * <ul>
379     *   <li>Stored procedures (CREATE/ALTER PROCEDURE, FUNCTION)</li>
380     *   <li>Triggers</li>
381     *   <li>EXECUTE statements</li>
382     *   <li>BEGIN/END blocks</li>
383     *   <li>TRY/CATCH blocks (MSSQL compatibility)</li>
384     *   <li>GO command (batch separator)</li>
385     * </ul>
386     */
387    private int doinformixgetrawsqlstatements(SqlParseResult.Builder builder) {
388        int errorcount = 0;
389        int case_end_nest = 0;
390
391        if (TBaseType.assigned(sqlstatements)) sqlstatements.clear();
392        if (!TBaseType.assigned(sourcetokenlist)) return -1;
393
394        gcurrentsqlstatement = null;
395        EFindSqlStateType gst = EFindSqlStateType.stnormal;
396        int lcblocklevel = 0;
397        int lctrycatchlevel = 0;
398        TSourceToken lcprevsolidtoken = null, lcnextsolidtoken, lcnnextsolidtoken;
399        TSourceToken ast = null;
400        int i;
401        boolean lcisendconversation, lcstillinsql;
402
403        for (i = 0; i < sourcetokenlist.size(); i++) {
404
405            if ((ast != null) && (ast.issolidtoken()))
406                lcprevsolidtoken = ast;
407
408            ast = sourcetokenlist.get(i);
409            sourcetokenlist.curpos = i;
410            if (ast.tokenstatus == ETokenStatus.tsignoredbygetrawstatement) {
411                //tsignoredbygetrawstatement is set when cte is found in dbcmds.findcte function
412                gcurrentsqlstatement.sourcetokenlist.add(ast);
413                continue;
414            }
415
416            if (gst == EFindSqlStateType.ststoredprocedurebody) {
417                if (!(
418                        (ast.tokencode == TBaseType.rrw_go)
419                                || (ast.tokencode == TBaseType.rrw_create)
420                                || (ast.tokencode == TBaseType.rrw_alter))
421                ) {
422                    gcurrentsqlstatement.sourcetokenlist.add(ast);
423                    continue;
424                }
425            }
426
427            TCustomSqlStatement lcnextsqlstatement = sqlcmds.issql(ast, gst, gcurrentsqlstatement);
428
429            switch (gst) {
430                case sterror: {
431                    if (TBaseType.assigned(lcnextsqlstatement)) {
432                        onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
433                        gcurrentsqlstatement = lcnextsqlstatement;
434                        gcurrentsqlstatement.sourcetokenlist.add(ast);
435                        gst = EFindSqlStateType.stsql;
436                    } else if ((ast.tokentype == ETokenType.ttsemicolon)) {
437                        gcurrentsqlstatement.sourcetokenlist.add(ast);
438                        onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
439                        gst = EFindSqlStateType.stnormal;
440                    } else {
441                        gcurrentsqlstatement.sourcetokenlist.add(ast);
442                    }
443                    break;
444                }
445                case stnormal: {
446                    if ((ast.tokencode == TBaseType.cmtdoublehyphen)
447                            || (ast.tokencode == TBaseType.cmtslashstar)
448                            || (ast.tokencode == TBaseType.lexspace)
449                            || (ast.tokencode == TBaseType.lexnewline)
450                            || (ast.tokentype == ETokenType.ttsemicolon)) {
451                        if (TBaseType.assigned(gcurrentsqlstatement)) {
452                            gcurrentsqlstatement.sourcetokenlist.add(ast);
453                        }
454
455                        if (TBaseType.assigned(lcprevsolidtoken) && (ast.tokentype == ETokenType.ttsemicolon)) {
456                            if (lcprevsolidtoken.tokentype == ETokenType.ttsemicolon) {
457                                // ;;;; continuous semicolon,treat it as comment
458                                ast.tokentype = ETokenType.ttsimplecomment;
459                                ast.tokencode = TBaseType.cmtdoublehyphen;
460                            } else {
461                            }
462
463                        }
464
465                        continue;
466                    }
467
468                    gcurrentsqlstatement = lcnextsqlstatement; //isstoredprocedure(ast,dbvendor,gst,sqlstatements);
469
470                    if (TBaseType.assigned(gcurrentsqlstatement)) {
471                        switch (gcurrentsqlstatement.sqlstatementtype) {    //
472                            case sstinformixCreateProcedure:
473                            case sstinformixCreateFunction:
474                            case sstcreatetrigger:
475                            case sstinformixAlterProcedure:
476                            case sstinformixAlterFunction: {
477                                gcurrentsqlstatement.sourcetokenlist.add(ast);
478                                gst = EFindSqlStateType.ststoredprocedure;
479                                break;
480                            }
481                            case sstmssqlbegintry:
482                            case sstmssqlbegincatch: {
483                                gcurrentsqlstatement.sourcetokenlist.add(ast);
484                                gst = EFindSqlStateType.sttrycatch;
485                                lctrycatchlevel = 0;
486                                break;
487                            }
488                            case sstmssqlgo: {
489                                gcurrentsqlstatement.sourcetokenlist.add(ast);
490                                onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
491                                gst = EFindSqlStateType.stnormal;
492                                break;
493                            }
494                            case sstinformixExecute: {
495                                // the EXECUTE keyword opens the statement, as in every other case
496                                gcurrentsqlstatement.sourcetokenlist.add(ast);
497                                gst = EFindSqlStateType.stExec;
498                                break;
499                            }
500                            default: {
501                                gcurrentsqlstatement.sourcetokenlist.add(ast);
502                                gst = EFindSqlStateType.stsql;
503                                break;
504                            }
505                        }    // case
506                    } else {
507                        if (ast.tokencode == TBaseType.rrw_begin) {
508                            gcurrentsqlstatement = new TMssqlBlock(vendor);
509                            gcurrentsqlstatement.sqlstatementtype = ESqlStatementType.sstmssqlblock;
510                            gcurrentsqlstatement.sourcetokenlist.add(ast);
511                            gst = EFindSqlStateType.stblock;
512                        } else {
513                            if (sqlstatements.size() == 0) {
514                                //first statement of mssql batch, treat it as exec sp
515                                gst = EFindSqlStateType.stsql;
516                                gcurrentsqlstatement = new TMssqlExecute(vendor);
517                                //tmssqlexecute(gcurrentsqlstatement).exectype = metnoexeckeyword;
518// todo need previous line need to be implemented
519                                gcurrentsqlstatement.sourcetokenlist.add(ast);
520                            } else if (sqlstatements.get(sqlstatements.size() - 1).sqlstatementtype == ESqlStatementType.sstmssqlgo) {
521                                // prev sql is go, treat it as exec sp
522                                gst = EFindSqlStateType.stsql;
523                                gcurrentsqlstatement = new TMssqlExecute(vendor);
524                                //todo need to be implemented: tmssqlexecute(gcurrentsqlstatement).exectype = metnoexeckeyword;
525                                gcurrentsqlstatement.sourcetokenlist.add(ast);
526                            } else {
527                            }
528                        }
529                    }
530
531
532                    if (!TBaseType.assigned(gcurrentsqlstatement)) //error tokentext found
533                    {
534
535//                  if ( TBaseType.assigned(ontokenlizertokenerror) )
536//                    ontokenlizertokenerror(self,ast);
537//                  incerrorcount;
538//                  fsyntaxerrors[ferrorcount].hint = 'error when tokenlize';
539//                  fsyntaxerrors[ferrorcount].errortype = spwarning;
540//                  fsyntaxerrors[ferrorcount].tokentext = ast.sourcecode;
541//                  fsyntaxerrors[ferrorcount].lines = ast.lines;
542//                  fsyntaxerrors[ferrorcount].columns = ast.columns;// - length(fsyntaxerrors[ferrorcount].tokentext);
543
544//                  errormessage = "error when tokenlize:"+ast.astext+"("+ast.lineNo +","+ast.columnNo +")";
545//                  errorcount = 1;
546                        this.syntaxErrors.add(new TSyntaxError(ast.getAstext(), ast.lineNo, (ast.columnNo < 0 ? 0 : ast.columnNo)
547                                , "Error when tokenlize", EErrorType.spwarning, TBaseType.MSG_WARNING_ERROR_WHEN_TOKENIZE, null, ast.posinlist));
548
549                        ast.tokentype = ETokenType.tttokenlizererrortoken;
550                        gst = EFindSqlStateType.sterror;
551
552                        gcurrentsqlstatement = new TUnknownSqlStatement(vendor);
553                        gcurrentsqlstatement.sqlstatementtype = ESqlStatementType.sstinvalid;
554                        gcurrentsqlstatement.sourcetokenlist.add(ast);
555                    }
556                    break;
557                }
558                case stExec: {
559                    gcurrentsqlstatement.sourcetokenlist.add(ast);
560                    if (ast.tokentype == ETokenType.ttsemicolon) {
561                        gst = EFindSqlStateType.stnormal;
562                        onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
563                    }
564                    break;
565                }
566                case stblock: {
567                    if (TBaseType.assigned(lcnextsqlstatement)) {
568                        if (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlgo) {
569                            onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
570                            gcurrentsqlstatement = lcnextsqlstatement;
571                            gcurrentsqlstatement.sourcetokenlist.add(ast);
572                            onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
573                            gst = EFindSqlStateType.stnormal;
574                        } else {
575                            lcnextsqlstatement = null;
576                        }
577                    }
578
579                    if (gst == EFindSqlStateType.stblock) {
580                        gcurrentsqlstatement.sourcetokenlist.add(ast);
581                        if (ast.tokencode == TBaseType.rrw_begin) {
582                            // { [distributed] transaxtion/trans statement
583                            // { dialog [ conversation ]
584                            // { conversation timer
585                            //  doesn't start block ({ .. })
586                            lcnextsolidtoken = sourcetokenlist.nextsolidtoken(i, 1, false);
587                            if (TBaseType.assigned(lcnextsolidtoken)) {
588                                if (!(TBaseType.mysametext(lcnextsolidtoken.getAstext(), "tran")
589                                        || TBaseType.mysametext(lcnextsolidtoken.getAstext(), "transaction")
590                                        || TBaseType.mysametext(lcnextsolidtoken.getAstext(), "distributed")
591                                        || TBaseType.mysametext(lcnextsolidtoken.getAstext(), "dialog")
592                                        || TBaseType.mysametext(lcnextsolidtoken.getAstext(), "conversation")
593                                ))
594                                    lcblocklevel++;
595                            } else
596                                lcblocklevel++;
597
598                        } else if (ast.tokencode == TBaseType.rrw_case)  // case ... }
599                            lcblocklevel++;
600                        else if (ast.tokencode == TBaseType.rrw_end) {
601
602                            lcisendconversation = false;
603
604
605                            lcnextsolidtoken = sourcetokenlist.nextsolidtoken(i, 1, false);
606                            if (TBaseType.assigned(lcnextsolidtoken)) {
607                                if (lcnextsolidtoken.tokencode == flexer.getkeywordvalue("conversation".toUpperCase(java.util.Locale.ROOT)))
608                                    lcisendconversation = true; // } conversation statement
609                            }
610
611
612                            if (!lcisendconversation) {
613
614                                if (lcblocklevel == 0) {
615                                    if (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlif) {
616                                        if (TBaseType.assigned(lcnextsolidtoken)) {
617                                            if (lcnextsolidtoken.tokencode == TBaseType.rrw_else) {
618                                                // { .. } else
619                                                gst = EFindSqlStateType.stsql;
620                                            } else if (lcnextsolidtoken.tokentype == ETokenType.ttsemicolon) {
621                                                lcnnextsolidtoken = sourcetokenlist.nextsolidtoken(lcnextsolidtoken.posinlist, 1, false);
622                                                if (TBaseType.assigned(lcnnextsolidtoken)) {
623                                                    if (lcnnextsolidtoken.tokencode == TBaseType.rrw_else) {
624                                                        // { .. } else
625                                                        gst = EFindSqlStateType.stsql;
626                                                    }
627                                                }
628                                            }
629                                        }
630                                    }
631
632                                    if (gst != EFindSqlStateType.stsql) {
633                                        onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
634                                        gst = EFindSqlStateType.stnormal;
635                                    }
636
637                                } else {
638                                    lcblocklevel--;
639                                }
640
641                            }
642                        }
643                    }
644                    break;
645                }
646                case sttrycatch: {
647
648                    if (TBaseType.assigned(lcnextsqlstatement)) {
649                        if (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlgo) {
650                            onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
651                            gcurrentsqlstatement = lcnextsqlstatement;
652                            gcurrentsqlstatement.sourcetokenlist.add(ast);
653                            onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
654                            gst = EFindSqlStateType.stnormal;
655                        } else {
656                            if (
657                                    (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlbegintry)
658                                            || (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlbegincatch)
659                            )
660                                lctrycatchlevel++;
661                            lcnextsqlstatement = null;
662                        }
663                    }
664
665                    if (gst == EFindSqlStateType.sttrycatch) {
666                        gcurrentsqlstatement.sourcetokenlist.add(ast);
667                        if ((ast.tokencode == TBaseType.rrw_try) ||
668                                (ast.tokencode == TBaseType.rrw_catch)) {
669                            // lcprevsolidtoken = sourcetokenlist.solidtokenbefore(i);
670                            if (TBaseType.assigned(lcprevsolidtoken)) {
671                                if (lcprevsolidtoken.tokencode == TBaseType.rrw_end) {
672                                    if (lctrycatchlevel == 0) {
673                                        onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
674                                        gst = EFindSqlStateType.stnormal;
675                                    } else
676                                        lctrycatchlevel--;
677                                }
678                            }
679                        }
680                    }
681                    break;
682                }
683                case stsql: {
684                    if ((ast.tokentype == ETokenType.ttsemicolon)) {
685                        lcstillinsql = false;
686                        if (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlif) {
687                            lcnextsolidtoken = sourcetokenlist.nextsolidtoken(i, 1, false);
688                            if (TBaseType.assigned(lcnextsolidtoken)) {
689                                if (lcnextsolidtoken.tokencode == TBaseType.rrw_else) {
690                                    // if ( expr stmt; else
691                                    gcurrentsqlstatement.sourcetokenlist.add(ast);
692                                    lcstillinsql = true;
693                                }
694
695                            }
696                        }
697
698                        if (!lcstillinsql) {
699                            gst = EFindSqlStateType.stnormal;
700                            gcurrentsqlstatement.sourcetokenlist.add(ast);
701                            gcurrentsqlstatement.semicolonended = ast;
702                            onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
703                        }
704
705                    } else if (TBaseType.assigned(lcnextsqlstatement)) {
706
707                        if (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlgo) {
708                            onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
709                            gcurrentsqlstatement = lcnextsqlstatement;
710                            gcurrentsqlstatement.sourcetokenlist.add(ast);
711                            onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
712                            gst = EFindSqlStateType.stnormal;
713                            continue;
714                        }
715
716                        switch (gcurrentsqlstatement.sqlstatementtype) {    //
717                            case sstmssqlif:
718                            case sstmssqlwhile: {
719                                if ((lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlbegincatch)
720                                        || (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlbegintry)) {
721                                    gcurrentsqlstatement.sourcetokenlist.add(ast);
722                                    gst = EFindSqlStateType.stblock;
723                                    lcblocklevel = 1;
724                                    lcnextsqlstatement = null;
725                                    continue;
726
727                                }
728                                // if ( || while only contain one sql(not closed by {/} pair)
729                                // so will still in if ( || while statement
730                                else if (gcurrentsqlstatement.dummytag == 1) {
731                                    // if ( cond ^stmt nextstmt (^ stands for current pos)
732                                    gcurrentsqlstatement.sourcetokenlist.add(ast);
733
734                                    if ((lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlif)
735                                            || (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlwhile)
736                                    )
737                                        gcurrentsqlstatement.dummytag = 1;
738                                    else
739                                        gcurrentsqlstatement.dummytag = 0;
740
741
742                                    lcnextsqlstatement = null;
743                                    continue;
744                                }
745                                break;
746                            }//
747                            case sstmssqlalterqueue: {
748                                if (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlexec) {
749                                    // execute can't be used to delimite alter queue
750                                    gcurrentsqlstatement.sourcetokenlist.add(ast);
751
752
753                                    lcnextsqlstatement = null;
754                                    continue;
755
756                                }
757                                break;
758                            }
759                            case sstmssqlcreateschema: {
760                                gcurrentsqlstatement.sourcetokenlist.add(ast);
761                                lcnextsqlstatement = null;
762                                continue;
763                            }
764                            case sstinformixGrant:
765                            case sstinformixGrantFragment:
766                            case sstinformixRevoke:
767                            case sstinformixRevokeFragment:
768                            case sstinformixSetIsolation:
769                            case sstinformixSetEnvironment: {
770                                // Privilege lists (GRANT/REVOKE SELECT, INSERT,
771                                // UPDATE, DELETE, INDEX, ALTER ...) and isolation
772                                // options (RETAIN UPDATE LOCKS) contain statement-
773                                // starting keywords that are part of the current
774                                // statement; never split before the terminating
775                                // semicolon.
776                                gcurrentsqlstatement.sourcetokenlist.add(ast);
777                                lcnextsqlstatement = null;
778                                continue;
779                            }
780                        }//case
781
782                        onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
783                        gcurrentsqlstatement = lcnextsqlstatement;
784                        gcurrentsqlstatement.sourcetokenlist.add(ast);
785
786                        switch (gcurrentsqlstatement.sqlstatementtype) {    //
787                            case sstinformixCreateProcedure:
788                            case sstinformixCreateFunction:
789                            case sstcreatetrigger:
790                            case sstinformixAlterProcedure:
791                            case sstinformixAlterFunction: {
792                                gst = EFindSqlStateType.ststoredprocedure;
793                                break;
794                            }
795                            case sstmssqlbegintry:
796                            case sstmssqlbegincatch: {
797                                gst = EFindSqlStateType.sttrycatch;
798                                lctrycatchlevel = 0;
799                                break;
800                            }
801                            case sstmssqlgo: {
802                                gst = EFindSqlStateType.stnormal;
803                                break;
804                            }
805                            default: {
806                                gst = EFindSqlStateType.stsql;
807                                break;
808                            }
809                        }    // case
810
811                    }//TBaseType.assigned(lcnextsqlstatement)
812                    else if ((ast.tokencode == TBaseType.rrw_begin)) {
813                        if ((gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlif)
814                                || (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlwhile)
815                        ) {
816                            // start block of if ( || while statement
817                            gst = EFindSqlStateType.stblock;
818                            lcblocklevel = 0;
819                            gcurrentsqlstatement.sourcetokenlist.add(ast);
820                        } else if (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqldeclare) {
821                            onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
822                            gcurrentsqlstatement = new TMssqlBlock(vendor);
823                            gcurrentsqlstatement.sqlstatementtype = ESqlStatementType.sstmssqlblock;
824                            gcurrentsqlstatement.sourcetokenlist.add(ast);
825                            gst = EFindSqlStateType.stblock;
826                        } else {
827                            gcurrentsqlstatement.sourcetokenlist.add(ast);
828                        }
829                    } else if ((ast.tokencode == TBaseType.rrw_case)) {
830                        case_end_nest++;
831                        gcurrentsqlstatement.sourcetokenlist.add(ast);
832                    } else if ((ast.tokencode == TBaseType.rrw_end)) {
833                        if (case_end_nest > 0) {
834                            case_end_nest--;
835                        }
836                        gcurrentsqlstatement.sourcetokenlist.add(ast);
837                    } else if ((ast.tokencode == TBaseType.rrw_else)) {
838                        gcurrentsqlstatement.sourcetokenlist.add(ast);
839                        // if ( cond stmt ^else stmt
840                        if (((gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlif)
841                                || (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlwhile)
842                        ) && (case_end_nest == 0))
843                            gcurrentsqlstatement.dummytag = 1; // reduce to 1 while stmt after else is found: if ( cond stmt ^else stmt
844                    } else {
845                        gcurrentsqlstatement.sourcetokenlist.add(ast);
846                    }
847                    break;
848                }
849                case ststoredprocedure: {
850                    if (TBaseType.assigned(lcnextsqlstatement)) {
851                        if (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlgo) {
852                            onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
853                            gcurrentsqlstatement = lcnextsqlstatement;
854                            gcurrentsqlstatement.sourcetokenlist.add(ast);
855                            onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
856                            gst = EFindSqlStateType.stnormal;
857                        } else {
858                            gst = EFindSqlStateType.ststoredprocedurebody;
859                            gcurrentsqlstatement.sourcetokenlist.add(ast);
860
861                            lcnextsqlstatement = null;
862                        }
863                    }
864
865                    if (gst == EFindSqlStateType.ststoredprocedure) {
866                        gcurrentsqlstatement.sourcetokenlist.add(ast);
867                        if ((ast.tokencode == TBaseType.rrw_procedure)
868                                || (ast.tokencode == TBaseType.rrw_function)) {
869                            if (ast.searchToken(TBaseType.rrw_end, -1) != null)
870                                gst = EFindSqlStateType.stsql;
871                        }
872                    }
873                    break;
874                } //stplsql
875                case ststoredprocedurebody: {
876                    if (TBaseType.assigned(lcnextsqlstatement)) {
877                        switch (lcnextsqlstatement.sqlstatementtype) {    //
878                            case sstmssqlgo: {
879                                onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
880                                gcurrentsqlstatement = lcnextsqlstatement;
881                                gcurrentsqlstatement.sourcetokenlist.add(ast);
882                                onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
883                                gst = EFindSqlStateType.stnormal;
884                                break;
885                            }
886                            case sstinformixCreateProcedure:
887                            case sstinformixCreateFunction:
888                            case sstcreatetrigger:
889                            case sstinformixAlterProcedure:
890                            case sstinformixAlterFunction: {
891                                onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
892                                gcurrentsqlstatement = lcnextsqlstatement;
893                                gcurrentsqlstatement.sourcetokenlist.add(ast);
894                                gst = EFindSqlStateType.ststoredprocedure;
895                                break;
896                            }
897                            default: {
898                                lcnextsqlstatement = null;
899                                break;
900                            }
901                        }//case
902                    }
903
904                    if (gst == EFindSqlStateType.ststoredprocedurebody)
905                        gcurrentsqlstatement.sourcetokenlist.add(ast);
906                }
907                break;
908            } //case
909        } //for
910
911
912        //last statement
913        if (TBaseType.assigned(gcurrentsqlstatement) && (gst != EFindSqlStateType.stnormal)) {
914            onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, true, builder);
915        }
916
917        // Set results in builder
918        builder.sqlStatements(this.sqlstatements);
919        builder.errorCode(errorcount);
920        builder.errorMessage(errorcount == 0 ? "" : String.format("Extraction completed with %d error(s)", errorcount));
921
922        return errorcount;
923
924    }
925
926    // ========== Statement Parsing ==========
927
928    /**
929     * Parse all raw statements.
930     * <p>
931     * Migrated from TGSqlParser.doparse() with Informix-specific handling.
932     */
933    @Override
934    protected TStatementList performParsing(ParserContext context, TCustomParser parser,
935                                            TCustomParser secondaryParser, TSourceTokenList tokens,
936                                            TStatementList rawStatements) {
937        // Store references
938        this.fparser = (TParserInformix) parser;
939        this.sourcetokenlist = tokens;
940        this.parserContext = context;
941
942        // Use the raw statements passed from AbstractSqlParser.parse()
943        this.sqlstatements = rawStatements;
944
945        // Initialize statement parsing infrastructure
946        this.sqlcmds = SqlCmdsFactory.get(vendor);
947
948        // CRITICAL: Inject sqlcmds into parser
949        this.fparser.sqlcmds = this.sqlcmds;
950
951        // Initialize global context for semantic analysis
952        initializeGlobalContext();
953
954        // Parse each statement
955        for (int i = 0; i < sqlstatements.size(); i++) {
956            TCustomSqlStatement stmt = sqlstatements.getRawSql(i);
957
958            try {
959                stmt.setFrameStack(frameStack);
960                int parseResult = stmt.parsestatement(null, false, context.isOnlyNeedRawParseTree());
961
962                // Vendor-specific post-processing hook (default: no-op)
963                afterStatementParsed(stmt);
964
965                // Error recovery for CREATE TABLE statements
966                boolean doRecover = TBaseType.ENABLE_ERROR_RECOVER_IN_CREATE_TABLE;
967                if (doRecover && ((parseResult != 0) || (stmt.getErrorCount() > 0))) {
968                    handleCreateTableErrorRecovery(stmt);
969                }
970
971                // Collect errors from statement
972                if ((parseResult != 0) || (stmt.getErrorCount() > 0)) {
973                    copyErrorsFromStatement(stmt);
974                }
975
976            } catch (Exception ex) {
977                // Use inherited exception handler
978                handleStatementParsingException(stmt, i, ex);
979                continue;
980            }
981        }
982
983        // Clean up frame stack
984        if (globalFrame != null) {
985            globalFrame.popMeFromStack(frameStack);
986        }
987
988        return this.sqlstatements;
989    }
990
991    /**
992     * Handle CREATE TABLE/INDEX error recovery for Informix.
993     * <p>
994     * Migrated from TGSqlParser.doparse() lines 16914-16971.
995     * <p>
996     * This method implements intelligent error recovery for CREATE TABLE statements
997     * that fail to parse due to vendor-specific table properties or options that
998     * appear after the column definition.
999     * <p>
1000     * <b>Recovery Strategy:</b>
1001     * <ol>
1002     *   <li>Find the closing parenthesis ')' of the column definitions</li>
1003     *   <li>Mark all tokens after this parenthesis (except semicolon) as sqlpluscmd</li>
1004     *   <li>This causes the parser to ignore unknown table properties/options</li>
1005     *   <li>Re-parse the statement with ignored tokens</li>
1006     * </ol>
1007     * <p>
1008     * <b>Example:</b> Informix CREATE TABLE with FRAGMENT BY clause that may not
1009     * be fully supported:
1010     * <pre>
1011     * CREATE TABLE customer (id INT, name CHAR(32))
1012     * FRAGMENT BY EXPRESSION ... -- This gets ignored if it causes parse errors
1013     * </pre>
1014     */
1015    private void handleCreateTableErrorRecovery(TCustomSqlStatement stmt) {
1016        if (((stmt.sqlstatementtype == ESqlStatementType.sstcreatetable)
1017                || (stmt.sqlstatementtype == ESqlStatementType.sstcreateindex))
1018                && (!TBaseType.c_createTableStrictParsing)) {
1019
1020            int nested = 0;
1021            boolean isIgnore = false, isFoundIgnoreToken = false;
1022            TSourceToken firstIgnoreToken = null;
1023
1024            for (int k = 0; k < stmt.sourcetokenlist.size(); k++) {
1025                TSourceToken st = stmt.sourcetokenlist.get(k);
1026                if (isIgnore) {
1027                    if (st.issolidtoken() && (st.tokencode != ';')) {
1028                        isFoundIgnoreToken = true;
1029                        if (firstIgnoreToken == null) {
1030                            firstIgnoreToken = st;
1031                        }
1032                    }
1033                    if (st.tokencode != ';') {
1034                        st.tokencode = TBaseType.sqlpluscmd;
1035                    }
1036                    continue;
1037                }
1038                if (st.tokencode == (int) ')') {
1039                    nested--;
1040                    if (nested == 0) {
1041                        // Check if this is CREATE TABLE AS (SELECT ...)
1042                        // If so, don't ignore the SELECT part
1043                        boolean isSelect = false;
1044                        TSourceToken st1 = st.searchToken(TBaseType.rrw_as, 1);
1045                        if (st1 != null) {
1046                            TSourceToken st2 = st.searchToken((int) '(', 2);
1047                            if (st2 != null) {
1048                                TSourceToken st3 = st.searchToken(TBaseType.rrw_select, 3);
1049                                isSelect = (st3 != null);
1050                            }
1051                        }
1052                        if (!isSelect) isIgnore = true;
1053                    }
1054                } else if ((st.tokencode == (int) '(') || (st.tokencode == TBaseType.left_parenthesis_2)) {
1055                    nested++;
1056                }
1057            }
1058
1059            // If we found tokens to ignore, clear errors and re-parse
1060            if (isFoundIgnoreToken) {
1061                stmt.clearError();
1062                stmt.parsestatement(null, false);
1063            }
1064        }
1065    }
1066
1067    /**
1068     * Perform semantic analysis using TSQLResolver.
1069     * <p>
1070     * Inherited pattern from AbstractSqlParser with Informix-specific context.
1071     */
1072    @Override
1073    protected void performSemanticAnalysis(ParserContext context, TStatementList statements) {
1074        if (TBaseType.isEnableResolver() && getSyntaxErrors().isEmpty()) {
1075            TSQLResolver resolver = new TSQLResolver(globalContext, statements);
1076            resolver.resolve();
1077        }
1078    }
1079
1080    /**
1081     * Perform interpretation (AST evaluation).
1082     * <p>
1083     * Inherited pattern from AbstractSqlParser with Informix-specific context.
1084     */
1085    @Override
1086    protected void performInterpreter(ParserContext context, TStatementList statements) {
1087        if (TBaseType.ENABLE_INTERPRETER && getSyntaxErrors().isEmpty()) {
1088            TLog.clearLogs();
1089            TGlobalScope interpreterScope = new TGlobalScope(sqlEnv);
1090            TLog.enableInterpreterLogOnly();
1091            TASTEvaluator astEvaluator = new TASTEvaluator(statements, interpreterScope);
1092            astEvaluator.eval();
1093        }
1094    }
1095
1096    @Override
1097    public String toString() {
1098        return "InformixSqlParser{vendor=" + vendor + "}";
1099    }
1100}