001package gudusoft.gsqlparser.parser;
002
003import gudusoft.gsqlparser.EDbVendor;
004import gudusoft.gsqlparser.EErrorType;
005import gudusoft.gsqlparser.EFindSqlStateType;
006import gudusoft.gsqlparser.ESqlStatementType;
007import gudusoft.gsqlparser.ETokenStatus;
008import gudusoft.gsqlparser.ETokenType;
009import gudusoft.gsqlparser.TBaseType;
010import gudusoft.gsqlparser.TCustomLexer;
011import gudusoft.gsqlparser.TCustomParser;
012import gudusoft.gsqlparser.TCustomSqlStatement;
013import gudusoft.gsqlparser.TLexerEdb;
014import gudusoft.gsqlparser.TParserEdb;
015import gudusoft.gsqlparser.TSourceToken;
016import gudusoft.gsqlparser.TSourceTokenList;
017import gudusoft.gsqlparser.TStatementList;
018import gudusoft.gsqlparser.TSyntaxError;
019import gudusoft.gsqlparser.TLog;
020import gudusoft.gsqlparser.compiler.TASTEvaluator;
021import gudusoft.gsqlparser.compiler.TContext;
022import gudusoft.gsqlparser.compiler.TFrame;
023import gudusoft.gsqlparser.compiler.TGlobalScope;
024import gudusoft.gsqlparser.resolver.TSQLResolver;
025import gudusoft.gsqlparser.sqlcmds.ISqlCmds;
026import gudusoft.gsqlparser.sqlcmds.SqlCmdsFactory;
027import gudusoft.gsqlparser.sqlenv.TSQLEnv;
028import gudusoft.gsqlparser.stmt.TCommonBlock;
029import gudusoft.gsqlparser.stmt.TCreateTableSqlStatement;
030import gudusoft.gsqlparser.stmt.TRoutine;
031import gudusoft.gsqlparser.stmt.TUnknownSqlStatement;
032import gudusoft.gsqlparser.stmt.oracle.TPlsqlCreatePackage;
033import gudusoft.gsqlparser.stmt.oracle.TSqlplusCmdStatement;
034
035import java.util.Stack;
036
037/**
038 * Edb SQL parser implementation.
039 *
040 * <p>This parser handles Edb-specific SQL syntax including:
041 * <ul>
042 *   <li>PostgreSQL-based SQL syntax with Oracle compatibility</li>
043 *   <li>Stored procedures and functions (Oracle-style and PostgreSQL-style)</li>
044 *   <li>Package specifications and bodies</li>
045 *   <li>SQL*Plus-like commands</li>
046 *   <li>Dynamic delimiter support</li>
047 * </ul>
048 *
049 * <p><b>Implementation Status:</b> MIGRATED
050 * <ul>
051 *   <li><b>Completed:</b> Migrated from TGSqlParser to AbstractSqlParser</li>
052 *   <li><b>Current:</b> Fully self-contained Edb parser</li>
053 * </ul>
054 *
055 * <p><b>Design Notes:</b>
056 * <ul>
057 *   <li>Extends {@link AbstractSqlParser} using template method pattern</li>
058 *   <li>Uses single parser: {@link TParserEdb}</li>
059 *   <li>Primary delimiter: semicolon (;)</li>
060 *   <li>Supports both Oracle-style and PostgreSQL-style routines</li>
061 *   <li>Handles package specifications and bodies with Edb-specific state tracking</li>
062 * </ul>
063 *
064 * @see SqlParser
065 * @see AbstractSqlParser
066 * @see TLexerEdb
067 * @see TParserEdb
068 * @since 3.2.0.0
069 */
070public class EdbSqlParser extends AbstractSqlParser {
071
072    // ========== Lexer and Parser ==========
073
074    /** The Edb lexer used for tokenization */
075    public TLexerEdb flexer;
076
077    /** The Edb parser used for parsing */
078    private TParserEdb fparser;
079
080    // ========== Statement Extraction State ==========
081
082    /** Current statement being built during raw extraction */
083    private TCustomSqlStatement gcurrentsqlstatement;
084
085    /** Flag to indicate if this is an Oracle-style routine */
086    private boolean isOracleStyleRoutine = true;
087
088    /** Procedure statement reference for tracking */
089    private TSourceToken stProcedure = null;
090
091    /** Function statement reference for tracking */
092    private TSourceToken stFunction = null;
093
094    /**
095     * Construct Edb SQL parser.
096     * <p>
097     * Configures the parser for Edb database with semicolon (;) as the default delimiter.
098     */
099    public EdbSqlParser() {
100        super(EDbVendor.dbvedb);
101        this.delimiterChar = ';';
102        this.defaultDelimiterStr = ";";
103
104        // Create lexer once - will be reused for all parsing operations
105        this.flexer = new TLexerEdb();
106        this.flexer.delimiterchar = this.delimiterChar;
107        this.flexer.defaultDelimiterStr = this.defaultDelimiterStr;
108
109        // Set parent's lexer reference for shared tokenization logic
110        this.lexer = this.flexer;
111
112        // Create parser once - will be reused for all parsing operations
113        this.fparser = new TParserEdb(null);
114        this.fparser.lexer = this.flexer;
115    }
116
117    // ========== Abstract Method Implementations ==========
118
119    @Override
120    protected TCustomLexer getLexer(ParserContext context) {
121        return this.flexer;
122    }
123
124    @Override
125    protected TCustomParser getParser(ParserContext context, TSourceTokenList tokens) {
126        this.fparser.sourcetokenlist = tokens;
127        return this.fparser;
128    }
129
130    @Override
131    protected TCustomParser getSecondaryParser(ParserContext context, TSourceTokenList tokens) {
132        // Edb does not have a secondary parser
133        return null;
134    }
135
136    // ========== Phase 2: Tokenization (Hook Pattern) ==========
137
138    @Override
139    protected void tokenizeVendorSql() {
140        doedbtexttotokenlist();
141    }
142
143    /**
144     * Tokenize Edb SQL text into source tokens.
145     * <p>
146     * Migrated from TGSqlParser.doedbtexttotokenlist() (lines 2899-3026).
147     * <p>
148     * Handles:
149     * <ul>
150     *   <li>SQL*Plus-like command detection</li>
151     *   <li>Forward slash disambiguation (division vs SQL*Plus command)</li>
152     *   <li>PostgreSQL-style keyword handling (INNER, NOT DEFERRABLE, etc.)</li>
153     * </ul>
154     */
155    private void doedbtexttotokenlist() {
156        boolean insqlpluscmd = false;
157        boolean isvalidplace = true;
158        boolean waitingreturnforfloatdiv = false;
159        boolean waitingreturnforsemicolon = false;
160        boolean continuesqlplusatnewline = false;
161
162        TSourceToken lct = null, prevst = null;
163
164        TSourceToken asourcetoken, lcprevst;
165        int yychar;
166
167        asourcetoken = getanewsourcetoken();
168        if (asourcetoken == null) return;
169        yychar = asourcetoken.tokencode;
170
171        while (yychar > 0) {
172            sourcetokenlist.add(asourcetoken);
173            switch (yychar) {
174                case TBaseType.cmtdoublehyphen:
175                case TBaseType.cmtslashstar:
176                case TBaseType.lexspace: {
177                    if (insqlpluscmd) {
178                        asourcetoken.insqlpluscmd = true;
179                    }
180                    break;
181                }
182                case TBaseType.lexnewline: {
183                    if (insqlpluscmd) {
184                        insqlpluscmd = false;
185                        isvalidplace = true;
186
187                        if (continuesqlplusatnewline) {
188                            insqlpluscmd = true;
189                            isvalidplace = false;
190                            asourcetoken.insqlpluscmd = true;
191                        }
192                    }
193
194                    if (waitingreturnforsemicolon) {
195                        isvalidplace = true;
196                    }
197                    if (waitingreturnforfloatdiv) {
198                        isvalidplace = true;
199                        lct.tokencode = TBaseType.sqlpluscmd;
200                        if (lct.tokentype != ETokenType.ttslash) {
201                            lct.tokentype = ETokenType.ttsqlpluscmd;
202                        }
203                    }
204                    flexer.insqlpluscmd = insqlpluscmd;
205                    break;
206                } //case newline
207                default: {
208                    //solid token
209                    continuesqlplusatnewline = false;
210                    waitingreturnforsemicolon = false;
211                    waitingreturnforfloatdiv = false;
212                    if (insqlpluscmd) {
213                        asourcetoken.insqlpluscmd = true;
214                        if (asourcetoken.toString().equalsIgnoreCase("-")) {
215                            continuesqlplusatnewline = true;
216                        }
217                    } else {
218                        if (asourcetoken.tokentype == ETokenType.ttsemicolon) {
219                            waitingreturnforsemicolon = true;
220                        }
221                        if ((asourcetoken.tokentype == ETokenType.ttslash)
222                                && (isvalidplace || (isValidPlaceForDivToSqlplusCmd(sourcetokenlist, asourcetoken.posinlist)))) {
223                            lct = asourcetoken;
224                            waitingreturnforfloatdiv = true;
225                        }
226                        if ((isvalidplace) && isvalidsqlpluscmdInPostgresql(asourcetoken.toString())) {
227                            asourcetoken.tokencode = TBaseType.sqlpluscmd;
228                            if (asourcetoken.tokentype != ETokenType.ttslash) {
229                                asourcetoken.tokentype = ETokenType.ttsqlpluscmd;
230                            }
231                            insqlpluscmd = true;
232                            flexer.insqlpluscmd = insqlpluscmd;
233                        }
234                    }
235                    isvalidplace = false;
236
237                    // the inner keyword token should be converted to TBaseType.ident when
238                    // next solid token is not join
239
240                    if (prevst != null) {
241                        if (prevst.tokencode == TBaseType.rrw_inner)//flexer.getkeywordvalue("INNER"))
242                        {
243                            if (asourcetoken.tokencode != flexer.getkeywordvalue("JOIN")) {
244                                prevst.tokencode = TBaseType.ident;
245                            }
246                        }
247
248
249                        if ((prevst.tokencode == TBaseType.rrw_not)
250                                && (asourcetoken.tokencode == flexer.getkeywordvalue("DEFERRABLE"))) {
251                            prevst.tokencode = flexer.getkeywordvalue("NOT_DEFERRABLE");
252                            asourcetoken.tokenstatus = ETokenStatus.tsignorebyyacc;
253                        }
254                    }
255
256                    if (asourcetoken.tokencode == TBaseType.rrw_inner) {
257                        prevst = asourcetoken;
258                    } else if (asourcetoken.tokencode == TBaseType.rrw_not) {
259                        prevst = asourcetoken;
260                    } else {
261                        prevst = null;
262                    }
263
264                    if ((asourcetoken.tokencode == flexer.getkeywordvalue("DIRECT_LOAD"))
265                            || (asourcetoken.tokencode == flexer.getkeywordvalue("ALL"))) {
266                        // RW_COMPRESS RW_FOR RW_ALL RW_OPERATIONS
267                        // RW_COMPRESS RW_FOR RW_DIRECT_LOAD RW_OPERATIONS
268                        // change rw_for to TBaseType.rw_for1, it conflicts with compress for update in create materialized view
269
270                        lcprevst = getprevsolidtoken(asourcetoken);
271                        if (lcprevst != null) {
272                            if (lcprevst.tokencode == TBaseType.rrw_for)
273                                lcprevst.tokencode = TBaseType.rw_for1;
274                        }
275                    }
276
277                    if (asourcetoken.tokencode == TBaseType.rrw_dense_rank) {
278                        //keep keyword can be column alias, make keep in keep_denserankclause as a different token code
279                        TSourceToken stKeep = asourcetoken.searchToken(TBaseType.rrw_keep, -2);
280                        if (stKeep != null) {
281                            stKeep.tokencode = TBaseType.rrw_keep_before_dense_rank;
282                        }
283                    }
284
285                    if ((asourcetoken.tokencode == TBaseType.rrw_postgresql_rowtype)
286                            || (asourcetoken.tokencode == TBaseType.rrw_postgresql_type)) {
287                        TSourceToken stPercent = asourcetoken.searchToken('%', -1);
288                        if (stPercent != null) {
289                            stPercent.tokencode = TBaseType.rowtype_operator;
290                        }
291                    }
292
293                    if (asourcetoken.tokencode == TBaseType.JSON_EXIST) {
294                        TSourceToken stPercent = asourcetoken.searchToken('=', -1);
295                        if (stPercent != null) {    // = ?, ? after = should not be treated as json exist operator
296                            asourcetoken.tokencode = TBaseType.ident;
297                        }
298                    }
299
300                    if (asourcetoken.tokencode == TBaseType.rrw_update) { // on conflict do update in insert statement
301                        TSourceToken stDo = asourcetoken.searchToken(TBaseType.rrw_do, -1);
302                        if (stDo != null) {
303                            asourcetoken.tokencode = TBaseType.rrw_postgresql_do_update;
304                        }
305                    }
306
307                    break;
308                }
309            }
310            asourcetoken = getanewsourcetoken();
311            if (asourcetoken != null) {
312                yychar = asourcetoken.tokencode;
313            } else {
314                yychar = 0;
315
316                if (waitingreturnforfloatdiv) {
317                    // / at the end of line treat as sqlplus command
318                    lct.tokencode = TBaseType.sqlpluscmd;
319                    if (lct.tokentype != ETokenType.ttslash) {
320                        lct.tokentype = ETokenType.ttsqlpluscmd;
321                    }
322                }
323            }
324        }
325    }
326
327    /**
328     * Check if a valid place for division operator to be treated as SQL*Plus command.
329     * <p>
330     * Migrated from TGSqlParser.IsValidPlaceForDivToSqlplusCmd() (lines 2641-2655).
331     */
332    private boolean isValidPlaceForDivToSqlplusCmd(TSourceTokenList pstlist, int pPos) {
333        boolean ret = false;
334
335        if ((pPos <= 0) || (pPos > pstlist.size() - 1)) return ret;
336        //token directly before div must be ttreturn without space appending it
337        TSourceToken lcst = pstlist.get(pPos - 1);
338        if (lcst.tokentype != ETokenType.ttreturn) {
339            return ret;
340        }
341
342        if (!(lcst.getAstext().charAt(lcst.getAstext().length() - 1) == ' ')) {
343            ret = true;
344        }
345
346        return ret;
347    }
348
349    /**
350     * Check if a string is a valid SQL*Plus command in PostgreSQL/Edb context.
351     * <p>
352     * Migrated from TGSqlParser.isvalidsqlpluscmdInPostgresql() (lines 2658-2660).
353     * <p>
354     * Note: This is a placeholder function that always returns false.
355     */
356    private boolean isvalidsqlpluscmdInPostgresql(String astr) {
357        return false;
358    }
359
360    /**
361     * Get the previous solid token before the given token.
362     * <p>
363     * Migrated from TGSqlParser.getprevsolidtoken() (lines 2752-2773).
364     */
365    private TSourceToken getprevsolidtoken(TSourceToken ptoken) {
366        TSourceToken ret = null;
367        TSourceTokenList lctokenlist = ptoken.container;
368        if (lctokenlist != null) {
369            if ((ptoken.posinlist > 0) && (lctokenlist.size() > ptoken.posinlist - 1)) {
370                if (!((lctokenlist.get(ptoken.posinlist - 1).tokentype == ETokenType.ttwhitespace)
371                        || (lctokenlist.get(ptoken.posinlist - 1).tokentype == ETokenType.ttreturn)
372                        || (lctokenlist.get(ptoken.posinlist - 1).tokentype == ETokenType.ttsimplecomment)
373                        || (lctokenlist.get(ptoken.posinlist - 1).tokentype == ETokenType.ttbracketedcomment)
374                )) {
375                    ret = lctokenlist.get(ptoken.posinlist - 1);
376                } else {
377                    ret = getprevsolidtoken(lctokenlist.get(ptoken.posinlist - 1));
378                }
379            }
380        }
381        return ret;
382    }
383
384    /**
385     * Find package name in Edb package statements.
386     * <p>
387     * Migrated from TGSqlParser.findPkgName() (lines 7437-7485).
388     * <p>
389     * @param st Starting token
390     * @param mode 1=package spec, 2=package body, 3=package name after END
391     * @return Package name string
392     */
393    private String findPkgName(TSourceToken st, int mode) {
394        String pkgName = "";
395
396        boolean inpkgname = false;
397        if (mode == 3) inpkgname = true;
398        int MAX_TRY_TOKENS = 20;
399        int tryTokens = 0;
400        while (st != null) {
401
402            if (st.isnonsolidtoken()) {
403                st = st.getNextTokenInChain();
404                continue;
405            }
406            tryTokens++;
407
408            if ((mode == 1) && (st.tokencode == TBaseType.rrw_package)) {
409                inpkgname = true;
410                st = st.getNextTokenInChain();
411                continue;
412            } else if ((mode == 2) && (st.tokencode == TBaseType.rrw_body)) {
413                inpkgname = true;
414                st = st.getNextTokenInChain();
415                continue;
416            }
417
418            if (((mode == 1) || (mode == 2))
419                    && ((st.tokencode == TBaseType.rrw_is) || (st.tokencode == TBaseType.rrw_as))
420            ) {
421                break;
422            } else if ((mode == 3) && (st.tokencode == ';')) {
423                break;
424            }
425
426            if (!inpkgname) {
427                st = st.getNextTokenInChain();
428                continue;
429            }
430            pkgName = pkgName + st.toString();
431            st = st.getNextTokenInChain();
432
433            if (tryTokens > MAX_TRY_TOKENS) {
434                TBaseType.log(String.format("Package name is not found in create package"), TLog.ERROR, st);
435                break;
436            }
437        }
438
439        return pkgName;
440    }
441
442    // ========== Phase 3: Raw Statement Extraction (Hook Pattern) ==========
443
444    @Override
445    protected void setupVendorParsersForExtraction() {
446        this.fparser.sqlcmds = this.sqlcmds;
447        this.fparser.sourcetokenlist = this.sourcetokenlist;
448    }
449
450    @Override
451    protected void extractVendorRawStatements(SqlParseResult.Builder builder) {
452        doedbgetrawsqlstatements(builder);
453    }
454
455    /**
456     * Extract raw SQL statements from token list.
457     * <p>
458     * Migrated from TGSqlParser.doedbgetrawsqlstatements() (lines 7495-8049).
459     * <p>
460     * This method implements a state machine to identify statement boundaries:
461     * <ul>
462     *   <li>Regular SQL statements terminated by semicolon</li>
463     *   <li>Oracle-style stored procedures with BEGIN/END pairs</li>
464     *   <li>PostgreSQL-style procedures with $$ delimiters</li>
465     *   <li>Package specifications and bodies with Edb-specific state tracking</li>
466     *   <li>Slash (/) and period (.) terminators for procedural blocks</li>
467     * </ul>
468     */
469    private void doedbgetrawsqlstatements(SqlParseResult.Builder builder) {
470        int waitingEnd = 0;
471        boolean foundEnd = false;
472        int declarePending = 0; // tracks nested IS/AS/DECLARE without matching BEGIN
473        isOracleStyleRoutine = true;
474        stProcedure = null;
475        stFunction = null;
476
477        if (TBaseType.assigned(sqlstatements)) sqlstatements.clear();
478        if (!TBaseType.assigned(sourcetokenlist)) {
479            builder.errorCode(-1);
480            return;
481        }
482
483        gcurrentsqlstatement = null;
484        EFindSqlStateType gst = EFindSqlStateType.stnormal;
485        TSourceToken lcprevsolidtoken = null, ast = null;
486
487        // Handle single PL block mode
488        if (parserContext != null && parserContext.isSinglePLBlock()) {
489            gcurrentsqlstatement = new TCommonBlock(vendor);
490        }
491
492        for (int i = 0; i < sourcetokenlist.size(); i++) {
493
494            if ((ast != null) && (ast.issolidtoken()))
495                lcprevsolidtoken = ast;
496
497            ast = sourcetokenlist.get(i);
498            sourcetokenlist.curpos = i;
499
500            if (parserContext != null && parserContext.isSinglePLBlock()) {
501                gcurrentsqlstatement.sourcetokenlist.add(ast);
502                continue;
503            }
504
505            // Token preprocessing: Adjust token codes based on context
506            if (ast.tokencode == TBaseType.JSON_EXIST) {
507                TSourceToken stConstant = ast.searchToken(TBaseType.sconst, 1);
508                if (stConstant == null) {
509                    ast.tokencode = TBaseType.ident;
510                }
511            } else if (ast.tokencode == TBaseType.rrw_postgresql_POSITION) {
512                TSourceToken st1 = ast.nextSolidToken();
513                if (st1 != null) {
514                    if (st1.tokencode == '(') {
515                        ast.tokencode = TBaseType.rrw_postgresql_POSITION_FUNCTION;
516                    }
517                }
518            } else if (ast.tokencode == TBaseType.rrw_postgresql_ordinality) {
519                TSourceToken lcprevst = getprevsolidtoken(ast);
520
521                if (lcprevst != null) {
522                    if (lcprevst.tokencode == TBaseType.rrw_with) {
523                        TSourceToken lcnextst = ast.nextSolidToken();
524                        if ((lcnextst != null) && (lcnextst.tokencode == TBaseType.rrw_as)) {
525                            // with ordinality as (select 1 as x) select * from ordinality;
526                            // don't change with to rrw_postgresql_with_lookahead
527                        } else {
528                            lcprevst.tokencode = TBaseType.rrw_postgresql_with_lookahead;
529                        }
530
531                    }
532                }
533            } else if ((ast.tokencode == TBaseType.rrw_postgresql_filter)
534                    || (ast.tokencode == TBaseType.GAUSSDB_TO_BINARY_DOUBLE)
535                    || (ast.tokencode == TBaseType.GAUSSDB_TO_BINARY_FLOAT)
536                    || (ast.tokencode == TBaseType.GAUSSDB_TO_NUMBER)
537                    || (ast.tokencode == TBaseType.GAUSSDB_TO_DATE)
538                    || (ast.tokencode == TBaseType.GAUSSDB_TO_TIMESTAMP)
539                    || (ast.tokencode == TBaseType.GAUSSDB_TO_TIMESTAMP_TZ)
540            ) {
541                TSourceToken st1 = ast.nextSolidToken();
542                if (st1 != null) {
543                    if (st1.tokencode == '(') {
544                        // Keep as function
545                    } else {
546                        ast.tokencode = TBaseType.ident;
547                    }
548                }
549            } else if (ast.tokencode == TBaseType.rrw_postgresql_jsonb) {
550                TSourceToken st1 = ast.nextSolidToken();
551                if (st1 != null) {
552                    if (st1.tokencode == '?') {
553                        st1.tokencode = TBaseType.OP_JSONB_QUESTION;
554                    }
555                }
556            } else if (ast.tokencode == TBaseType.GAUSSDB_TO_NUMBER) {
557                TSourceToken st1 = ast.searchToken('(', 1);
558                if (st1 == null) {
559                    ast.tokencode = TBaseType.ident;
560                }
561            } else if (ast.tokencode == '?') {
562                TSourceToken st1 = ast.nextSolidToken();
563                if (st1 != null) {
564                    if (st1.tokencode == TBaseType.sconst) {
565                        ast.tokencode = TBaseType.OP_JSONB_QUESTION;
566                    }
567                }
568            } else if (ast.tokencode == TBaseType.rrw_values) {
569                TSourceToken stParen = ast.searchToken('(', 1);
570                if (stParen != null) {
571                    TSourceToken stInsert = ast.searchToken(TBaseType.rrw_insert, -ast.posinlist);
572                    if (stInsert != null) {
573                        TSourceToken stSemiColon = ast.searchToken(';', -ast.posinlist);
574                        if ((stSemiColon != null) && (stSemiColon.posinlist > stInsert.posinlist)) {
575                            // INSERT INTO test values (16,1), (8,2), (4,4), (2,0), (97, 16);
576                            // VALUES (1);
577                            // don't treat values(1) as insert values
578
579                        } else {
580                            TSourceToken stFrom = ast.searchToken(TBaseType.rrw_from, -ast.posinlist);
581                            if ((stFrom != null) && (stFrom.posinlist > stInsert.posinlist)) {
582                                // Don't treat values after from keyword as an insert values
583                            } else {
584                                ast.tokencode = TBaseType.rrw_postgresql_insert_values;
585                            }
586                        }
587                    }
588                }
589            }
590
591            switch (gst) {
592                case sterror: {
593                    if (ast.tokentype == ETokenType.ttsemicolon) {
594                        appendToken(gcurrentsqlstatement, ast);
595                        onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder);
596                        gst = EFindSqlStateType.stnormal;
597                    } else {
598                        appendToken(gcurrentsqlstatement, ast);
599                    }
600                    break;
601                } //sterror
602
603                case stnormal: {
604                    if ((ast.tokencode == TBaseType.cmtdoublehyphen)
605                            || (ast.tokencode == TBaseType.cmtslashstar)
606                            || (ast.tokencode == TBaseType.lexspace)
607                            || (ast.tokencode == TBaseType.lexnewline)
608                            || (ast.tokentype == ETokenType.ttsemicolon)) {
609                        if (gcurrentsqlstatement != null) {
610                            appendToken(gcurrentsqlstatement, ast);
611                        }
612
613                        if ((lcprevsolidtoken != null) && (ast.tokentype == ETokenType.ttsemicolon)) {
614                            if (lcprevsolidtoken.tokentype == ETokenType.ttsemicolon) {
615                                // ;;;; continuous semicolon, treat it as comment
616                                ast.tokentype = ETokenType.ttsimplecomment;
617                                ast.tokencode = TBaseType.cmtdoublehyphen;
618                            }
619                        }
620
621                        continue;
622                    }
623
624                    if (ast.tokencode == TBaseType.sqlpluscmd) {
625                        gst = EFindSqlStateType.stsqlplus;
626                        gcurrentsqlstatement = new TSqlplusCmdStatement(vendor);
627                        appendToken(gcurrentsqlstatement, ast);
628                        continue;
629                    }
630
631                    // find a token to start sql or plsql mode
632                    gcurrentsqlstatement = sqlcmds.issql(ast, gst, gcurrentsqlstatement);
633
634                    if (gcurrentsqlstatement != null) {
635                        declarePending = 0;
636                        if (gcurrentsqlstatement.isGaussDBStoredProcedure()) {
637                            appendToken(gcurrentsqlstatement, ast);
638                            if (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstplsql_createpackage) {
639                                gst = EFindSqlStateType.stGaussDBPkgSpec;
640                                TPlsqlCreatePackage pkgspec = (TPlsqlCreatePackage) gcurrentsqlstatement;
641                                pkgspec.setPackageNameStr(findPkgName(ast.getNextTokenInChain(), 1));
642                            } else if (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstoraclecreatepackagebody) {
643                                gst = EFindSqlStateType.stGaussDBPkgBody;
644                                TPlsqlCreatePackage pkgspec = (TPlsqlCreatePackage) gcurrentsqlstatement;
645                                pkgspec.setPackageNameStr(findPkgName(ast.getNextTokenInChain(), 2));
646                            } else {
647                                gst = EFindSqlStateType.ststoredprocedure;
648
649                                foundEnd = false;
650                                if ((ast.tokencode == TBaseType.rrw_begin)
651                                        || (ast.tokencode == TBaseType.rrw_package)
652                                        || (ast.searchToken(TBaseType.rrw_package, 4) != null)
653                                ) {
654                                    waitingEnd = 1;
655                                } else if (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstplsql_createtypebody) {
656                                    // CREATE TYPE BODY has AS...END wrapper around member definitions
657                                    // each member has its own BEGIN...END, so we need an extra nesting level
658                                    waitingEnd = 1;
659                                } else if (ast.tokencode == TBaseType.rrw_declare) {
660                                    declarePending = 1;
661                                }
662
663                                if (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstcreateprocedure) {
664                                    // create procedure, create or replace procedure
665                                    stProcedure = ast.searchToken(TBaseType.rrw_procedure, 3);
666                                } else if (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstcreatefunction) {
667                                    // create function, create or replace function
668                                    stFunction = ast.searchToken(TBaseType.rrw_function, 3);
669                                }
670                            }
671                        } else {
672                            gst = EFindSqlStateType.stsql;
673                            appendToken(gcurrentsqlstatement, ast);
674                        }
675                    } else {
676                        //error token found
677
678                        this.syntaxErrors.add(new TSyntaxError(ast.getAstext(), ast.lineNo, (ast.columnNo < 0 ? 0 : ast.columnNo)
679                                , "Error when tokenlize", EErrorType.spwarning, TBaseType.MSG_WARNING_ERROR_WHEN_TOKENIZE, null, ast.posinlist));
680
681                        ast.tokentype = ETokenType.tttokenlizererrortoken;
682                        gst = EFindSqlStateType.sterror;
683
684                        gcurrentsqlstatement = new TUnknownSqlStatement(vendor);
685                        gcurrentsqlstatement.sqlstatementtype = ESqlStatementType.sstinvalid;
686                        appendToken(gcurrentsqlstatement, ast);
687
688                    }
689
690                    break;
691                } // stnormal
692
693                case stsqlplus: {
694                    if (ast.insqlpluscmd) {
695                        appendToken(gcurrentsqlstatement, ast);
696                    } else {
697                        gst = EFindSqlStateType.stnormal; //this token must be newline,
698                        appendToken(gcurrentsqlstatement, ast); // so add it here
699                        onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder);
700                    }
701
702                    break;
703                }//case stsqlplus
704
705                case stsql: {
706                    if (ast.tokentype == ETokenType.ttsemicolon) {
707                        gst = EFindSqlStateType.stnormal;
708                        appendToken(gcurrentsqlstatement, ast);
709                        gcurrentsqlstatement.semicolonended = ast;
710                        onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder);
711                        continue;
712                    }
713
714                    if (sourcetokenlist.sqlplusaftercurtoken()) { //most probably is / cmd
715                        // In Edb, if / appears in regular SQL (non-stored procedure) and is identified as sqlplus,
716                        // it should be a false positive. For example, in division expressions.
717                        // We need to restore / to a regular character /
718                        TSourceToken st = ast.nextSolidToken();
719                        if (st.tokentype == ETokenType.ttslash) {
720                            st.tokencode = '/';
721                        } else { // Non-/ sqlplus commands are still treated as sqlplus, but whether Edb has Oracle-like sqlplus needs further verification
722                            gst = EFindSqlStateType.stnormal;
723                            appendToken(gcurrentsqlstatement, ast);
724                            onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder);
725                            continue;
726                        }
727                    }
728
729                    if (ast.tokencode == TBaseType.cmtdoublehyphen) {
730                        if (ast.toString().trim().endsWith(TBaseType.sqlflow_stmt_delimiter_str)) { // -- sqlflow-delimiter
731                            gst = EFindSqlStateType.stnormal;
732                            onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder);
733                            continue;
734                        }
735                    }
736
737                    appendToken(gcurrentsqlstatement, ast);
738                    break;
739                }//case stsql
740
741                case ststoredprocedure: {
742                    if (ast.tokencode == TBaseType.rrw_postgresql_function_delimiter) {
743                        appendToken(gcurrentsqlstatement, ast);
744                        isOracleStyleRoutine = false;
745                        gst = EFindSqlStateType.ststoredprocedurePgStartBody;
746                        continue;
747                    }
748
749                    if (ast.tokencode == TBaseType.rrw_postgresql_language) {
750                        // check next token which is the language used by this stored procedure
751                        TSourceToken nextSt = ast.nextSolidToken();
752                        if (nextSt != null) {
753                            if (gcurrentsqlstatement instanceof TRoutine) {  // can be TCreateProcedureStmt or TCreateFunctionStmt
754                                TRoutine p = (TRoutine) gcurrentsqlstatement;
755                                p.setRoutineLanguage(nextSt.toString());
756                                isOracleStyleRoutine = false;
757                            }
758                        }
759                    }
760
761                    if ((ast.tokentype == ETokenType.ttsemicolon) && (waitingEnd == 0) && (declarePending == 0)) {
762                        gst = EFindSqlStateType.stnormal;
763                        appendToken(gcurrentsqlstatement, ast);
764                        gcurrentsqlstatement.semicolonended = ast;
765
766                        onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder);
767                        continue;
768                    }
769
770                    // Compound trigger: COMPOUND TRIGGER adds an extra nesting level
771                    // because the body has internal END...;  for each timing point section
772                    // but the outer END trigger_name; is the real end of the trigger.
773                    if (ast.toString().equalsIgnoreCase("COMPOUND")) {
774                        TSourceToken nextSt2 = ast.nextSolidToken();
775                        if (nextSt2 != null && nextSt2.toString().equalsIgnoreCase("TRIGGER")) {
776                            waitingEnd++;
777                        }
778                    }
779
780                    if ((ast.tokencode == TBaseType.rrw_begin)
781                    ) {
782                        waitingEnd++;
783                        if (declarePending > 0) {
784                            declarePending--;
785                        }
786                    } else if (
787                            (ast.tokencode == TBaseType.rrw_declare)
788                                    || (ast.tokencode == TBaseType.rrw_as) || (ast.tokencode == TBaseType.rrw_is)
789                    ) {
790                        TSourceToken next = ast.nextSolidToken();
791                        if ((next != null) && ((next.tokencode == TBaseType.sconst) || (next.tokencode == TBaseType.GAUSSDB_NULL))) {
792                            // CREATE FUNCTION func_add_sql(integer, integer) RETURNS integer
793                            //    AS 'select $1 + $2;'
794                            //    LANGUAGE SQL
795                            //    IMMUTABLE
796                            //    RETURNS NULL ON NULL INPUT;
797                        } else {
798                            declarePending++;
799                        }
800                    } else if ((ast.tokencode == TBaseType.rrw_if)
801                    ) {
802                        if (ast.searchToken(TBaseType.rrw_end, -1) == null) {
803                            //this is not if after END
804                            waitingEnd++;
805                        }
806                    } else if ((ast.tokencode == TBaseType.rrw_case)
807                    ) {
808                        if (ast.searchToken(TBaseType.rrw_end, -1) == null) {
809                            //this is not case after END
810                            waitingEnd++;
811                        }
812                    } else if ((ast.tokencode == TBaseType.rrw_loop)
813                    ) {
814                        if (ast.searchToken(TBaseType.rrw_end, -1) == null) {
815                            //this is not loop after END
816                            waitingEnd++;
817                        }
818                    } else if (ast.tokencode == TBaseType.rrw_end) {
819                        foundEnd = true;
820                        waitingEnd--;
821                        if (waitingEnd < 0) {
822                            waitingEnd = 0;
823                        }
824                    }
825
826                    if ((ast.tokentype == ETokenType.ttslash) && (ast.tokencode == TBaseType.sqlpluscmd)) //and (prevst.NewlineIsLastTokenInTailerToken)) then
827                    {
828                        // TPlsqlStatementParse(asqlstatement).TerminatorToken := ast;
829                        ast.tokenstatus = ETokenStatus.tsignorebyyacc;
830                        gst = EFindSqlStateType.stnormal;
831
832                        onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder);
833
834                        //make / a sqlplus cmd
835                        gcurrentsqlstatement = new TSqlplusCmdStatement(vendor);
836                        appendToken(gcurrentsqlstatement, ast);
837                        onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder);
838                    } else if ((ast.tokentype == ETokenType.ttperiod) && (sourcetokenlist.returnaftercurtoken(false)) && (sourcetokenlist.returnbeforecurtoken(false))) {    // single dot at a separate line
839                        ast.tokenstatus = ETokenStatus.tsignorebyyacc;
840                        gst = EFindSqlStateType.stnormal;
841
842                        onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder);
843
844                        //make ttperiod a sqlplus cmd
845                        gcurrentsqlstatement = new TSqlplusCmdStatement(vendor);
846                        appendToken(gcurrentsqlstatement, ast);
847                        onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder);
848                    } else {
849                        appendToken(gcurrentsqlstatement, ast);
850                        if ((ast.tokentype == ETokenType.ttsemicolon) && (waitingEnd == 0) && (declarePending == 0)
851                                && (foundEnd)
852                        ) {
853                            gst = EFindSqlStateType.stnormal;
854                            onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder);
855                        }
856                    }
857
858                    if (ast.tokencode == TBaseType.sqlpluscmd) {
859                        //change tokencode back to keyword or TBaseType.ident, because sqlplus cmd
860                        //in a sql statement(almost is plsql block) is not really a sqlplus cmd
861                        int m = flexer.getkeywordvalue(ast.getAstext());
862                        if (m != 0) {
863                            ast.tokencode = m;
864                        } else {
865                            ast.tokencode = TBaseType.ident;
866                        }
867                    }
868
869                    if ((gst == EFindSqlStateType.ststoredprocedure) && (ast.tokencode == TBaseType.cmtdoublehyphen)) {
870                        if (ast.toString().trim().endsWith(TBaseType.sqlflow_stmt_delimiter_str)) { // -- sqlflow-delimiter
871                            gst = EFindSqlStateType.stnormal;
872                            onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder);
873                        }
874                    }
875
876                    break;
877                } //ststoredprocedure
878
879                case ststoredprocedurePgStartBody: {
880                    appendToken(gcurrentsqlstatement, ast);
881
882                    if (ast.tokencode == TBaseType.rrw_postgresql_function_delimiter) {
883                        gst = EFindSqlStateType.ststoredprocedurePgEndBody;
884                        continue;
885                    }
886
887                    break;
888                }
889
890                case ststoredprocedurePgEndBody: {
891
892                    if (ast.tokentype == ETokenType.ttsemicolon) {
893                        gst = EFindSqlStateType.stnormal;
894                        appendToken(gcurrentsqlstatement, ast);
895                        gcurrentsqlstatement.semicolonended = ast;
896                        onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder);
897                        continue;
898                    } else if (ast.tokencode == TBaseType.cmtdoublehyphen) {
899                        if (ast.toString().trim().endsWith(TBaseType.sqlflow_stmt_delimiter_str)) { // -- sqlflow-delimiter
900                            gst = EFindSqlStateType.stnormal;
901                            onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder);
902                            continue;
903                        }
904                    }
905
906                    appendToken(gcurrentsqlstatement, ast);
907
908                    if (ast.tokencode == TBaseType.rrw_postgresql_language) {
909                        // check next token which is the language used by this stored procedure
910                        TSourceToken nextSt = ast.nextSolidToken();
911                        if (nextSt != null) {
912                            if (gcurrentsqlstatement instanceof TRoutine) {  // can be TCreateProcedureStmt or TCreateFunctionStmt
913                                TRoutine p = (TRoutine) gcurrentsqlstatement;
914                                p.setRoutineLanguage(nextSt.toString());
915                            }
916                        }
917                    }
918
919                    break;
920                }
921
922                case stGaussDBPkgSpec: {
923                    appendToken(gcurrentsqlstatement, ast);
924
925                    if (ast.tokencode == TBaseType.rrw_end) {
926                        TPlsqlCreatePackage plsqlCreatePackage = (TPlsqlCreatePackage) gcurrentsqlstatement;
927
928                        String pkgName = findPkgName(ast.getNextTokenInChain(), 3);
929                        if ((pkgName != null) && (pkgName.equalsIgnoreCase(plsqlCreatePackage.getPackageNameStr()))) {
930                            gst = EFindSqlStateType.stGaussDBPkgSpecEnd;
931                        }
932                    }
933                    break;
934                }
935                case stGaussDBPkgSpecEnd: {
936                    appendToken(gcurrentsqlstatement, ast);
937                    if (ast.tokencode == ';') {
938                        gst = EFindSqlStateType.stnormal;
939                        onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder);
940                        continue;
941                    }
942                    break;
943                }
944
945                case stGaussDBPkgBody: {
946                    appendToken(gcurrentsqlstatement, ast);
947                    if (ast.tokencode == TBaseType.rrw_end) {
948                        TPlsqlCreatePackage plsqlCreatePackage = (TPlsqlCreatePackage) gcurrentsqlstatement;
949
950                        String pkgName = findPkgName(ast.getNextTokenInChain(), 3);
951                        if ((pkgName != null) && (pkgName.equalsIgnoreCase(plsqlCreatePackage.getPackageNameStr()))) {
952                            gst = EFindSqlStateType.stGaussDBPkgBodyEnd;
953                        }
954                    }
955                    break;
956                }
957                case stGaussDBPkgBodyEnd: {
958                    appendToken(gcurrentsqlstatement, ast);
959                    if (ast.tokencode == ';') {
960                        gst = EFindSqlStateType.stnormal;
961                        onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder);
962                        continue;
963                    }
964                    break;
965                }
966
967            } //switch
968        }//for
969
970        //last statement
971        boolean isSinglePLBlock = parserContext != null && parserContext.isSinglePLBlock();
972        if ((gcurrentsqlstatement != null) &&
973                ((gst == EFindSqlStateType.stsqlplus) || (gst == EFindSqlStateType.stsql)
974                        || (gst == EFindSqlStateType.ststoredprocedure)
975                        || (gst == EFindSqlStateType.ststoredprocedurePgEndBody)
976                        || (gst == EFindSqlStateType.sterror) || (isSinglePLBlock)
977                )) {
978            onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, true, builder);
979        }
980
981        // Populate builder with extracted statements
982        builder.sqlStatements(this.sqlstatements);
983        builder.errorCode(0);
984        builder.errorMessage("");
985    }
986
987    /**
988     * Helper method to append a token to a statement.
989     * <p>
990     * Sets the token's statement reference and adds it to the statement's token list.
991     */
992    private void appendToken(TCustomSqlStatement statement, TSourceToken token) {
993        if (statement == null || token == null) {
994            return;
995        }
996        token.stmt = statement;
997        statement.sourcetokenlist.add(token);
998    }
999
1000    // ========== Phase 4: Statement Parsing ==========
1001
1002    @Override
1003    protected TStatementList performParsing(ParserContext context, TCustomParser parser,
1004                                            TCustomParser secondaryParser, TSourceTokenList tokens,
1005                                            TStatementList rawStatements) {
1006        if (TBaseType.DUMP_RESOLVER_LOG_TO_CONSOLE) {
1007            System.out.println("EdbSqlParser.performParsing() CALLED with " +
1008                    (rawStatements != null ? rawStatements.size() : 0) + " statements");
1009        }
1010
1011        // Store references
1012        this.fparser = (TParserEdb) parser;
1013        this.sourcetokenlist = tokens;
1014        this.parserContext = context;
1015
1016        // Use the raw statements passed from AbstractSqlParser.parse()
1017        // (already extracted - DO NOT re-extract to avoid duplication)
1018        this.sqlstatements = rawStatements;
1019
1020        // Initialize sqlcmds
1021        this.sqlcmds = SqlCmdsFactory.get(vendor);
1022        this.fparser.sqlcmds = this.sqlcmds;
1023
1024        // Initialize global context using inherited method
1025        initializeGlobalContext();
1026
1027        // Parse each statement
1028        for (int i = 0; i < sqlstatements.size(); i++) {
1029            TCustomSqlStatement stmt = sqlstatements.getRawSql(i);
1030            try {
1031                stmt.setFrameStack(frameStack);
1032                int parseResult = stmt.parsestatement(null, false, context.isOnlyNeedRawParseTree());
1033
1034                // Error recovery
1035                boolean doRecover = TBaseType.ENABLE_ERROR_RECOVER_IN_CREATE_TABLE;
1036                if (doRecover && ((parseResult != 0) || (stmt.getErrorCount() > 0))) {
1037                    handleCreateTableErrorRecovery(stmt);
1038                }
1039
1040                // Collect errors
1041                if ((parseResult != 0) || (stmt.getErrorCount() > 0)) {
1042                    copyErrorsFromStatement(stmt);
1043                }
1044            } catch (Exception ex) {
1045                // Use inherited exception handler
1046                handleStatementParsingException(stmt, i, ex);
1047                continue;
1048            }
1049        }
1050
1051        // Clean up frame stack
1052        if (globalFrame != null) {
1053            globalFrame.popMeFromStack(frameStack);
1054        }
1055
1056        return sqlstatements;
1057    }
1058
1059    /**
1060     * Handle CREATE TABLE error recovery.
1061     * <p>
1062     * Migrated from TGSqlParser.doparse() error recovery logic.
1063     * <p>
1064     * Attempts to recover from parsing errors in CREATE TABLE statements by
1065     * marking unparseable table properties as sqlpluscmd and retrying.
1066     * This allows parsing CREATE TABLE statements with vendor-specific extensions
1067     * that may not be in the grammar.
1068     */
1069    private void handleCreateTableErrorRecovery(TCustomSqlStatement stmt) {
1070        // Check if this is a CREATE TABLE or CREATE INDEX statement
1071        if (!(stmt.sqlstatementtype == ESqlStatementType.sstcreatetable ||
1072                stmt.sqlstatementtype == ESqlStatementType.sstcreateindex)) {
1073            return;
1074        }
1075
1076        // Check if strict parsing is disabled
1077        if (TBaseType.c_createTableStrictParsing) {
1078            return;
1079        }
1080
1081        TCustomSqlStatement errorSqlStatement = stmt;
1082
1083        int nested = 0;
1084        boolean isIgnore = false;
1085        boolean isFoundIgnoreToken = false;
1086        TSourceToken firstIgnoreToken = null;
1087
1088        // Iterate through tokens to find the closing parenthesis of table definition
1089        for (int k = 0; k < errorSqlStatement.sourcetokenlist.size(); k++) {
1090            TSourceToken st = errorSqlStatement.sourcetokenlist.get(k);
1091
1092            if (isIgnore) {
1093                // We're past the table definition, mark tokens as ignoreable
1094                if (st.issolidtoken() && (st.tokencode != ';')) {
1095                    isFoundIgnoreToken = true;
1096                    if (firstIgnoreToken == null) {
1097                        firstIgnoreToken = st;
1098                    }
1099                }
1100                // Mark all tokens (except semicolon) as sqlpluscmd to ignore them
1101                if (st.tokencode != ';') {
1102                    st.tokencode = TBaseType.sqlpluscmd;
1103                }
1104                continue;
1105            }
1106
1107            // Track closing parentheses
1108            if (st.tokencode == (int) ')') {
1109                nested--;
1110                if (nested == 0) {
1111                    // Found the closing parenthesis of table definition
1112                    // Check if next tokens are "AS ( SELECT" (CTAS pattern)
1113                    boolean isSelect = false;
1114                    TSourceToken st1 = st.searchToken(TBaseType.rrw_as, 1);
1115                    if (st1 != null) {
1116                        TSourceToken st2 = st.searchToken((int) '(', 2);
1117                        if (st2 != null) {
1118                            TSourceToken st3 = st.searchToken(TBaseType.rrw_select, 3);
1119                            isSelect = (st3 != null);
1120                        }
1121                    }
1122                    // If not a CTAS, start ignoring subsequent tokens
1123                    if (!isSelect) {
1124                        isIgnore = true;
1125                    }
1126                }
1127            }
1128
1129            // Track opening parentheses
1130            if ((st.tokencode == (int) '(') || (st.tokencode == TBaseType.left_parenthesis_2)) {
1131                nested++;
1132            }
1133        }
1134
1135        // If we found ignoreable tokens, clear errors and retry parsing
1136        if (isFoundIgnoreToken) {
1137            errorSqlStatement.clearError();
1138            int retryResult = errorSqlStatement.parsestatement(null, false, parserContext.isOnlyNeedRawParseTree());
1139        }
1140    }
1141
1142    // ========== Phase 5: Semantic Analysis & Interpretation ==========
1143
1144    @Override
1145    protected void performSemanticAnalysis(ParserContext context, TStatementList statements) {
1146        if (!TBaseType.isEnableResolver()) {
1147            return;
1148        }
1149
1150        if (getSyntaxErrors().isEmpty()) {
1151            TSQLResolver resolver = new TSQLResolver(this.globalContext, statements);
1152            resolver.resolve();
1153        }
1154    }
1155
1156    @Override
1157    protected void performInterpreter(ParserContext context, TStatementList statements) {
1158        if (!TBaseType.ENABLE_INTERPRETER) {
1159            return;
1160        }
1161
1162        if (getSyntaxErrors().isEmpty()) {
1163            TGlobalScope interpreterScope = new TGlobalScope(sqlEnv);
1164            TASTEvaluator astEvaluator = new TASTEvaluator(statements, interpreterScope);
1165            astEvaluator.eval();
1166        }
1167    }
1168
1169    // ========== Vendor-Specific Statement Completion ==========
1170
1171    /**
1172     * Edb-specific statement completion logic.
1173     * <p>
1174     * Migrated from TGSqlParser.doongetrawsqlstatementevent() (lines 5129-5141).
1175     * <p>
1176     * For CREATE PROCEDURE/FUNCTION statements, marks Oracle-style routines
1177     * by setting special token codes to distinguish them from PostgreSQL-style routines.
1178     *
1179     * @param statement the completed statement
1180     */
1181    @Override
1182    protected void onRawStatementCompleteVendorSpecific(TCustomSqlStatement statement) {
1183        // First, call parent implementation for PostgreSQL-family logic
1184        // (Edb inherits PostgreSQL compatibility, so this applies too)
1185        super.onRawStatementCompleteVendorSpecific(statement);
1186
1187        // Edb-specific: Mark Oracle-style procedures/functions
1188        // Migrated from TGSqlParser.doongetrawsqlstatementevent() lines 5129-5141
1189        if ((statement.sqlstatementtype == ESqlStatementType.sstcreateprocedure)
1190                || (statement.sqlstatementtype == ESqlStatementType.sstcreatefunction)) {
1191
1192            if (isOracleStyleRoutine) {
1193                if (stFunction != null) {
1194                    stFunction.tokencode = TBaseType.GAUSSDB_FUNCTION_ORA;
1195                } else if (stProcedure != null) {
1196                    stProcedure.tokencode = TBaseType.GAUSSDB_PROCEDURE_ORA;
1197                }
1198            }
1199
1200            // Reset state for next statement
1201            stFunction = null;
1202            stProcedure = null;
1203            isOracleStyleRoutine = true;
1204        }
1205    }
1206
1207    @Override
1208    public EDbVendor getVendor() {
1209        return vendor;
1210    }
1211
1212    @Override
1213    public String toString() {
1214        return "EdbSqlParser{vendor=" + vendor + "}";
1215    }
1216}