001package gudusoft.gsqlparser.parser;
002
003import gudusoft.gsqlparser.EDbVendor;
004import gudusoft.gsqlparser.TBaseType;
005import gudusoft.gsqlparser.TCustomLexer;
006import gudusoft.gsqlparser.TCustomParser;
007import gudusoft.gsqlparser.TCustomSqlStatement;
008import gudusoft.gsqlparser.TLexerGreenplum;
009import gudusoft.gsqlparser.TParserGreenplum;
010import gudusoft.gsqlparser.TSourceToken;
011import gudusoft.gsqlparser.TSourceTokenList;
012import gudusoft.gsqlparser.TStatementList;
013import gudusoft.gsqlparser.TSyntaxError;
014import gudusoft.gsqlparser.EFindSqlStateType;
015import gudusoft.gsqlparser.ETokenType;
016import gudusoft.gsqlparser.ETokenStatus;
017import gudusoft.gsqlparser.ESqlStatementType;
018import gudusoft.gsqlparser.EErrorType;
019import gudusoft.gsqlparser.stmt.TUnknownSqlStatement;
020import gudusoft.gsqlparser.stmt.greenplum.TSlashCommand;
021import gudusoft.gsqlparser.stmt.TRoutine;
022import gudusoft.gsqlparser.sqlcmds.ISqlCmds;
023import gudusoft.gsqlparser.sqlcmds.SqlCmdsFactory;
024import gudusoft.gsqlparser.compiler.TContext;
025import gudusoft.gsqlparser.sqlenv.TSQLEnv;
026import gudusoft.gsqlparser.compiler.TGlobalScope;
027import gudusoft.gsqlparser.compiler.TFrame;
028import gudusoft.gsqlparser.resolver.TSQLResolver;
029import gudusoft.gsqlparser.TLog;
030import gudusoft.gsqlparser.compiler.TASTEvaluator;
031
032import java.io.BufferedReader;
033import java.util.ArrayList;
034import java.util.List;
035import java.util.Stack;
036
037/**
038 * Greenplum database SQL parser implementation.
039 *
040 * <p>This parser handles Greenplum-specific SQL syntax including:
041 * <ul>
042 *   <li>PL/pgSQL blocks (procedures, functions, triggers)</li>
043 *   <li>Dollar-quoted strings ($$ ... $$)</li>
044 *   <li>Backslash meta commands (\d, \dt, etc.)</li>
045 *   <li>PostgreSQL-compatible syntax extensions</li>
046 *   <li>Special token handling (INNER, NOT DEFERRABLE, etc.)</li>
047 * </ul>
048 *
049 * <p><b>Design Notes:</b>
050 * <ul>
051 *   <li>Extends {@link AbstractSqlParser} using the template method pattern</li>
052 *   <li>Uses {@link TLexerGreenplum} for tokenization</li>
053 *   <li>Uses {@link TParserGreenplum} for parsing</li>
054 *   <li>Delimiter character: '/' for PL/pgSQL blocks</li>
055 * </ul>
056 *
057 * <p><b>Usage Example:</b>
058 * <pre>
059 * // Get Greenplum parser from factory
060 * SqlParser parser = SqlParserFactory.get(EDbVendor.dbvgreenplum);
061 *
062 * // Build context
063 * ParserContext context = new ParserContext.Builder(EDbVendor.dbvgreenplum)
064 *     .sqlText("SELECT * FROM employees WHERE dept_id = 10")
065 *     .build();
066 *
067 * // Parse
068 * SqlParseResult result = parser.parse(context);
069 *
070 * // Access statements
071 * TStatementList statements = result.getSqlStatements();
072 * </pre>
073 *
074 * @see SqlParser
075 * @see AbstractSqlParser
076 * @see TLexerGreenplum
077 * @see TParserGreenplum
078 * @since 3.3.1.0
079 */
080public class GreenplumSqlParser extends AbstractSqlParser {
081
082    /**
083     * Construct Greenplum SQL parser.
084     * <p>
085     * Configures the parser for Greenplum database with default delimiter (/).
086     * <p>
087     * Following the original TGSqlParser pattern, the lexer and parser are
088     * created once in the constructor and reused for all parsing operations.
089     */
090    public GreenplumSqlParser() {
091        super(EDbVendor.dbvgreenplum);
092        this.delimiterChar = '/';
093        this.defaultDelimiterStr = "/";
094
095        // Create lexer once - will be reused for all parsing operations
096        this.flexer = new TLexerGreenplum();
097        this.flexer.delimiterchar = this.delimiterChar;
098        this.flexer.defaultDelimiterStr = this.defaultDelimiterStr;
099
100        // Set parent's lexer reference for shared tokenization logic
101        this.lexer = this.flexer;
102
103        // Create parser once - will be reused for all parsing operations
104        this.fparser = new TParserGreenplum(null);
105        this.fparser.lexer = this.flexer;
106    }
107
108    // ========== Parser Components ==========
109
110    /** The Greenplum lexer used for tokenization */
111    public TLexerGreenplum flexer;
112
113    /** SQL parser (for Greenplum statements) */
114    private TParserGreenplum fparser;
115
116    /** Current statement being built during extraction */
117    private TCustomSqlStatement gcurrentsqlstatement;
118
119    // Note: Global context and frame stack fields inherited from AbstractSqlParser:
120    // - protected TContext globalContext
121    // - protected TSQLEnv sqlEnv
122    // - protected Stack<TFrame> frameStack
123    // - protected TFrame globalFrame
124
125    // ========== AbstractSqlParser Abstract Methods Implementation ==========
126
127    /**
128     * Return the Greenplum lexer instance.
129     */
130    @Override
131    protected TCustomLexer getLexer(ParserContext context) {
132        return this.flexer;
133    }
134
135    /**
136     * Return the Greenplum SQL parser instance with updated token list.
137     */
138    @Override
139    protected TCustomParser getParser(ParserContext context, TSourceTokenList tokens) {
140        this.fparser.sourcetokenlist = tokens;
141        return this.fparser;
142    }
143
144    /**
145     * Greenplum doesn't use secondary parser (unlike Oracle with PL/SQL parser).
146     */
147    @Override
148    protected TCustomParser getSecondaryParser(ParserContext context, TSourceTokenList tokens) {
149        return null;
150    }
151
152    /**
153     * Call Greenplum-specific tokenization logic.
154     * <p>
155     * Delegates to dogreenplumtexttotokenlist which handles Greenplum's
156     * tokenization including backslash commands, dollar-quoted strings, and
157     * PostgreSQL-compatible syntax.
158     */
159    @Override
160    protected void tokenizeVendorSql() {
161        dogreenplumtexttotokenlist();
162    }
163
164    /**
165     * Setup vendor parsers before raw statement extraction.
166     * <p>
167     * Injects sqlcmds and sourcetokenlist into the Greenplum parser.
168     */
169    @Override
170    protected void setupVendorParsersForExtraction() {
171        this.fparser.sqlcmds = this.sqlcmds;
172        this.fparser.sourcetokenlist = this.sourcetokenlist;
173    }
174
175    /**
176     * Call Greenplum-specific raw statement extraction.
177     * <p>
178     * Delegates to dogreenplumgetrawsqlstatements which handles:
179     * - PL/pgSQL block boundaries (dollar-quoted strings)
180     * - Backslash meta commands
181     * - Semicolon-separated statements
182     */
183    @Override
184    protected void extractVendorRawStatements(SqlParseResult.Builder builder) {
185        dogreenplumgetrawsqlstatements(builder);
186    }
187
188    /**
189     * Parse all statements in the statement list.
190     * <p>
191     * This is the main parsing loop that processes each raw statement
192     * and converts it into a fully parsed AST.
193     */
194    @Override
195    protected TStatementList performParsing(ParserContext context,
196                                           TCustomParser mainParser,
197                                           TCustomParser secondaryParser,
198                                           TSourceTokenList tokens,
199                                           TStatementList rawStatements) {
200        // Store references
201        this.fparser = (TParserGreenplum) mainParser;
202        this.sourcetokenlist = tokens;
203        this.parserContext = context;
204
205        // Initialize sqlcmds for this parsing session
206        this.sqlcmds = SqlCmdsFactory.get(vendor);
207        this.fparser.sqlcmds = this.sqlcmds;
208
209        // Initialize global context and frame stack
210        initializeGlobalContext();
211
212        // Parse each statement
213        for (int i = 0; i < rawStatements.size(); i++) {
214            TCustomSqlStatement stmt = rawStatements.getRawSql(i);
215            try {
216                stmt.setFrameStack(frameStack);
217                int parseResult = stmt.parsestatement(null, false, context.isOnlyNeedRawParseTree());
218
219                // Vendor-specific post-processing (if needed)
220                afterStatementParsed(stmt);
221
222                // Error recovery for CREATE TABLE statements
223                boolean doRecover = TBaseType.ENABLE_ERROR_RECOVER_IN_CREATE_TABLE;
224                if (doRecover && ((parseResult != 0) || (stmt.getErrorCount() > 0))) {
225                    handleCreateTableErrorRecovery(stmt);
226                }
227
228                // Collect syntax errors from the statement
229                if ((parseResult != 0) || (stmt.getErrorCount() > 0)) {
230                    copyErrorsFromStatement(stmt);
231                }
232            } catch (Exception ex) {
233                // Use inherited exception handler
234                handleStatementParsingException(stmt, i, ex);
235                continue;
236            }
237        }
238
239        // Clean up frame stack
240        if (globalFrame != null) {
241            globalFrame.popMeFromStack(frameStack);
242        }
243
244        return rawStatements;
245    }
246
247    /**
248     * Perform semantic analysis on parsed statements.
249     * <p>
250     * This runs the TSQLResolver to resolve column-to-table relationships,
251     * data flow analysis, and other semantic checks.
252     */
253    @Override
254    protected void performSemanticAnalysis(ParserContext context, TStatementList statements) {
255        if (TBaseType.isEnableResolver() && getSyntaxErrors().isEmpty()) {
256            TSQLResolver resolver = new TSQLResolver(globalContext, statements);
257            resolver.resolve();
258        }
259    }
260
261
262    // ========== Greenplum-Specific Tokenization ==========
263
264    /**
265     * Tokenize Greenplum SQL text into tokens.
266     * <p>
267     * This method handles Greenplum-specific tokenization including:
268     * - Backslash meta commands (\d, \dt, etc.)
269     * - Forward slash as SQL*Plus-like command delimiter
270     * - INNER keyword disambiguation
271     * - NOT DEFERRABLE keyword combination
272     * - Special operators (%, ROWTYPE, etc.)
273     */
274    private void dogreenplumtexttotokenlist() {
275        boolean insqlpluscmd = false;
276        boolean isvalidplace = true;
277        boolean waitingreturnforfloatdiv = false;
278        boolean waitingreturnforsemicolon = false;
279        boolean continuesqlplusatnewline = false;
280
281        TSourceToken lct = null, prevst = null;
282
283        TSourceToken asourcetoken, lcprevst;
284        int yychar;
285
286        asourcetoken = getanewsourcetoken();
287        if (asourcetoken == null) return;
288        yychar = asourcetoken.tokencode;
289
290        while (yychar > 0) {
291            sourcetokenlist.add(asourcetoken);
292            switch (yychar) {
293                case TBaseType.cmtdoublehyphen:
294                case TBaseType.cmtslashstar:
295                case TBaseType.lexspace: {
296                    if (insqlpluscmd) {
297                        asourcetoken.insqlpluscmd = true;
298                    }
299                    break;
300                }
301                case TBaseType.lexnewline: {
302                    if (insqlpluscmd) {
303                        insqlpluscmd = false;
304                        isvalidplace = true;
305
306                        if (continuesqlplusatnewline) {
307                            insqlpluscmd = true;
308                            isvalidplace = false;
309                            asourcetoken.insqlpluscmd = true;
310                        }
311                    }
312
313                    if (waitingreturnforsemicolon) {
314                        isvalidplace = true;
315                    }
316                    if (waitingreturnforfloatdiv) {
317                        isvalidplace = true;
318                        lct.tokencode = TBaseType.sqlpluscmd;
319                        if (lct.tokentype != ETokenType.ttslash) {
320                            lct.tokentype = ETokenType.ttsqlpluscmd;
321                        }
322                    }
323                    flexer.insqlpluscmd = insqlpluscmd;
324                    break;
325                } //case newline
326                default: {
327                    //solid token
328                    continuesqlplusatnewline = false;
329                    waitingreturnforsemicolon = false;
330                    waitingreturnforfloatdiv = false;
331                    if (insqlpluscmd) {
332                        asourcetoken.insqlpluscmd = true;
333                        if (asourcetoken.toString().equalsIgnoreCase("-")) {
334                            continuesqlplusatnewline = true;
335                        }
336                    } else {
337                        if (asourcetoken.tokentype == ETokenType.ttsemicolon) {
338                            waitingreturnforsemicolon = true;
339                        }
340                        if ((asourcetoken.tokentype == ETokenType.ttslash)
341                                && (isvalidplace || (IsValidPlaceForDivToSqlplusCmd(sourcetokenlist, asourcetoken.posinlist)))) {
342                            lct = asourcetoken;
343                            waitingreturnforfloatdiv = true;
344                        }
345                        if ((isvalidplace) && isvalidsqlpluscmdInPostgresql(asourcetoken.toString())) {
346                            asourcetoken.tokencode = TBaseType.sqlpluscmd;
347                            if (asourcetoken.tokentype != ETokenType.ttslash) {
348                                asourcetoken.tokentype = ETokenType.ttsqlpluscmd;
349                            }
350                            insqlpluscmd = true;
351                            flexer.insqlpluscmd = insqlpluscmd;
352                        }
353                    }
354                    isvalidplace = false;
355
356                    // the inner keyword token should be convert to ident when
357                    // next solid token is not join
358
359                    if (prevst != null) {
360                        if (prevst.tokencode == TBaseType.rrw_inner) {
361                            if (asourcetoken.tokencode != flexer.getkeywordvalue("JOIN")) {
362                                prevst.tokencode = TBaseType.ident;
363                            }
364                        }
365
366                        if ((prevst.tokencode == TBaseType.rrw_not)
367                                && (asourcetoken.tokencode == flexer.getkeywordvalue("DEFERRABLE"))) {
368                            prevst.tokencode = flexer.getkeywordvalue("NOT_DEFERRABLE");
369                        }
370                    }
371
372                    if (asourcetoken.tokencode == TBaseType.rrw_inner) {
373                        prevst = asourcetoken;
374                    } else if (asourcetoken.tokencode == TBaseType.rrw_not) {
375                        prevst = asourcetoken;
376                    } else {
377                        prevst = null;
378                    }
379
380                    if ((asourcetoken.tokencode == flexer.getkeywordvalue("DIRECT_LOAD"))
381                            || (asourcetoken.tokencode == flexer.getkeywordvalue("ALL"))) {
382                        // RW_COMPRESS RW_FOR RW_ALL RW_OPERATIONS
383                        // RW_COMPRESS RW_FOR RW_DIRECT_LOAD RW_OPERATIONS
384                        // change rw_for to rw_for1, it conflicts with compress for update in create materialized view
385
386                        lcprevst = getprevsolidtoken(asourcetoken);
387                        if (lcprevst != null) {
388                            if (lcprevst.tokencode == TBaseType.rrw_for)
389                                lcprevst.tokencode = TBaseType.rw_for1;
390                        }
391                    }
392
393                    if (asourcetoken.tokencode == TBaseType.rrw_dense_rank) {
394                        //keep keyword can be column alias, make keep in keep_denserankclause as a different token code
395                        TSourceToken stKeep = asourcetoken.searchToken(TBaseType.rrw_keep, -2);
396                        if (stKeep != null) {
397                            stKeep.tokencode = TBaseType.rrw_keep_before_dense_rank;
398                        }
399                    }
400
401                    if (asourcetoken.tokencode == TBaseType.rrw_greenplum_rowtype) {
402                        TSourceToken stPercent = asourcetoken.searchToken('%', -1);
403                        if (stPercent != null) {
404                            stPercent.tokencode = TBaseType.rowtype_operator;
405                        }
406                    }
407
408                }
409            }
410
411            //flexer.yylexwrap(asourcetoken);
412            asourcetoken = getanewsourcetoken();
413            if (asourcetoken != null) {
414                yychar = asourcetoken.tokencode;
415            } else {
416                yychar = 0;
417
418                if (waitingreturnforfloatdiv) { // / at the end of line treat as sqlplus command
419                    //isvalidplace = true;
420                    lct.tokencode = TBaseType.sqlpluscmd;
421                    if (lct.tokentype != ETokenType.ttslash) {
422                        lct.tokentype = ETokenType.ttsqlpluscmd;
423                    }
424                }
425
426            }
427
428            if ((yychar == 0) && (prevst != null)) {
429                if (prevst.tokencode == TBaseType.rrw_inner) {
430                    prevst.tokencode = TBaseType.ident;
431                }
432            }
433
434        }
435    }
436
437    // ========== Greenplum-Specific Raw Statement Extraction ==========
438
439    /**
440     * Extract raw SQL statements from token list.
441     * <p>
442     * This method handles Greenplum-specific statement boundaries:
443     * - Semicolon (;) for regular SQL statements
444     * - Dollar-quoted strings ($$ ... $$) for PL/pgSQL function bodies
445     * - Backslash commands (\d, \dt, etc.)
446     * - BEGIN/END blocks for stored procedures
447     */
448    private int dogreenplumgetrawsqlstatements(SqlParseResult.Builder builder) {
449        int waitingEnd = 0;
450        boolean foundEnd = false, enterDeclare = false;
451
452        if (TBaseType.assigned(sqlstatements)) sqlstatements.clear();
453        if (!TBaseType.assigned(sourcetokenlist)) return -1;
454
455        gcurrentsqlstatement = null;
456        EFindSqlStateType gst = EFindSqlStateType.stnormal;
457        TSourceToken lcprevsolidtoken = null, ast = null;
458        TSourceToken dollarStringToken = null;
459
460        for (int i = 0; i < sourcetokenlist.size(); i++) {
461
462            if ((ast != null) && (ast.issolidtoken()))
463                lcprevsolidtoken = ast;
464
465            ast = sourcetokenlist.get(i);
466            sourcetokenlist.curpos = i;
467
468            // Special token adjustments
469            if (ast.tokencode == TBaseType.rrw_date) {
470                TSourceToken st1 = ast.nextSolidToken();
471                if (st1 != null) {
472                    if (st1.tokencode == '(') {
473                        ast.tokencode = TBaseType.rrw_greenplum_DATE_FUNCTION;
474                    }
475                }
476            } else if (ast.tokencode == TBaseType.rrw_greenplum_POSITION) {
477                TSourceToken st1 = ast.nextSolidToken();
478                if (st1 != null) {
479                    if (st1.tokencode == '(') {
480                        ast.tokencode = TBaseType.rrw_greenplum_POSITION_FUNCTION;
481                    }
482                }
483            } else if (ast.tokencode == TBaseType.rrw_greenplum_filter) {
484                TSourceToken st1 = ast.nextSolidToken();
485                if (st1 != null) {
486                    if (st1.tokencode == '(') {
487
488                    } else {
489                        ast.tokencode = TBaseType.ident;
490                    }
491                }
492            } else if (ast.tokencode == TBaseType.rrw_values) {
493                TSourceToken stParen = ast.searchToken('(', 1);
494                if (stParen != null) {
495                    TSourceToken stInsert = ast.searchToken(TBaseType.rrw_insert, -ast.posinlist);
496                    if (stInsert != null) {
497                        TSourceToken stSemiColon = ast.searchToken(';', -ast.posinlist);
498                        if ((stSemiColon != null) && (stSemiColon.posinlist > stInsert.posinlist)) {
499//                            INSERT INTO test values (16,1), (8,2), (4,4), (2,0), (97, 16);
500//                            VALUES (1);
501                            // don't treat values(1) as insert values
502
503                        } else {
504                            TSourceToken stFrom = ast.searchToken(TBaseType.rrw_from, -ast.posinlist);
505                            if (stFrom != null) {
506                                // don't treat values after from keyword as a insert values
507                                // insert into inserttest values(10, 20, '40'), (-1, 2, DEFAULT),  ((select 2), (select i from (values(3) ) as foo (i)), 'values are fun!');
508
509                            } else {
510                                ast.tokencode = TBaseType.rrw_greenplum_values_insert;
511                            }
512
513                        }
514
515                    }
516                }
517            }
518
519            switch (gst) {
520                case sterror: {
521                    if (ast.tokentype == ETokenType.ttsemicolon) {
522                        gcurrentsqlstatement.sourcetokenlist.add(ast);
523                        onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
524                        gst = EFindSqlStateType.stnormal;
525                    } else {
526                        gcurrentsqlstatement.sourcetokenlist.add(ast);
527                    }
528                    break;
529                } //sterror
530
531                case stnormal: {
532                    if ((ast.tokencode == TBaseType.cmtdoublehyphen)
533                            || (ast.tokencode == TBaseType.cmtslashstar)
534                            || (ast.tokencode == TBaseType.lexspace)
535                            || (ast.tokencode == TBaseType.lexnewline)
536                            || (ast.tokentype == ETokenType.ttsemicolon)) {
537                        if (gcurrentsqlstatement != null) {
538                            gcurrentsqlstatement.sourcetokenlist.add(ast);
539                        }
540
541                        if ((lcprevsolidtoken != null) && (ast.tokentype == ETokenType.ttsemicolon)) {
542                            if (lcprevsolidtoken.tokentype == ETokenType.ttsemicolon) {
543                                // ;;;; continuous semicolon,treat it as comment
544                                ast.tokentype = ETokenType.ttsimplecomment;
545                                ast.tokencode = TBaseType.cmtdoublehyphen;
546                            }
547                        }
548
549                        continue;
550                    }
551
552                    if (ast.tokencode == '\\') {
553                        gst = EFindSqlStateType.stsqlplus;
554                        gcurrentsqlstatement = new TSlashCommand(vendor);
555                        gcurrentsqlstatement.sourcetokenlist.add(ast);
556                        continue;
557                    }
558
559                    // find a token to start sql or plsql mode
560                    gcurrentsqlstatement = sqlcmds.issql(ast, gst, gcurrentsqlstatement);
561
562                    if (gcurrentsqlstatement != null) {
563                        if (gcurrentsqlstatement.isgreeplumplsql()) {
564                            gst = EFindSqlStateType.ststoredprocedure;
565                            gcurrentsqlstatement.sourcetokenlist.add(ast);
566                            foundEnd = false;
567                            if ((ast.tokencode == TBaseType.rrw_begin)
568                                    || (ast.tokencode == TBaseType.rrw_package)
569                                    || (ast.searchToken(TBaseType.rrw_package, 4) != null)) {
570                                waitingEnd = 1;
571                            } else if (ast.tokencode == TBaseType.rrw_declare) {
572                                enterDeclare = true;
573                            }
574                        } else {
575                            gst = EFindSqlStateType.stsql;
576                            gcurrentsqlstatement.sourcetokenlist.add(ast);
577                        }
578                    } else {
579                        //error token found
580
581                        this.syntaxErrors.add(new TSyntaxError(ast.getAstext(), ast.lineNo, (ast.columnNo < 0 ? 0 : ast.columnNo)
582                                , "Error when tokenlize", EErrorType.spwarning, TBaseType.MSG_WARNING_ERROR_WHEN_TOKENIZE, null, ast.posinlist));
583
584                        ast.tokentype = ETokenType.tttokenlizererrortoken;
585                        gst = EFindSqlStateType.sterror;
586
587                        gcurrentsqlstatement = new TUnknownSqlStatement(vendor);
588                        gcurrentsqlstatement.sqlstatementtype = ESqlStatementType.sstinvalid;
589                        gcurrentsqlstatement.sourcetokenlist.add(ast);
590
591                    }
592
593                    break;
594                } // stnormal
595
596                case stsqlplus: {
597                    if (ast.tokencode == TBaseType.lexnewline) {
598                        gst = EFindSqlStateType.stnormal; //this token must be newline,
599                        gcurrentsqlstatement.sourcetokenlist.add(ast); // so add it here
600                        onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
601                    }
602                    if (ast.tokencode == '\\') {
603                        TSourceToken nextst = ast.searchToken('\\', 1);
604                        if (nextst != null) {
605                            gst = EFindSqlStateType.stnormal;
606                            gcurrentsqlstatement.sourcetokenlist.add(ast);
607                            onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
608                        } else {
609                            gst = EFindSqlStateType.stsqlplus;
610                            gcurrentsqlstatement = new TSlashCommand(vendor);
611                            gcurrentsqlstatement.sourcetokenlist.add(ast);
612                            continue;
613                        }
614                    } else {
615                        {
616                            gcurrentsqlstatement.sourcetokenlist.add(ast);
617                        }
618                    }
619
620                    break;
621                }//case greenplum meta command
622
623                case stsql: {
624                    if (ast.tokentype == ETokenType.ttsemicolon) {
625                        gst = EFindSqlStateType.stnormal;
626                        gcurrentsqlstatement.sourcetokenlist.add(ast);
627                        gcurrentsqlstatement.semicolonended = ast;
628                        onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
629                        continue;
630                    }
631
632                    if (sourcetokenlist.sqlplusaftercurtoken()) //most probably is / cmd
633                    {
634                        gst = EFindSqlStateType.stnormal;
635                        gcurrentsqlstatement.sourcetokenlist.add(ast);
636                        onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
637                        continue;
638                    }
639                    gcurrentsqlstatement.sourcetokenlist.add(ast);
640                    break;
641                }//case stsql
642
643                case ststoredprocedure: {
644                    if (ast.tokencode == TBaseType.rrw_greenplum_function_delimiter) {
645                        gcurrentsqlstatement.sourcetokenlist.add(ast);
646                        gst = EFindSqlStateType.ststoredprocedurePgStartBody;
647                        dollarStringToken = ast;
648                        continue;
649                    }
650
651                    if (ast.tokencode == TBaseType.rrw_greenplum_language) {
652                        // check next token which is the language used by this stored procedure
653                        TSourceToken nextSt = ast.nextSolidToken();
654                        if (nextSt != null) {
655                            if (gcurrentsqlstatement instanceof TRoutine) {  // can be TCreateProcedureStmt or TCreateFunctionStmt
656                                TRoutine p = (TRoutine) gcurrentsqlstatement;
657                                p.setRoutineLanguage(nextSt.toString());
658                            }
659                        }
660                    }
661
662                    if ((ast.tokentype == ETokenType.ttsemicolon) && (waitingEnd == 0) && (!enterDeclare)) {
663                        gst = EFindSqlStateType.stnormal;
664                        gcurrentsqlstatement.sourcetokenlist.add(ast);
665                        gcurrentsqlstatement.semicolonended = ast;
666                        onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
667                        continue;
668                    }
669
670
671                    if ((ast.tokencode == TBaseType.rrw_begin)) {
672                        waitingEnd++;
673                        enterDeclare = false;
674                    } else if ((ast.tokencode == TBaseType.rrw_declare)) {
675                        enterDeclare = true;
676                    } else if ((ast.tokencode == TBaseType.rrw_if)) {
677                        if (ast.searchToken(TBaseType.rrw_end, -1) == null) {
678                            //this is not if after END
679                            if (!((ast.searchToken(TBaseType.rrw_greenplum_exits, 1) != null)
680                                    || (ast.searchToken(TBaseType.rrw_greenplum_exits, 2) != null))) // if not exists, if exists is not a real if statement, so skip those
681                            {
682                                waitingEnd++;
683                            }
684                        }
685                    } else if ((ast.tokencode == TBaseType.rrw_case)) {
686                        if (ast.searchToken(TBaseType.rrw_end, -1) == null) {
687                            //this is not case after END
688                            waitingEnd++;
689                        }
690                    } else if ((ast.tokencode == TBaseType.rrw_loop)) {
691                        if (ast.searchToken(TBaseType.rrw_end, -1) == null) {
692                            //this is not loop after END
693                            waitingEnd++;
694                        }
695                    } else if (ast.tokencode == TBaseType.rrw_end) {
696                        foundEnd = true;
697                        waitingEnd--;
698                        if (waitingEnd < 0) {
699                            waitingEnd = 0;
700                        }
701                    }
702
703                    if ((ast.tokentype == ETokenType.ttslash) && (ast.tokencode == TBaseType.sqlpluscmd)) //and (prevst.NewlineIsLastTokenInTailerToken)) then
704                    {
705                        // TPlsqlStatementParse(asqlstatement).TerminatorToken := ast;
706                        ast.tokenstatus = ETokenStatus.tsignorebyyacc;
707                        gst = EFindSqlStateType.stnormal;
708                        onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
709
710                        //make / a sqlplus cmd
711                        gcurrentsqlstatement = new gudusoft.gsqlparser.stmt.oracle.TSqlplusCmdStatement(vendor);
712                        gcurrentsqlstatement.sourcetokenlist.add(ast);
713                        onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
714                    } else if ((ast.tokentype == ETokenType.ttperiod) && (sourcetokenlist.returnaftercurtoken(false)) && (sourcetokenlist.returnbeforecurtoken(false))) {    // single dot at a separate line
715                        ast.tokenstatus = ETokenStatus.tsignorebyyacc;
716                        gst = EFindSqlStateType.stnormal;
717                        onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
718
719                        //make ttperiod a sqlplus cmd
720                        gcurrentsqlstatement = new gudusoft.gsqlparser.stmt.oracle.TSqlplusCmdStatement(vendor);
721                        gcurrentsqlstatement.sourcetokenlist.add(ast);
722                        onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
723                    } else if ((ast.tokencode == TBaseType.rrw_declare) && (waitingEnd == 0)) {
724                        i--;
725                        onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
726                        gst = EFindSqlStateType.stnormal;
727                    } else {
728                        gcurrentsqlstatement.sourcetokenlist.add(ast);
729                        if ((ast.tokentype == ETokenType.ttsemicolon) && (waitingEnd == 0) && (foundEnd) && (gcurrentsqlstatement.OracleStatementCanBeSeparatedByBeginEndPair())) {
730                            gst = EFindSqlStateType.stnormal;
731                            onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
732                        }
733                    }
734
735                    if (ast.tokencode == TBaseType.sqlpluscmd) {
736                        //change tokencode back to keyword or ident, because sqlplus cmd
737                        //in a sql statement(almost is plsql block) is not really a sqlplus cmd
738                        int m = flexer.getkeywordvalue(ast.getAstext());
739                        if (m != 0) {
740                            ast.tokencode = m;
741                        } else {
742                            ast.tokencode = TBaseType.ident;
743                        }
744                    }
745
746                    break;
747                } //ststoredprocedure
748
749                case ststoredprocedurePgStartBody: {
750                    gcurrentsqlstatement.sourcetokenlist.add(ast);
751
752                    if (ast.tokencode == TBaseType.rrw_greenplum_function_delimiter) {
753                        if (dollarStringToken.toString().equalsIgnoreCase(ast.toString())) {
754                            // must match the $$ token
755                            gst = EFindSqlStateType.ststoredprocedurePgEndBody;
756                            continue;
757                        }
758                    }
759
760                    break;
761                }
762
763                case ststoredprocedurePgEndBody: {
764
765                    if (ast.tokentype == ETokenType.ttsemicolon) {
766                        gst = EFindSqlStateType.stnormal;
767                        gcurrentsqlstatement.sourcetokenlist.add(ast);
768                        gcurrentsqlstatement.semicolonended = ast;
769                        onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
770                        continue;
771                    } else if (ast.tokencode == TBaseType.cmtdoublehyphen) {
772                        if (ast.toString().trim().endsWith(TBaseType.sqlflow_stmt_delimiter_str)) { // -- sqlflow-delimiter
773                            gst = EFindSqlStateType.stnormal;
774                            onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder);
775                            continue;
776                        }
777                    }
778
779                    gcurrentsqlstatement.sourcetokenlist.add(ast);
780
781                    if (ast.tokencode == TBaseType.rrw_greenplum_language) {
782                        // check next token which is the language used by this stored procedure
783                        TSourceToken nextSt = ast.nextSolidToken();
784                        if (nextSt != null) {
785                            if (gcurrentsqlstatement instanceof TRoutine) {  // can be TCreateProcedureStmt or TCreateFunctionStmt
786                                TRoutine p = (TRoutine) gcurrentsqlstatement;
787                                p.setRoutineLanguage(nextSt.toString());
788                            }
789                        }
790                    }
791
792                    break;
793                }
794
795            } //switch
796        }//for
797
798        //last statement
799        if ((gcurrentsqlstatement != null) &&
800                ((gst == EFindSqlStateType.stsqlplus) || (gst == EFindSqlStateType.stsql)
801                        || (gst == EFindSqlStateType.ststoredprocedure) || (gst == EFindSqlStateType.ststoredprocedurePgEndBody)
802                        || (gst == EFindSqlStateType.sterror))) {
803            onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, true, builder);
804        }
805
806        // Set results in builder
807        builder.sqlStatements(this.sqlstatements);
808        builder.errorCode(0);
809        builder.errorMessage("");
810
811        return 0;
812    }
813
814    // ========== Helper Methods ==========
815
816    /**
817     * Get previous solid token (non-whitespace, non-comment).
818     */
819    private TSourceToken getprevsolidtoken(TSourceToken asourcetoken) {
820        int i = asourcetoken.posinlist;
821        TSourceToken st;
822        for (i = i - 1; i >= 0; i--) {
823            st = sourcetokenlist.get(i);
824            if (st.issolidtoken()) return st;
825        }
826        return null;
827    }
828
829    /**
830     * Placeholder function for PostgreSQL-style meta command validation.
831     * Always returns false - Greenplum doesn't use SQL*Plus commands.
832     */
833    private boolean isvalidsqlpluscmdInPostgresql(String astr) {
834        return false;
835    }
836
837    /**
838     * Check if forward slash (/) is in a valid position to be treated as a SQL*Plus command.
839     */
840    private boolean IsValidPlaceForDivToSqlplusCmd(TSourceTokenList tokenList, int pos) {
841        // Implementation similar to TSourceTokenList.TokenBeforeCurToken
842        // This checks if there's a newline before the current token
843        if (pos <= 0) return false;
844        for (int i = pos - 1; i >= 0; i--) {
845            TSourceToken st = tokenList.get(i);
846            if (st.tokencode == TBaseType.lexnewline) {
847                return true;
848            }
849            if (st.issolidtoken()) {
850                return false;
851            }
852        }
853        return false;
854    }
855
856    /**
857     * Hook method called after each statement is parsed.
858     * <p>
859     * Greenplum doesn't need special post-processing, so this is a no-op.
860     */
861    @Override
862    protected void afterStatementParsed(TCustomSqlStatement stmt) {
863        // No special post-processing needed for Greenplum
864    }
865
866    /**
867     * Handle error recovery for CREATE TABLE statements.
868     * <p>
869     * This attempts to recover from syntax errors in table properties
870     * by marking unparseable tokens as SQL*Plus commands and retrying.
871     * <p>
872     * Migrated from TGSqlParser.doparse() lines 16914-16971
873     */
874    private void handleCreateTableErrorRecovery(TCustomSqlStatement stmt) {
875        // Only handle CREATE TABLE and CREATE INDEX (but not for Couchbase)
876        if (!((stmt.sqlstatementtype == ESqlStatementType.sstcreatetable)
877                || ((stmt.sqlstatementtype == ESqlStatementType.sstcreateindex) && (vendor != EDbVendor.dbvcouchbase)))) {
878            return;
879        }
880
881        // Don't recover if strict parsing is enabled
882        if (TBaseType.c_createTableStrictParsing) {
883            return;
884        }
885
886        // only parse main body of create table,
887        // ignore vendor-specific table properties after closing parenthesis
888        TCustomSqlStatement errorSqlStatement = stmt;
889
890        int nested = 0;
891        boolean isIgnore = false, isFoundIgnoreToken = false;
892        TSourceToken firstIgnoreToken = null;
893
894        for (int k = 0; k < errorSqlStatement.sourcetokenlist.size(); k++) {
895            TSourceToken st = errorSqlStatement.sourcetokenlist.get(k);
896
897            if (isIgnore) {
898                if (st.issolidtoken() && (st.tokencode != ';')) {
899                    isFoundIgnoreToken = true;
900                    if (firstIgnoreToken == null) {
901                        firstIgnoreToken = st;
902                    }
903                }
904                if (st.tokencode != ';') {
905                    st.tokencode = TBaseType.sqlpluscmd;
906                }
907                continue;
908            }
909
910            if (st.tokencode == (int) ')') {
911                nested--;
912                if (nested == 0) {
913                    //let's check if next token is: AS ( SELECT
914                    boolean isSelect = false;
915                    TSourceToken st1 = st.searchToken(TBaseType.rrw_as, 1);
916                    if (st1 != null) {
917                        TSourceToken st2 = st.searchToken((int) '(', 2);
918                        if (st2 != null) {
919                            TSourceToken st3 = st.searchToken(TBaseType.rrw_select, 3);
920                            isSelect = (st3 != null);
921                        }
922                    }
923                    if (!isSelect) isIgnore = true;
924                }
925            }
926
927            if ((st.tokencode == (int) '(') || (st.tokencode == TBaseType.left_parenthesis_2)) {
928                nested++;
929            }
930        }
931
932        // For Oracle, check if the first ignored token is a valid table property
933        if ((vendor == EDbVendor.dbvoracle) && ((firstIgnoreToken != null) && (!TBaseType.searchOracleTablePros(firstIgnoreToken.toString())))) {
934            // if it is not a valid Oracle table properties option, let raise the error.
935            isFoundIgnoreToken = false;
936        }
937
938        if (isFoundIgnoreToken) {
939            errorSqlStatement.clearError();
940            stmt.parsestatement(null, false, parserContext.isOnlyNeedRawParseTree());
941        }
942    }
943}