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] <statement> 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}