001package gudusoft.gsqlparser.stmt.flink;
002
003import gudusoft.gsqlparser.*;
004import gudusoft.gsqlparser.nodes.*;
005import gudusoft.gsqlparser.stmt.TExplainPlan;
006
007/**
008 * Flink EXPLAIN statement.
009 *
010 * <p>Displays the logical and physical execution plan for a SQL statement.</p>
011 *
012 * <h3>Syntax:</h3>
013 * <pre>
014 * EXPLAIN [([ExplainDetail[, ExplainDetail]*]) | PLAN FOR] &lt;statement&gt;
015 * </pre>
016 *
017 * <h3>ExplainDetail options:</h3>
018 * <ul>
019 *   <li>ESTIMATED_COST - Shows estimated cost of each physical rel node</li>
020 *   <li>CHANGELOG_MODE - Shows changelog mode of each physical rel node</li>
021 *   <li>PLAN_ADVICE - Provides potential risk warnings and optimization advice</li>
022 *   <li>JSON_EXECUTION_PLAN - Shows JSON-format execution plan</li>
023 * </ul>
024 *
025 * <h3>Examples:</h3>
026 * <pre>
027 * EXPLAIN SELECT * FROM Orders;
028 * EXPLAIN PLAN FOR SELECT * FROM Orders;
029 * EXPLAIN ESTIMATED_COST, CHANGELOG_MODE SELECT * FROM Orders;
030 * EXPLAIN (ESTIMATED_COST, CHANGELOG_MODE, PLAN_ADVICE, JSON_EXECUTION_PLAN) SELECT * FROM Orders;
031 * </pre>
032 *
033 * @since 3.2.0.0
034 */
035public class TFlinkExplainStmt extends TExplainPlan {
036
037    private boolean estimatedCost = false;
038    private boolean changelogMode = false;
039    private boolean planAdvice = false;
040    private boolean jsonExecutionPlan = false;
041    private boolean isPlanFor = false;
042
043    public TFlinkExplainStmt(EDbVendor dbvendor) {
044        super(dbvendor);
045    }
046
047    /**
048     * Returns whether ESTIMATED_COST option was specified.
049     */
050    public boolean isEstimatedCost() {
051        return estimatedCost;
052    }
053
054    public void setEstimatedCost(boolean estimatedCost) {
055        this.estimatedCost = estimatedCost;
056    }
057
058    /**
059     * Returns whether CHANGELOG_MODE option was specified.
060     */
061    public boolean isChangelogMode() {
062        return changelogMode;
063    }
064
065    public void setChangelogMode(boolean changelogMode) {
066        this.changelogMode = changelogMode;
067    }
068
069    /**
070     * Returns whether PLAN_ADVICE option was specified.
071     */
072    public boolean isPlanAdvice() {
073        return planAdvice;
074    }
075
076    public void setPlanAdvice(boolean planAdvice) {
077        this.planAdvice = planAdvice;
078    }
079
080    /**
081     * Returns whether JSON_EXECUTION_PLAN option was specified.
082     */
083    public boolean isJsonExecutionPlan() {
084        return jsonExecutionPlan;
085    }
086
087    public void setJsonExecutionPlan(boolean jsonExecutionPlan) {
088        this.jsonExecutionPlan = jsonExecutionPlan;
089    }
090
091    /**
092     * Returns whether PLAN FOR syntax was used.
093     */
094    public boolean isPlanFor() {
095        return isPlanFor;
096    }
097
098    public void setIsPlanFor(boolean isPlanFor) {
099        this.isPlanFor = isPlanFor;
100    }
101
102    @Override
103    public int doParseStatement(TCustomSqlStatement psql) {
104        // First, extract options from the original tokens before they were hidden
105        extractExplainOptions();
106
107        // Then call parent to handle the inner statement parsing
108        return super.doParseStatement(psql);
109    }
110
111    /**
112     * Extract EXPLAIN options from the source tokens.
113     * This examines the original SQL text to determine which options were specified.
114     */
115    private void extractExplainOptions() {
116        if (sourcetokenlist == null || sourcetokenlist.size() == 0) {
117            return;
118        }
119
120        // Look for EXPLAIN options in the token stream
121        // The tokens have been marked as sqlpluscmd, so we check the original string
122        boolean foundExplain = false;
123        boolean foundPlan = false;
124
125        for (int i = 0; i < sourcetokenlist.size(); i++) {
126            TSourceToken token = sourcetokenlist.get(i);
127            String tokenStr = token.toString().toUpperCase();
128
129            // Skip whitespace tokens
130            if (token.tokentype == ETokenType.ttwhitespace) {
131                continue;
132            }
133
134            // Check for EXPLAIN keyword
135            if (tokenStr.equals("EXPLAIN")) {
136                foundExplain = true;
137                continue;
138            }
139
140            // After EXPLAIN, check for options
141            if (foundExplain) {
142                if (tokenStr.equals("PLAN")) {
143                    foundPlan = true;
144                } else if (tokenStr.equals("FOR") && foundPlan) {
145                    this.isPlanFor = true;
146                } else if (tokenStr.equals("ESTIMATED_COST")) {
147                    this.estimatedCost = true;
148                } else if (tokenStr.equals("CHANGELOG_MODE")) {
149                    this.changelogMode = true;
150                } else if (tokenStr.equals("PLAN_ADVICE")) {
151                    this.planAdvice = true;
152                } else if (tokenStr.equals("JSON_EXECUTION_PLAN")) {
153                    this.jsonExecutionPlan = true;
154                }
155
156                // Stop when we reach the inner statement keywords
157                if (tokenStr.equals("SELECT") || tokenStr.equals("INSERT") ||
158                    tokenStr.equals("UPDATE") || tokenStr.equals("DELETE") ||
159                    tokenStr.equals("WITH") || tokenStr.equals("CREATE")) {
160                    break;
161                }
162            }
163        }
164    }
165}