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                                gst = EFindSqlStateType.stExec;
496                                break;
497                            }
498                            default: {
499                                gcurrentsqlstatement.sourcetokenlist.add(ast);
500                                gst = EFindSqlStateType.stsql;
501                                break;
502                            }
503                        }    // case
504                    } else {
505                        if (ast.tokencode == TBaseType.rrw_begin) {
506                            gcurrentsqlstatement = new TMssqlBlock(vendor);
507                            gcurrentsqlstatement.sqlstatementtype = ESqlStatementType.sstmssqlblock;
508                            gcurrentsqlstatement.sourcetokenlist.add(ast);
509                            gst = EFindSqlStateType.stblock;
510                        } else {
511                            if (sqlstatements.size() == 0) {
512                                //first statement of mssql batch, treat it as exec sp
513                                gst = EFindSqlStateType.stsql;
514                                gcurrentsqlstatement = new TMssqlExecute(vendor);
515                                //tmssqlexecute(gcurrentsqlstatement).exectype = metnoexeckeyword;
516// todo need previous line need to be implemented
517                                gcurrentsqlstatement.sourcetokenlist.add(ast);
518                            } else if (sqlstatements.get(sqlstatements.size() - 1).sqlstatementtype == ESqlStatementType.sstmssqlgo) {
519                                // prev sql is go, treat it as exec sp
520                                gst = EFindSqlStateType.stsql;
521                                gcurrentsqlstatement = new TMssqlExecute(vendor);
522                                //todo need to be implemented: tmssqlexecute(gcurrentsqlstatement).exectype = metnoexeckeyword;
523                                gcurrentsqlstatement.sourcetokenlist.add(ast);
524                            } else {
525                            }
526                        }
527                    }
528
529
530                    if (!TBaseType.assigned(gcurrentsqlstatement)) //error tokentext found
531                    {
532
533//                  if ( TBaseType.assigned(ontokenlizertokenerror) )
534//                    ontokenlizertokenerror(self,ast);
535//                  incerrorcount;
536//                  fsyntaxerrors[ferrorcount].hint = 'error when tokenlize';
537//                  fsyntaxerrors[ferrorcount].errortype = spwarning;
538//                  fsyntaxerrors[ferrorcount].tokentext = ast.sourcecode;
539//                  fsyntaxerrors[ferrorcount].lines = ast.lines;
540//                  fsyntaxerrors[ferrorcount].columns = ast.columns;// - length(fsyntaxerrors[ferrorcount].tokentext);
541
542//                  errormessage = "error when tokenlize:"+ast.astext+"("+ast.lineNo +","+ast.columnNo +")";
543//                  errorcount = 1;
544                        this.syntaxErrors.add(new TSyntaxError(ast.getAstext(), ast.lineNo, (ast.columnNo < 0 ? 0 : ast.columnNo)
545                                , "Error when tokenlize", EErrorType.spwarning, TBaseType.MSG_WARNING_ERROR_WHEN_TOKENIZE, null, ast.posinlist));
546
547                        ast.tokentype = ETokenType.tttokenlizererrortoken;
548                        gst = EFindSqlStateType.sterror;
549
550                        gcurrentsqlstatement = new TUnknownSqlStatement(vendor);
551                        gcurrentsqlstatement.sqlstatementtype = ESqlStatementType.sstinvalid;
552                        gcurrentsqlstatement.sourcetokenlist.add(ast);
553                    }
554                    break;
555                }
556                case stExec: {
557                    gcurrentsqlstatement.sourcetokenlist.add(ast);
558                    if (ast.tokentype == ETokenType.ttsemicolon) {
559                        gst = EFindSqlStateType.stnormal;
560                        onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
561                    }
562                    break;
563                }
564                case stblock: {
565                    if (TBaseType.assigned(lcnextsqlstatement)) {
566                        if (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlgo) {
567                            onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
568                            gcurrentsqlstatement = lcnextsqlstatement;
569                            gcurrentsqlstatement.sourcetokenlist.add(ast);
570                            onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
571                            gst = EFindSqlStateType.stnormal;
572                        } else {
573                            lcnextsqlstatement = null;
574                        }
575                    }
576
577                    if (gst == EFindSqlStateType.stblock) {
578                        gcurrentsqlstatement.sourcetokenlist.add(ast);
579                        if (ast.tokencode == TBaseType.rrw_begin) {
580                            // { [distributed] transaxtion/trans statement
581                            // { dialog [ conversation ]
582                            // { conversation timer
583                            //  doesn't start block ({ .. })
584                            lcnextsolidtoken = sourcetokenlist.nextsolidtoken(i, 1, false);
585                            if (TBaseType.assigned(lcnextsolidtoken)) {
586                                if (!(TBaseType.mysametext(lcnextsolidtoken.getAstext(), "tran")
587                                        || TBaseType.mysametext(lcnextsolidtoken.getAstext(), "transaction")
588                                        || TBaseType.mysametext(lcnextsolidtoken.getAstext(), "distributed")
589                                        || TBaseType.mysametext(lcnextsolidtoken.getAstext(), "dialog")
590                                        || TBaseType.mysametext(lcnextsolidtoken.getAstext(), "conversation")
591                                ))
592                                    lcblocklevel++;
593                            } else
594                                lcblocklevel++;
595
596                        } else if (ast.tokencode == TBaseType.rrw_case)  // case ... }
597                            lcblocklevel++;
598                        else if (ast.tokencode == TBaseType.rrw_end) {
599
600                            lcisendconversation = false;
601
602
603                            lcnextsolidtoken = sourcetokenlist.nextsolidtoken(i, 1, false);
604                            if (TBaseType.assigned(lcnextsolidtoken)) {
605                                if (lcnextsolidtoken.tokencode == flexer.getkeywordvalue("conversation".toUpperCase()))
606                                    lcisendconversation = true; // } conversation statement
607                            }
608
609
610                            if (!lcisendconversation) {
611
612                                if (lcblocklevel == 0) {
613                                    if (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlif) {
614                                        if (TBaseType.assigned(lcnextsolidtoken)) {
615                                            if (lcnextsolidtoken.tokencode == TBaseType.rrw_else) {
616                                                // { .. } else
617                                                gst = EFindSqlStateType.stsql;
618                                            } else if (lcnextsolidtoken.tokentype == ETokenType.ttsemicolon) {
619                                                lcnnextsolidtoken = sourcetokenlist.nextsolidtoken(lcnextsolidtoken.posinlist, 1, false);
620                                                if (TBaseType.assigned(lcnnextsolidtoken)) {
621                                                    if (lcnnextsolidtoken.tokencode == TBaseType.rrw_else) {
622                                                        // { .. } else
623                                                        gst = EFindSqlStateType.stsql;
624                                                    }
625                                                }
626                                            }
627                                        }
628                                    }
629
630                                    if (gst != EFindSqlStateType.stsql) {
631                                        onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
632                                        gst = EFindSqlStateType.stnormal;
633                                    }
634
635                                } else {
636                                    lcblocklevel--;
637                                }
638
639                            }
640                        }
641                    }
642                    break;
643                }
644                case sttrycatch: {
645
646                    if (TBaseType.assigned(lcnextsqlstatement)) {
647                        if (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlgo) {
648                            onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
649                            gcurrentsqlstatement = lcnextsqlstatement;
650                            gcurrentsqlstatement.sourcetokenlist.add(ast);
651                            onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
652                            gst = EFindSqlStateType.stnormal;
653                        } else {
654                            if (
655                                    (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlbegintry)
656                                            || (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlbegincatch)
657                            )
658                                lctrycatchlevel++;
659                            lcnextsqlstatement = null;
660                        }
661                    }
662
663                    if (gst == EFindSqlStateType.sttrycatch) {
664                        gcurrentsqlstatement.sourcetokenlist.add(ast);
665                        if ((ast.tokencode == TBaseType.rrw_try) ||
666                                (ast.tokencode == TBaseType.rrw_catch)) {
667                            // lcprevsolidtoken = sourcetokenlist.solidtokenbefore(i);
668                            if (TBaseType.assigned(lcprevsolidtoken)) {
669                                if (lcprevsolidtoken.tokencode == TBaseType.rrw_end) {
670                                    if (lctrycatchlevel == 0) {
671                                        onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
672                                        gst = EFindSqlStateType.stnormal;
673                                    } else
674                                        lctrycatchlevel--;
675                                }
676                            }
677                        }
678                    }
679                    break;
680                }
681                case stsql: {
682                    if ((ast.tokentype == ETokenType.ttsemicolon)) {
683                        lcstillinsql = false;
684                        if (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlif) {
685                            lcnextsolidtoken = sourcetokenlist.nextsolidtoken(i, 1, false);
686                            if (TBaseType.assigned(lcnextsolidtoken)) {
687                                if (lcnextsolidtoken.tokencode == TBaseType.rrw_else) {
688                                    // if ( expr stmt; else
689                                    gcurrentsqlstatement.sourcetokenlist.add(ast);
690                                    lcstillinsql = true;
691                                }
692
693                            }
694                        }
695
696                        if (!lcstillinsql) {
697                            gst = EFindSqlStateType.stnormal;
698                            gcurrentsqlstatement.sourcetokenlist.add(ast);
699                            gcurrentsqlstatement.semicolonended = ast;
700                            onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
701                        }
702
703                    } else if (TBaseType.assigned(lcnextsqlstatement)) {
704
705                        if (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlgo) {
706                            onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
707                            gcurrentsqlstatement = lcnextsqlstatement;
708                            gcurrentsqlstatement.sourcetokenlist.add(ast);
709                            onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
710                            gst = EFindSqlStateType.stnormal;
711                            continue;
712                        }
713
714                        switch (gcurrentsqlstatement.sqlstatementtype) {    //
715                            case sstmssqlif:
716                            case sstmssqlwhile: {
717                                if ((lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlbegincatch)
718                                        || (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlbegintry)) {
719                                    gcurrentsqlstatement.sourcetokenlist.add(ast);
720                                    gst = EFindSqlStateType.stblock;
721                                    lcblocklevel = 1;
722                                    lcnextsqlstatement = null;
723                                    continue;
724
725                                }
726                                // if ( || while only contain one sql(not closed by {/} pair)
727                                // so will still in if ( || while statement
728                                else if (gcurrentsqlstatement.dummytag == 1) {
729                                    // if ( cond ^stmt nextstmt (^ stands for current pos)
730                                    gcurrentsqlstatement.sourcetokenlist.add(ast);
731
732                                    if ((lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlif)
733                                            || (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlwhile)
734                                    )
735                                        gcurrentsqlstatement.dummytag = 1;
736                                    else
737                                        gcurrentsqlstatement.dummytag = 0;
738
739
740                                    lcnextsqlstatement = null;
741                                    continue;
742                                }
743                                break;
744                            }//
745                            case sstmssqlalterqueue: {
746                                if (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlexec) {
747                                    // execute can't be used to delimite alter queue
748                                    gcurrentsqlstatement.sourcetokenlist.add(ast);
749
750
751                                    lcnextsqlstatement = null;
752                                    continue;
753
754                                }
755                                break;
756                            }
757                            case sstmssqlcreateschema: {
758                                gcurrentsqlstatement.sourcetokenlist.add(ast);
759                                lcnextsqlstatement = null;
760                                continue;
761                            }
762                        }//case
763
764                        onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
765                        gcurrentsqlstatement = lcnextsqlstatement;
766                        gcurrentsqlstatement.sourcetokenlist.add(ast);
767
768                        switch (gcurrentsqlstatement.sqlstatementtype) {    //
769                            case sstinformixCreateProcedure:
770                            case sstinformixCreateFunction:
771                            case sstcreatetrigger:
772                            case sstinformixAlterProcedure:
773                            case sstinformixAlterFunction: {
774                                gst = EFindSqlStateType.ststoredprocedure;
775                                break;
776                            }
777                            case sstmssqlbegintry:
778                            case sstmssqlbegincatch: {
779                                gst = EFindSqlStateType.sttrycatch;
780                                lctrycatchlevel = 0;
781                                break;
782                            }
783                            case sstmssqlgo: {
784                                gst = EFindSqlStateType.stnormal;
785                                break;
786                            }
787                            default: {
788                                gst = EFindSqlStateType.stsql;
789                                break;
790                            }
791                        }    // case
792
793                    }//TBaseType.assigned(lcnextsqlstatement)
794                    else if ((ast.tokencode == TBaseType.rrw_begin)) {
795                        if ((gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlif)
796                                || (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlwhile)
797                        ) {
798                            // start block of if ( || while statement
799                            gst = EFindSqlStateType.stblock;
800                            lcblocklevel = 0;
801                            gcurrentsqlstatement.sourcetokenlist.add(ast);
802                        } else if (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqldeclare) {
803                            onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
804                            gcurrentsqlstatement = new TMssqlBlock(vendor);
805                            gcurrentsqlstatement.sqlstatementtype = ESqlStatementType.sstmssqlblock;
806                            gcurrentsqlstatement.sourcetokenlist.add(ast);
807                            gst = EFindSqlStateType.stblock;
808                        } else {
809                            gcurrentsqlstatement.sourcetokenlist.add(ast);
810                        }
811                    } else if ((ast.tokencode == TBaseType.rrw_case)) {
812                        case_end_nest++;
813                        gcurrentsqlstatement.sourcetokenlist.add(ast);
814                    } else if ((ast.tokencode == TBaseType.rrw_end)) {
815                        if (case_end_nest > 0) {
816                            case_end_nest--;
817                        }
818                        gcurrentsqlstatement.sourcetokenlist.add(ast);
819                    } else if ((ast.tokencode == TBaseType.rrw_else)) {
820                        gcurrentsqlstatement.sourcetokenlist.add(ast);
821                        // if ( cond stmt ^else stmt
822                        if (((gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlif)
823                                || (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlwhile)
824                        ) && (case_end_nest == 0))
825                            gcurrentsqlstatement.dummytag = 1; // reduce to 1 while stmt after else is found: if ( cond stmt ^else stmt
826                    } else {
827                        gcurrentsqlstatement.sourcetokenlist.add(ast);
828                    }
829                    break;
830                }
831                case ststoredprocedure: {
832                    if (TBaseType.assigned(lcnextsqlstatement)) {
833                        if (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlgo) {
834                            onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
835                            gcurrentsqlstatement = lcnextsqlstatement;
836                            gcurrentsqlstatement.sourcetokenlist.add(ast);
837                            onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
838                            gst = EFindSqlStateType.stnormal;
839                        } else {
840                            gst = EFindSqlStateType.ststoredprocedurebody;
841                            gcurrentsqlstatement.sourcetokenlist.add(ast);
842
843                            lcnextsqlstatement = null;
844                        }
845                    }
846
847                    if (gst == EFindSqlStateType.ststoredprocedure) {
848                        gcurrentsqlstatement.sourcetokenlist.add(ast);
849                        if ((ast.tokencode == TBaseType.rrw_procedure)
850                                || (ast.tokencode == TBaseType.rrw_function)) {
851                            if (ast.searchToken(TBaseType.rrw_end, -1) != null)
852                                gst = EFindSqlStateType.stsql;
853                        }
854                    }
855                    break;
856                } //stplsql
857                case ststoredprocedurebody: {
858                    if (TBaseType.assigned(lcnextsqlstatement)) {
859                        switch (lcnextsqlstatement.sqlstatementtype) {    //
860                            case sstmssqlgo: {
861                                onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
862                                gcurrentsqlstatement = lcnextsqlstatement;
863                                gcurrentsqlstatement.sourcetokenlist.add(ast);
864                                onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
865                                gst = EFindSqlStateType.stnormal;
866                                break;
867                            }
868                            case sstinformixCreateProcedure:
869                            case sstinformixCreateFunction:
870                            case sstcreatetrigger:
871                            case sstinformixAlterProcedure:
872                            case sstinformixAlterFunction: {
873                                onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
874                                gcurrentsqlstatement = lcnextsqlstatement;
875                                gcurrentsqlstatement.sourcetokenlist.add(ast);
876                                gst = EFindSqlStateType.ststoredprocedure;
877                                break;
878                            }
879                            default: {
880                                lcnextsqlstatement = null;
881                                break;
882                            }
883                        }//case
884                    }
885
886                    if (gst == EFindSqlStateType.ststoredprocedurebody)
887                        gcurrentsqlstatement.sourcetokenlist.add(ast);
888                }
889                break;
890            } //case
891        } //for
892
893
894        //last statement
895        if (TBaseType.assigned(gcurrentsqlstatement) && (gst != EFindSqlStateType.stnormal)) {
896            onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, true, builder);
897        }
898
899        // Set results in builder
900        builder.sqlStatements(this.sqlstatements);
901        builder.errorCode(errorcount);
902        builder.errorMessage(errorcount == 0 ? "" : String.format("Extraction completed with %d error(s)", errorcount));
903
904        return errorcount;
905
906    }
907
908    // ========== Statement Parsing ==========
909
910    /**
911     * Parse all raw statements.
912     * <p>
913     * Migrated from TGSqlParser.doparse() with Informix-specific handling.
914     */
915    @Override
916    protected TStatementList performParsing(ParserContext context, TCustomParser parser,
917                                            TCustomParser secondaryParser, TSourceTokenList tokens,
918                                            TStatementList rawStatements) {
919        // Store references
920        this.fparser = (TParserInformix) parser;
921        this.sourcetokenlist = tokens;
922        this.parserContext = context;
923
924        // Use the raw statements passed from AbstractSqlParser.parse()
925        this.sqlstatements = rawStatements;
926
927        // Initialize statement parsing infrastructure
928        this.sqlcmds = SqlCmdsFactory.get(vendor);
929
930        // CRITICAL: Inject sqlcmds into parser
931        this.fparser.sqlcmds = this.sqlcmds;
932
933        // Initialize global context for semantic analysis
934        initializeGlobalContext();
935
936        // Parse each statement
937        for (int i = 0; i < sqlstatements.size(); i++) {
938            TCustomSqlStatement stmt = sqlstatements.getRawSql(i);
939
940            try {
941                stmt.setFrameStack(frameStack);
942                int parseResult = stmt.parsestatement(null, false, context.isOnlyNeedRawParseTree());
943
944                // Vendor-specific post-processing hook (default: no-op)
945                afterStatementParsed(stmt);
946
947                // Error recovery for CREATE TABLE statements
948                boolean doRecover = TBaseType.ENABLE_ERROR_RECOVER_IN_CREATE_TABLE;
949                if (doRecover && ((parseResult != 0) || (stmt.getErrorCount() > 0))) {
950                    handleCreateTableErrorRecovery(stmt);
951                }
952
953                // Collect errors from statement
954                if ((parseResult != 0) || (stmt.getErrorCount() > 0)) {
955                    copyErrorsFromStatement(stmt);
956                }
957
958            } catch (Exception ex) {
959                // Use inherited exception handler
960                handleStatementParsingException(stmt, i, ex);
961                continue;
962            }
963        }
964
965        // Clean up frame stack
966        if (globalFrame != null) {
967            globalFrame.popMeFromStack(frameStack);
968        }
969
970        return this.sqlstatements;
971    }
972
973    /**
974     * Handle CREATE TABLE/INDEX error recovery for Informix.
975     * <p>
976     * Migrated from TGSqlParser.doparse() lines 16914-16971.
977     * <p>
978     * This method implements intelligent error recovery for CREATE TABLE statements
979     * that fail to parse due to vendor-specific table properties or options that
980     * appear after the column definition.
981     * <p>
982     * <b>Recovery Strategy:</b>
983     * <ol>
984     *   <li>Find the closing parenthesis ')' of the column definitions</li>
985     *   <li>Mark all tokens after this parenthesis (except semicolon) as sqlpluscmd</li>
986     *   <li>This causes the parser to ignore unknown table properties/options</li>
987     *   <li>Re-parse the statement with ignored tokens</li>
988     * </ol>
989     * <p>
990     * <b>Example:</b> Informix CREATE TABLE with FRAGMENT BY clause that may not
991     * be fully supported:
992     * <pre>
993     * CREATE TABLE customer (id INT, name CHAR(32))
994     * FRAGMENT BY EXPRESSION ... -- This gets ignored if it causes parse errors
995     * </pre>
996     */
997    private void handleCreateTableErrorRecovery(TCustomSqlStatement stmt) {
998        if (((stmt.sqlstatementtype == ESqlStatementType.sstcreatetable)
999                || (stmt.sqlstatementtype == ESqlStatementType.sstcreateindex))
1000                && (!TBaseType.c_createTableStrictParsing)) {
1001
1002            int nested = 0;
1003            boolean isIgnore = false, isFoundIgnoreToken = false;
1004            TSourceToken firstIgnoreToken = null;
1005
1006            for (int k = 0; k < stmt.sourcetokenlist.size(); k++) {
1007                TSourceToken st = stmt.sourcetokenlist.get(k);
1008                if (isIgnore) {
1009                    if (st.issolidtoken() && (st.tokencode != ';')) {
1010                        isFoundIgnoreToken = true;
1011                        if (firstIgnoreToken == null) {
1012                            firstIgnoreToken = st;
1013                        }
1014                    }
1015                    if (st.tokencode != ';') {
1016                        st.tokencode = TBaseType.sqlpluscmd;
1017                    }
1018                    continue;
1019                }
1020                if (st.tokencode == (int) ')') {
1021                    nested--;
1022                    if (nested == 0) {
1023                        // Check if this is CREATE TABLE AS (SELECT ...)
1024                        // If so, don't ignore the SELECT part
1025                        boolean isSelect = false;
1026                        TSourceToken st1 = st.searchToken(TBaseType.rrw_as, 1);
1027                        if (st1 != null) {
1028                            TSourceToken st2 = st.searchToken((int) '(', 2);
1029                            if (st2 != null) {
1030                                TSourceToken st3 = st.searchToken(TBaseType.rrw_select, 3);
1031                                isSelect = (st3 != null);
1032                            }
1033                        }
1034                        if (!isSelect) isIgnore = true;
1035                    }
1036                } else if ((st.tokencode == (int) '(') || (st.tokencode == TBaseType.left_parenthesis_2)) {
1037                    nested++;
1038                }
1039            }
1040
1041            // If we found tokens to ignore, clear errors and re-parse
1042            if (isFoundIgnoreToken) {
1043                stmt.clearError();
1044                stmt.parsestatement(null, false);
1045            }
1046        }
1047    }
1048
1049    /**
1050     * Perform semantic analysis using TSQLResolver.
1051     * <p>
1052     * Inherited pattern from AbstractSqlParser with Informix-specific context.
1053     */
1054    @Override
1055    protected void performSemanticAnalysis(ParserContext context, TStatementList statements) {
1056        if (TBaseType.isEnableResolver() && getSyntaxErrors().isEmpty()) {
1057            TSQLResolver resolver = new TSQLResolver(globalContext, statements);
1058            resolver.resolve();
1059        }
1060    }
1061
1062    /**
1063     * Perform interpretation (AST evaluation).
1064     * <p>
1065     * Inherited pattern from AbstractSqlParser with Informix-specific context.
1066     */
1067    @Override
1068    protected void performInterpreter(ParserContext context, TStatementList statements) {
1069        if (TBaseType.ENABLE_INTERPRETER && getSyntaxErrors().isEmpty()) {
1070            TLog.clearLogs();
1071            TGlobalScope interpreterScope = new TGlobalScope(sqlEnv);
1072            TLog.enableInterpreterLogOnly();
1073            TASTEvaluator astEvaluator = new TASTEvaluator(statements, interpreterScope);
1074            astEvaluator.eval();
1075        }
1076    }
1077
1078    @Override
1079    public String toString() {
1080        return "InformixSqlParser{vendor=" + vendor + "}";
1081    }
1082}