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.TLexerGaussDB;
014import gudusoft.gsqlparser.TParserGaussDB;
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 * GaussDB SQL parser implementation.
039 *
040 * <p>This parser handles GaussDB-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 GaussDB 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 TParserGaussDB}</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 GaussDB-specific state tracking</li>
062 * </ul>
063 *
064 * @see SqlParser
065 * @see AbstractSqlParser
066 * @see TLexerGaussDB
067 * @see TParserGaussDB
068 * @since 3.2.0.0
069 */
070public class GaussDbSqlParser extends AbstractSqlParser {
071
072    // ========== Lexer and Parser ==========
073
074    /** The GaussDB lexer used for tokenization */
075    public TLexerGaussDB flexer;
076
077    /** The GaussDB parser used for parsing */
078    private TParserGaussDB 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 GaussDB SQL parser.
096     * <p>
097     * Configures the parser for GaussDB database with semicolon (;) as the default delimiter.
098     */
099    public GaussDbSqlParser() {
100        super(EDbVendor.dbvgaussdb);
101        this.delimiterChar = ';';
102        this.defaultDelimiterStr = ";";
103
104        // Create lexer once - will be reused for all parsing operations
105        this.flexer = new TLexerGaussDB();
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 TParserGaussDB(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        // GaussDB 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        dogaussdbtexttotokenlist();
141    }
142
143    /**
144     * Tokenize GaussDB SQL text into source tokens.
145     * <p>
146     * Migrated from TGSqlParser.dogaussdbtexttotokenlist() (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 dogaussdbtexttotokenlist() {
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/GaussDB 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 GaussDB 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        dogaussdbgetrawsqlstatements(builder);
453    }
454
455    /**
456     * Extract raw SQL statements from token list.
457     * <p>
458     * Migrated from TGSqlParser.dogaussdbgetrawsqlstatements() (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 GaussDB-specific state tracking</li>
466     *   <li>Slash (/) and period (.) terminators for procedural blocks</li>
467     * </ul>
468     */
469    private void dogaussdbgetrawsqlstatements(SqlParseResult.Builder builder) {
470        int waitingEnd = 0;
471        boolean foundEnd = false, enterDeclare = false;
472        isOracleStyleRoutine = true;
473        stProcedure = null;
474        stFunction = null;
475
476        if (TBaseType.assigned(sqlstatements)) sqlstatements.clear();
477        if (!TBaseType.assigned(sourcetokenlist)) {
478            builder.errorCode(-1);
479            return;
480        }
481
482        gcurrentsqlstatement = null;
483        EFindSqlStateType gst = EFindSqlStateType.stnormal;
484        TSourceToken lcprevsolidtoken = null, ast = null;
485
486        // Handle single PL block mode
487        if (parserContext != null && parserContext.isSinglePLBlock()) {
488            gcurrentsqlstatement = new TCommonBlock(vendor);
489        }
490
491        for (int i = 0; i < sourcetokenlist.size(); i++) {
492
493            if ((ast != null) && (ast.issolidtoken()))
494                lcprevsolidtoken = ast;
495
496            ast = sourcetokenlist.get(i);
497            sourcetokenlist.curpos = i;
498
499            if (parserContext != null && parserContext.isSinglePLBlock()) {
500                gcurrentsqlstatement.sourcetokenlist.add(ast);
501                continue;
502            }
503
504            // Token preprocessing: Adjust token codes based on context
505            if (ast.tokencode == TBaseType.JSON_EXIST) {
506                TSourceToken stConstant = ast.searchToken(TBaseType.sconst, 1);
507                if (stConstant == null) {
508                    ast.tokencode = TBaseType.ident;
509                }
510            } else if (ast.tokencode == TBaseType.rrw_postgresql_POSITION) {
511                TSourceToken st1 = ast.nextSolidToken();
512                if (st1 != null) {
513                    if (st1.tokencode == '(') {
514                        ast.tokencode = TBaseType.rrw_postgresql_POSITION_FUNCTION;
515                    }
516                }
517            } else if (ast.tokencode == TBaseType.rrw_postgresql_ordinality) {
518                TSourceToken lcprevst = getprevsolidtoken(ast);
519
520                if (lcprevst != null) {
521                    if (lcprevst.tokencode == TBaseType.rrw_with) {
522                        TSourceToken lcnextst = ast.nextSolidToken();
523                        if ((lcnextst != null) && (lcnextst.tokencode == TBaseType.rrw_as)) {
524                            // with ordinality as (select 1 as x) select * from ordinality;
525                            // don't change with to rrw_postgresql_with_lookahead
526                        } else {
527                            lcprevst.tokencode = TBaseType.rrw_postgresql_with_lookahead;
528                        }
529
530                    }
531                }
532            } else if ((ast.tokencode == TBaseType.rrw_postgresql_filter)
533                    || (ast.tokencode == TBaseType.GAUSSDB_TO_BINARY_DOUBLE)
534                    || (ast.tokencode == TBaseType.GAUSSDB_TO_BINARY_FLOAT)
535                    || (ast.tokencode == TBaseType.GAUSSDB_TO_NUMBER)
536                    || (ast.tokencode == TBaseType.GAUSSDB_TO_DATE)
537                    || (ast.tokencode == TBaseType.GAUSSDB_TO_TIMESTAMP)
538                    || (ast.tokencode == TBaseType.GAUSSDB_TO_TIMESTAMP_TZ)
539            ) {
540                TSourceToken st1 = ast.nextSolidToken();
541                if (st1 != null) {
542                    if (st1.tokencode == '(') {
543                        // Keep as function
544                    } else {
545                        ast.tokencode = TBaseType.ident;
546                    }
547                }
548            } else if (ast.tokencode == TBaseType.rrw_postgresql_jsonb) {
549                TSourceToken st1 = ast.nextSolidToken();
550                if (st1 != null) {
551                    if (st1.tokencode == '?') {
552                        st1.tokencode = TBaseType.OP_JSONB_QUESTION;
553                    }
554                }
555            } else if (ast.tokencode == TBaseType.GAUSSDB_TO_NUMBER) {
556                TSourceToken st1 = ast.searchToken('(', 1);
557                if (st1 == null) {
558                    ast.tokencode = TBaseType.ident;
559                }
560            } else if (ast.tokencode == '?') {
561                TSourceToken st1 = ast.nextSolidToken();
562                if (st1 != null) {
563                    if (st1.tokencode == TBaseType.sconst) {
564                        ast.tokencode = TBaseType.OP_JSONB_QUESTION;
565                    }
566                }
567            } else if (ast.tokencode == TBaseType.rrw_values) {
568                TSourceToken stParen = ast.searchToken('(', 1);
569                if (stParen != null) {
570                    TSourceToken stInsert = ast.searchToken(TBaseType.rrw_insert, -ast.posinlist);
571                    if (stInsert != null) {
572                        TSourceToken stSemiColon = ast.searchToken(';', -ast.posinlist);
573                        if ((stSemiColon != null) && (stSemiColon.posinlist > stInsert.posinlist)) {
574                            // INSERT INTO test values (16,1), (8,2), (4,4), (2,0), (97, 16);
575                            // VALUES (1);
576                            // don't treat values(1) as insert values
577
578                        } else {
579                            TSourceToken stFrom = ast.searchToken(TBaseType.rrw_from, -ast.posinlist);
580                            if ((stFrom != null) && (stFrom.posinlist > stInsert.posinlist)) {
581                                // Don't treat values after from keyword as an insert values
582                            } else {
583                                ast.tokencode = TBaseType.rrw_postgresql_insert_values;
584                            }
585                        }
586                    }
587                }
588            }
589
590            switch (gst) {
591                case sterror: {
592                    if (ast.tokentype == ETokenType.ttsemicolon) {
593                        appendToken(gcurrentsqlstatement, ast);
594                        onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder);
595                        gst = EFindSqlStateType.stnormal;
596                    } else {
597                        appendToken(gcurrentsqlstatement, ast);
598                    }
599                    break;
600                } //sterror
601
602                case stnormal: {
603                    if ((ast.tokencode == TBaseType.cmtdoublehyphen)
604                            || (ast.tokencode == TBaseType.cmtslashstar)
605                            || (ast.tokencode == TBaseType.lexspace)
606                            || (ast.tokencode == TBaseType.lexnewline)
607                            || (ast.tokentype == ETokenType.ttsemicolon)) {
608                        if (gcurrentsqlstatement != null) {
609                            appendToken(gcurrentsqlstatement, ast);
610                        }
611
612                        if ((lcprevsolidtoken != null) && (ast.tokentype == ETokenType.ttsemicolon)) {
613                            if (lcprevsolidtoken.tokentype == ETokenType.ttsemicolon) {
614                                // ;;;; continuous semicolon, treat it as comment
615                                ast.tokentype = ETokenType.ttsimplecomment;
616                                ast.tokencode = TBaseType.cmtdoublehyphen;
617                            }
618                        }
619
620                        continue;
621                    }
622
623                    if (ast.tokencode == TBaseType.sqlpluscmd) {
624                        gst = EFindSqlStateType.stsqlplus;
625                        gcurrentsqlstatement = new TSqlplusCmdStatement(vendor);
626                        appendToken(gcurrentsqlstatement, ast);
627                        continue;
628                    }
629
630                    // find a token to start sql or plsql mode
631                    gcurrentsqlstatement = sqlcmds.issql(ast, gst, gcurrentsqlstatement);
632
633                    if (gcurrentsqlstatement != null) {
634                        enterDeclare = false;
635                        if (gcurrentsqlstatement.isGaussDBStoredProcedure()) {
636                            appendToken(gcurrentsqlstatement, ast);
637                            if (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstplsql_createpackage) {
638                                gst = EFindSqlStateType.stGaussDBPkgSpec;
639                                TPlsqlCreatePackage pkgspec = (TPlsqlCreatePackage) gcurrentsqlstatement;
640                                pkgspec.setPackageNameStr(findPkgName(ast.getNextTokenInChain(), 1));
641                            } else if (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstoraclecreatepackagebody) {
642                                gst = EFindSqlStateType.stGaussDBPkgBody;
643                                TPlsqlCreatePackage pkgspec = (TPlsqlCreatePackage) gcurrentsqlstatement;
644                                pkgspec.setPackageNameStr(findPkgName(ast.getNextTokenInChain(), 2));
645                            } else {
646                                gst = EFindSqlStateType.ststoredprocedure;
647
648                                foundEnd = false;
649                                if ((ast.tokencode == TBaseType.rrw_begin)
650                                        || (ast.tokencode == TBaseType.rrw_package)
651                                        || (ast.searchToken(TBaseType.rrw_package, 4) != null)
652                                ) {
653                                    waitingEnd = 1;
654                                } else if (ast.tokencode == TBaseType.rrw_declare) {
655                                    enterDeclare = true;
656                                }
657
658                                if (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstcreateprocedure) {
659                                    // create procedure, create or replace procedure
660                                    stProcedure = ast.searchToken(TBaseType.rrw_procedure, 3);
661                                } else if (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstcreatefunction) {
662                                    // create function, create or replace function
663                                    stFunction = ast.searchToken(TBaseType.rrw_function, 3);
664                                }
665                            }
666                        } else {
667                            gst = EFindSqlStateType.stsql;
668                            appendToken(gcurrentsqlstatement, ast);
669                        }
670                    } else {
671                        //error token found
672
673                        this.syntaxErrors.add(new TSyntaxError(ast.getAstext(), ast.lineNo, (ast.columnNo < 0 ? 0 : ast.columnNo)
674                                , "Error when tokenlize", EErrorType.spwarning, TBaseType.MSG_WARNING_ERROR_WHEN_TOKENIZE, null, ast.posinlist));
675
676                        ast.tokentype = ETokenType.tttokenlizererrortoken;
677                        gst = EFindSqlStateType.sterror;
678
679                        gcurrentsqlstatement = new TUnknownSqlStatement(vendor);
680                        gcurrentsqlstatement.sqlstatementtype = ESqlStatementType.sstinvalid;
681                        appendToken(gcurrentsqlstatement, ast);
682
683                    }
684
685                    break;
686                } // stnormal
687
688                case stsqlplus: {
689                    if (ast.insqlpluscmd) {
690                        appendToken(gcurrentsqlstatement, ast);
691                    } else {
692                        gst = EFindSqlStateType.stnormal; //this token must be newline,
693                        appendToken(gcurrentsqlstatement, ast); // so add it here
694                        onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder);
695                    }
696
697                    break;
698                }//case stsqlplus
699
700                case stsql: {
701                    if (ast.tokentype == ETokenType.ttsemicolon) {
702                        gst = EFindSqlStateType.stnormal;
703                        appendToken(gcurrentsqlstatement, ast);
704                        gcurrentsqlstatement.semicolonended = ast;
705                        onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder);
706                        continue;
707                    }
708
709                    if (sourcetokenlist.sqlplusaftercurtoken()) { //most probably is / cmd
710                        // In GaussDB, if / appears in regular SQL (non-stored procedure) and is identified as sqlplus,
711                        // it should be a false positive. For example, in division expressions.
712                        // We need to restore / to a regular character /
713                        TSourceToken st = ast.nextSolidToken();
714                        if (st.tokentype == ETokenType.ttslash) {
715                            st.tokencode = '/';
716                        } else { // Non-/ sqlplus commands are still treated as sqlplus, but whether GaussDB has Oracle-like sqlplus needs further verification
717                            gst = EFindSqlStateType.stnormal;
718                            appendToken(gcurrentsqlstatement, ast);
719                            onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder);
720                            continue;
721                        }
722                    }
723
724                    if (ast.tokencode == TBaseType.cmtdoublehyphen) {
725                        if (ast.toString().trim().endsWith(TBaseType.sqlflow_stmt_delimiter_str)) { // -- sqlflow-delimiter
726                            gst = EFindSqlStateType.stnormal;
727                            onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder);
728                            continue;
729                        }
730                    }
731
732                    appendToken(gcurrentsqlstatement, ast);
733                    break;
734                }//case stsql
735
736                case ststoredprocedure: {
737                    if (ast.tokencode == TBaseType.rrw_postgresql_function_delimiter) {
738                        appendToken(gcurrentsqlstatement, ast);
739                        isOracleStyleRoutine = false;
740                        gst = EFindSqlStateType.ststoredprocedurePgStartBody;
741                        continue;
742                    }
743
744                    if (ast.tokencode == TBaseType.rrw_postgresql_language) {
745                        // check next token which is the language used by this stored procedure
746                        TSourceToken nextSt = ast.nextSolidToken();
747                        if (nextSt != null) {
748                            if (gcurrentsqlstatement instanceof TRoutine) {  // can be TCreateProcedureStmt or TCreateFunctionStmt
749                                TRoutine p = (TRoutine) gcurrentsqlstatement;
750                                p.setRoutineLanguage(nextSt.toString());
751                                isOracleStyleRoutine = false;
752                            }
753                        }
754                    }
755
756                    if ((ast.tokentype == ETokenType.ttsemicolon) && (waitingEnd == 0) && (!enterDeclare)) {
757                        gst = EFindSqlStateType.stnormal;
758                        appendToken(gcurrentsqlstatement, ast);
759                        gcurrentsqlstatement.semicolonended = ast;
760
761                        onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder);
762                        continue;
763                    }
764
765                    if ((ast.tokencode == TBaseType.rrw_begin)
766                    ) {
767                        waitingEnd++;
768                        enterDeclare = false;
769                    } else if (
770                            (ast.tokencode == TBaseType.rrw_declare)
771                                    || (ast.tokencode == TBaseType.rrw_as) || (ast.tokencode == TBaseType.rrw_is)
772                    ) {
773                        TSourceToken next = ast.nextSolidToken();
774                        if ((next != null) && ((next.tokencode == TBaseType.sconst) || (next.tokencode == TBaseType.GAUSSDB_NULL))) {
775                            // CREATE FUNCTION func_add_sql(integer, integer) RETURNS integer
776                            //    AS 'select $1 + $2;'
777                            //    LANGUAGE SQL
778                            //    IMMUTABLE
779                            //    RETURNS NULL ON NULL INPUT;
780                        } else {
781                            enterDeclare = true;
782                        }
783                    } else if ((ast.tokencode == TBaseType.rrw_if)
784                    ) {
785                        if (ast.searchToken(TBaseType.rrw_end, -1) == null) {
786                            //this is not if after END
787                            waitingEnd++;
788                        }
789                    } else if ((ast.tokencode == TBaseType.rrw_case)
790                    ) {
791                        if (ast.searchToken(TBaseType.rrw_end, -1) == null) {
792                            //this is not case after END
793                            waitingEnd++;
794                        }
795                    } else if ((ast.tokencode == TBaseType.rrw_loop)
796                    ) {
797                        if (ast.searchToken(TBaseType.rrw_end, -1) == null) {
798                            //this is not loop after END
799                            waitingEnd++;
800                        }
801                    } else if (ast.tokencode == TBaseType.rrw_end) {
802                        foundEnd = true;
803                        waitingEnd--;
804                        if (waitingEnd < 0) {
805                            waitingEnd = 0;
806                        }
807                    }
808
809                    if ((ast.tokentype == ETokenType.ttslash) && (ast.tokencode == TBaseType.sqlpluscmd)) //and (prevst.NewlineIsLastTokenInTailerToken)) then
810                    {
811                        // TPlsqlStatementParse(asqlstatement).TerminatorToken := ast;
812                        ast.tokenstatus = ETokenStatus.tsignorebyyacc;
813                        gst = EFindSqlStateType.stnormal;
814
815                        onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder);
816
817                        //make / a sqlplus cmd
818                        gcurrentsqlstatement = new TSqlplusCmdStatement(vendor);
819                        appendToken(gcurrentsqlstatement, ast);
820                        onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder);
821                    } else if ((ast.tokentype == ETokenType.ttperiod) && (sourcetokenlist.returnaftercurtoken(false)) && (sourcetokenlist.returnbeforecurtoken(false))) {    // single dot at a separate line
822                        ast.tokenstatus = ETokenStatus.tsignorebyyacc;
823                        gst = EFindSqlStateType.stnormal;
824
825                        onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder);
826
827                        //make ttperiod a sqlplus cmd
828                        gcurrentsqlstatement = new TSqlplusCmdStatement(vendor);
829                        appendToken(gcurrentsqlstatement, ast);
830                        onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder);
831                    } else {
832                        appendToken(gcurrentsqlstatement, ast);
833                        if ((ast.tokentype == ETokenType.ttsemicolon) && (waitingEnd == 0) && (!enterDeclare)
834                                && (foundEnd)
835                        ) {
836                            gst = EFindSqlStateType.stnormal;
837                            onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder);
838                        }
839                    }
840
841                    if (ast.tokencode == TBaseType.sqlpluscmd) {
842                        //change tokencode back to keyword or TBaseType.ident, because sqlplus cmd
843                        //in a sql statement(almost is plsql block) is not really a sqlplus cmd
844                        int m = flexer.getkeywordvalue(ast.getAstext());
845                        if (m != 0) {
846                            ast.tokencode = m;
847                        } else {
848                            ast.tokencode = TBaseType.ident;
849                        }
850                    }
851
852                    if ((gst == EFindSqlStateType.ststoredprocedure) && (ast.tokencode == TBaseType.cmtdoublehyphen)) {
853                        if (ast.toString().trim().endsWith(TBaseType.sqlflow_stmt_delimiter_str)) { // -- sqlflow-delimiter
854                            gst = EFindSqlStateType.stnormal;
855                            onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder);
856                        }
857                    }
858
859                    break;
860                } //ststoredprocedure
861
862                case ststoredprocedurePgStartBody: {
863                    appendToken(gcurrentsqlstatement, ast);
864
865                    if (ast.tokencode == TBaseType.rrw_postgresql_function_delimiter) {
866                        gst = EFindSqlStateType.ststoredprocedurePgEndBody;
867                        continue;
868                    }
869
870                    break;
871                }
872
873                case ststoredprocedurePgEndBody: {
874
875                    if (ast.tokentype == ETokenType.ttsemicolon) {
876                        gst = EFindSqlStateType.stnormal;
877                        appendToken(gcurrentsqlstatement, ast);
878                        gcurrentsqlstatement.semicolonended = ast;
879                        onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder);
880                        continue;
881                    } else if (ast.tokencode == TBaseType.cmtdoublehyphen) {
882                        if (ast.toString().trim().endsWith(TBaseType.sqlflow_stmt_delimiter_str)) { // -- sqlflow-delimiter
883                            gst = EFindSqlStateType.stnormal;
884                            onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder);
885                            continue;
886                        }
887                    }
888
889                    appendToken(gcurrentsqlstatement, ast);
890
891                    if (ast.tokencode == TBaseType.rrw_postgresql_language) {
892                        // check next token which is the language used by this stored procedure
893                        TSourceToken nextSt = ast.nextSolidToken();
894                        if (nextSt != null) {
895                            if (gcurrentsqlstatement instanceof TRoutine) {  // can be TCreateProcedureStmt or TCreateFunctionStmt
896                                TRoutine p = (TRoutine) gcurrentsqlstatement;
897                                p.setRoutineLanguage(nextSt.toString());
898                            }
899                        }
900                    }
901
902                    break;
903                }
904
905                case stGaussDBPkgSpec: {
906                    appendToken(gcurrentsqlstatement, ast);
907
908                    if (ast.tokencode == TBaseType.rrw_end) {
909                        TPlsqlCreatePackage plsqlCreatePackage = (TPlsqlCreatePackage) gcurrentsqlstatement;
910
911                        String pkgName = findPkgName(ast.getNextTokenInChain(), 3);
912                        if ((pkgName != null) && (pkgName.equalsIgnoreCase(plsqlCreatePackage.getPackageNameStr()))) {
913                            gst = EFindSqlStateType.stGaussDBPkgSpecEnd;
914                        }
915                    }
916                    break;
917                }
918                case stGaussDBPkgSpecEnd: {
919                    appendToken(gcurrentsqlstatement, ast);
920                    if (ast.tokencode == ';') {
921                        gst = EFindSqlStateType.stnormal;
922                        onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder);
923                        continue;
924                    }
925                    break;
926                }
927
928                case stGaussDBPkgBody: {
929                    appendToken(gcurrentsqlstatement, ast);
930                    if (ast.tokencode == TBaseType.rrw_end) {
931                        TPlsqlCreatePackage plsqlCreatePackage = (TPlsqlCreatePackage) gcurrentsqlstatement;
932
933                        String pkgName = findPkgName(ast.getNextTokenInChain(), 3);
934                        if ((pkgName != null) && (pkgName.equalsIgnoreCase(plsqlCreatePackage.getPackageNameStr()))) {
935                            gst = EFindSqlStateType.stGaussDBPkgBodyEnd;
936                        }
937                    }
938                    break;
939                }
940                case stGaussDBPkgBodyEnd: {
941                    appendToken(gcurrentsqlstatement, ast);
942                    if (ast.tokencode == ';') {
943                        gst = EFindSqlStateType.stnormal;
944                        onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder);
945                        continue;
946                    }
947                    break;
948                }
949
950            } //switch
951        }//for
952
953        //last statement
954        boolean isSinglePLBlock = parserContext != null && parserContext.isSinglePLBlock();
955        if ((gcurrentsqlstatement != null) &&
956                ((gst == EFindSqlStateType.stsqlplus) || (gst == EFindSqlStateType.stsql)
957                        || (gst == EFindSqlStateType.ststoredprocedure)
958                        || (gst == EFindSqlStateType.ststoredprocedurePgEndBody)
959                        || (gst == EFindSqlStateType.sterror) || (isSinglePLBlock)
960                )) {
961            onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, true, builder);
962        }
963
964        // Populate builder with extracted statements
965        builder.sqlStatements(this.sqlstatements);
966        builder.errorCode(0);
967        builder.errorMessage("");
968    }
969
970    /**
971     * Helper method to append a token to a statement.
972     * <p>
973     * Sets the token's statement reference and adds it to the statement's token list.
974     */
975    private void appendToken(TCustomSqlStatement statement, TSourceToken token) {
976        if (statement == null || token == null) {
977            return;
978        }
979        token.stmt = statement;
980        statement.sourcetokenlist.add(token);
981    }
982
983    // ========== Phase 4: Statement Parsing ==========
984
985    @Override
986    protected TStatementList performParsing(ParserContext context, TCustomParser parser,
987                                            TCustomParser secondaryParser, TSourceTokenList tokens,
988                                            TStatementList rawStatements) {
989        if (TBaseType.DUMP_RESOLVER_LOG_TO_CONSOLE) {
990            System.out.println("GaussDbSqlParser.performParsing() CALLED with " +
991                    (rawStatements != null ? rawStatements.size() : 0) + " statements");
992        }
993
994        // Store references
995        this.fparser = (TParserGaussDB) parser;
996        this.sourcetokenlist = tokens;
997        this.parserContext = context;
998
999        // Use the raw statements passed from AbstractSqlParser.parse()
1000        // (already extracted - DO NOT re-extract to avoid duplication)
1001        this.sqlstatements = rawStatements;
1002
1003        // Initialize sqlcmds
1004        this.sqlcmds = SqlCmdsFactory.get(vendor);
1005        this.fparser.sqlcmds = this.sqlcmds;
1006
1007        // Initialize global context using inherited method
1008        initializeGlobalContext();
1009
1010        // Parse each statement
1011        for (int i = 0; i < sqlstatements.size(); i++) {
1012            TCustomSqlStatement stmt = sqlstatements.getRawSql(i);
1013            try {
1014                stmt.setFrameStack(frameStack);
1015                int parseResult = stmt.parsestatement(null, false, context.isOnlyNeedRawParseTree());
1016
1017                // Error recovery
1018                boolean doRecover = TBaseType.ENABLE_ERROR_RECOVER_IN_CREATE_TABLE;
1019                if (doRecover && ((parseResult != 0) || (stmt.getErrorCount() > 0))) {
1020                    handleCreateTableErrorRecovery(stmt);
1021                }
1022
1023                // Collect errors
1024                if ((parseResult != 0) || (stmt.getErrorCount() > 0)) {
1025                    copyErrorsFromStatement(stmt);
1026                }
1027            } catch (Exception ex) {
1028                // Use inherited exception handler
1029                handleStatementParsingException(stmt, i, ex);
1030                continue;
1031            }
1032        }
1033
1034        // Clean up frame stack
1035        if (globalFrame != null) {
1036            globalFrame.popMeFromStack(frameStack);
1037        }
1038
1039        return sqlstatements;
1040    }
1041
1042    /**
1043     * Handle CREATE TABLE error recovery.
1044     * <p>
1045     * Migrated from TGSqlParser.doparse() error recovery logic.
1046     * <p>
1047     * Attempts to recover from parsing errors in CREATE TABLE statements by
1048     * marking unparseable table properties as sqlpluscmd and retrying.
1049     * This allows parsing CREATE TABLE statements with vendor-specific extensions
1050     * that may not be in the grammar.
1051     */
1052    private void handleCreateTableErrorRecovery(TCustomSqlStatement stmt) {
1053        // Check if this is a CREATE TABLE or CREATE INDEX statement
1054        if (!(stmt.sqlstatementtype == ESqlStatementType.sstcreatetable ||
1055                stmt.sqlstatementtype == ESqlStatementType.sstcreateindex)) {
1056            return;
1057        }
1058
1059        // Check if strict parsing is disabled
1060        if (TBaseType.c_createTableStrictParsing) {
1061            return;
1062        }
1063
1064        TCustomSqlStatement errorSqlStatement = stmt;
1065
1066        int nested = 0;
1067        boolean isIgnore = false;
1068        boolean isFoundIgnoreToken = false;
1069        TSourceToken firstIgnoreToken = null;
1070
1071        // Iterate through tokens to find the closing parenthesis of table definition
1072        for (int k = 0; k < errorSqlStatement.sourcetokenlist.size(); k++) {
1073            TSourceToken st = errorSqlStatement.sourcetokenlist.get(k);
1074
1075            if (isIgnore) {
1076                // We're past the table definition, mark tokens as ignoreable
1077                if (st.issolidtoken() && (st.tokencode != ';')) {
1078                    isFoundIgnoreToken = true;
1079                    if (firstIgnoreToken == null) {
1080                        firstIgnoreToken = st;
1081                    }
1082                }
1083                // Mark all tokens (except semicolon) as sqlpluscmd to ignore them
1084                if (st.tokencode != ';') {
1085                    st.tokencode = TBaseType.sqlpluscmd;
1086                }
1087                continue;
1088            }
1089
1090            // Track closing parentheses
1091            if (st.tokencode == (int) ')') {
1092                nested--;
1093                if (nested == 0) {
1094                    // Found the closing parenthesis of table definition
1095                    // Check if next tokens are "AS ( SELECT" (CTAS pattern)
1096                    boolean isSelect = false;
1097                    TSourceToken st1 = st.searchToken(TBaseType.rrw_as, 1);
1098                    if (st1 != null) {
1099                        TSourceToken st2 = st.searchToken((int) '(', 2);
1100                        if (st2 != null) {
1101                            TSourceToken st3 = st.searchToken(TBaseType.rrw_select, 3);
1102                            isSelect = (st3 != null);
1103                        }
1104                    }
1105                    // If not a CTAS, start ignoring subsequent tokens
1106                    if (!isSelect) {
1107                        isIgnore = true;
1108                    }
1109                }
1110            }
1111
1112            // Track opening parentheses
1113            if ((st.tokencode == (int) '(') || (st.tokencode == TBaseType.left_parenthesis_2)) {
1114                nested++;
1115            }
1116        }
1117
1118        // If we found ignoreable tokens, clear errors and retry parsing
1119        if (isFoundIgnoreToken) {
1120            errorSqlStatement.clearError();
1121            int retryResult = errorSqlStatement.parsestatement(null, false, parserContext.isOnlyNeedRawParseTree());
1122        }
1123    }
1124
1125    // ========== Phase 5: Semantic Analysis & Interpretation ==========
1126
1127    @Override
1128    protected void performSemanticAnalysis(ParserContext context, TStatementList statements) {
1129        if (!TBaseType.isEnableResolver()) {
1130            return;
1131        }
1132
1133        if (getSyntaxErrors().isEmpty()) {
1134            TSQLResolver resolver = new TSQLResolver(this.globalContext, statements);
1135            resolver.resolve();
1136        }
1137    }
1138
1139    @Override
1140    protected void performInterpreter(ParserContext context, TStatementList statements) {
1141        if (!TBaseType.ENABLE_INTERPRETER) {
1142            return;
1143        }
1144
1145        if (getSyntaxErrors().isEmpty()) {
1146            TGlobalScope interpreterScope = new TGlobalScope(sqlEnv);
1147            TASTEvaluator astEvaluator = new TASTEvaluator(statements, interpreterScope);
1148            astEvaluator.eval();
1149        }
1150    }
1151
1152    // ========== Vendor-Specific Statement Completion ==========
1153
1154    /**
1155     * GaussDB-specific statement completion logic.
1156     * <p>
1157     * Migrated from TGSqlParser.doongetrawsqlstatementevent() (lines 5129-5141).
1158     * <p>
1159     * For CREATE PROCEDURE/FUNCTION statements, marks Oracle-style routines
1160     * by setting special token codes to distinguish them from PostgreSQL-style routines.
1161     *
1162     * @param statement the completed statement
1163     */
1164    @Override
1165    protected void onRawStatementCompleteVendorSpecific(TCustomSqlStatement statement) {
1166        // First, call parent implementation for PostgreSQL-family logic
1167        // (GaussDB inherits PostgreSQL compatibility, so this applies too)
1168        super.onRawStatementCompleteVendorSpecific(statement);
1169
1170        // GaussDB-specific: Mark Oracle-style procedures/functions
1171        // Migrated from TGSqlParser.doongetrawsqlstatementevent() lines 5129-5141
1172        if ((statement.sqlstatementtype == ESqlStatementType.sstcreateprocedure)
1173                || (statement.sqlstatementtype == ESqlStatementType.sstcreatefunction)) {
1174
1175            if (isOracleStyleRoutine) {
1176                if (stFunction != null) {
1177                    stFunction.tokencode = TBaseType.GAUSSDB_FUNCTION_ORA;
1178                } else if (stProcedure != null) {
1179                    stProcedure.tokencode = TBaseType.GAUSSDB_PROCEDURE_ORA;
1180                }
1181            }
1182
1183            // Reset state for next statement
1184            stFunction = null;
1185            stProcedure = null;
1186            isOracleStyleRoutine = true;
1187        }
1188    }
1189
1190    @Override
1191    public EDbVendor getVendor() {
1192        return vendor;
1193    }
1194
1195    @Override
1196    public String toString() {
1197        return "GaussDbSqlParser{vendor=" + vendor + "}";
1198    }
1199}