1use std::fmt::Write;
11use vibesql_ast::{ExplainFormat, ExplainStmt, SelectStmt, Statement};
12use vibesql_storage::Database;
13
14use crate::errors::ExecutorError;
15use crate::select::scan::index_scan::cost_based_index_selection;
16
17#[derive(Debug, Clone)]
19pub struct PlanNode {
20 pub operation: String,
22 pub object: Option<String>,
24 pub details: Vec<String>,
26 pub estimated_rows: Option<f64>,
28 pub children: Vec<PlanNode>,
30}
31
32impl PlanNode {
33 fn new(operation: &str) -> Self {
34 PlanNode {
35 operation: operation.to_string(),
36 object: None,
37 details: Vec::new(),
38 estimated_rows: None,
39 children: Vec::new(),
40 }
41 }
42
43 fn with_object(mut self, object: &str) -> Self {
44 self.object = Some(object.to_string());
45 self
46 }
47
48 fn with_detail(mut self, detail: String) -> Self {
49 self.details.push(detail);
50 self
51 }
52
53 fn with_estimated_rows(mut self, rows: f64) -> Self {
54 self.estimated_rows = Some(rows);
55 self
56 }
57
58 fn add_child(&mut self, child: PlanNode) {
59 self.children.push(child);
60 }
61}
62
63#[derive(Debug)]
65pub struct ExplainResult {
66 pub plan: PlanNode,
68 pub format: ExplainFormat,
70}
71
72impl ExplainResult {
73 pub fn to_text(&self) -> String {
75 let mut output = String::new();
76 format_node_text(&self.plan, 0, &mut output);
77 output
78 }
79
80 pub fn to_json(&self) -> String {
82 format_node_json(&self.plan)
83 }
84}
85
86fn format_node_text(node: &PlanNode, depth: usize, output: &mut String) {
87 let indent = " ".repeat(depth);
88 let arrow = if depth > 0 { "-> " } else { "" };
89
90 let mut line = format!("{}{}{}", indent, arrow, node.operation);
92
93 if let Some(ref obj) = node.object {
94 write!(line, " on {}", obj).unwrap();
95 }
96
97 if let Some(rows) = node.estimated_rows {
98 write!(line, " (rows={:.0})", rows).unwrap();
99 }
100
101 writeln!(output, "{}", line).unwrap();
102
103 for detail in &node.details {
105 writeln!(output, "{} {}", indent, detail).unwrap();
106 }
107
108 for child in &node.children {
110 format_node_text(child, depth + 1, output);
111 }
112}
113
114fn format_node_json(node: &PlanNode) -> String {
115 let mut parts = vec![format!("\"operation\": \"{}\"", node.operation)];
116
117 if let Some(ref obj) = node.object {
118 parts.push(format!("\"object\": \"{}\"", obj));
119 }
120
121 if !node.details.is_empty() {
122 let details: Vec<String> = node.details.iter().map(|d| format!("\"{}\"", d)).collect();
123 parts.push(format!("\"details\": [{}]", details.join(", ")));
124 }
125
126 if let Some(rows) = node.estimated_rows {
127 parts.push(format!("\"estimated_rows\": {:.0}", rows));
128 }
129
130 if !node.children.is_empty() {
131 let children: Vec<String> = node.children.iter().map(format_node_json).collect();
132 parts.push(format!("\"children\": [{}]", children.join(", ")));
133 }
134
135 format!("{{{}}}", parts.join(", "))
136}
137
138pub struct ExplainExecutor;
140
141impl ExplainExecutor {
142 pub fn execute(
144 stmt: &ExplainStmt,
145 database: &Database,
146 ) -> Result<ExplainResult, ExecutorError> {
147 let plan = match stmt.statement.as_ref() {
148 Statement::Select(select_stmt) => Self::explain_select(select_stmt, database)?,
149 Statement::Insert(_) => {
150 PlanNode::new("Insert").with_detail("Inserts rows into target table".to_string())
151 }
152 Statement::Update(_) => PlanNode::new("Update")
153 .with_detail("Updates rows matching WHERE clause".to_string()),
154 Statement::Delete(_) => PlanNode::new("Delete")
155 .with_detail("Deletes rows matching WHERE clause".to_string()),
156 _ => {
157 return Err(ExecutorError::Other(
158 "EXPLAIN only supports SELECT, INSERT, UPDATE, DELETE statements".to_string(),
159 ));
160 }
161 };
162
163 Ok(ExplainResult { plan, format: stmt.format.clone() })
164 }
165
166 fn explain_select(stmt: &SelectStmt, database: &Database) -> Result<PlanNode, ExecutorError> {
168 let mut root = PlanNode::new("Select");
169
170 if let Some(ref from_clause) = stmt.from {
172 let scan_node = Self::explain_from_clause(
173 from_clause,
174 &stmt.where_clause,
175 &stmt.order_by,
176 database,
177 )?;
178 root.add_child(scan_node);
179 }
180
181 if stmt.where_clause.is_some() {
183 root.details.push("Filter: <where clause>".to_string());
184 }
185
186 if stmt.group_by.is_some() {
188 root.details.push("Group: <group by clause>".to_string());
189 }
190
191 if stmt.order_by.is_some() {
193 root.details.push("Sort: <order by clause>".to_string());
194 }
195
196 if let Some(limit) = stmt.limit {
198 root.details.push(format!("Limit: {}", limit));
199 }
200
201 Ok(root)
202 }
203
204 fn explain_from_clause(
206 from: &vibesql_ast::FromClause,
207 where_clause: &Option<vibesql_ast::Expression>,
208 order_by: &Option<Vec<vibesql_ast::OrderByItem>>,
209 database: &Database,
210 ) -> Result<PlanNode, ExecutorError> {
211 match from {
212 vibesql_ast::FromClause::Table { name, alias, .. } => {
213 Self::explain_table_scan(name, alias.as_deref(), where_clause, order_by, database)
214 }
215 vibesql_ast::FromClause::Join { left, right, join_type, condition, natural } => {
216 let join_name = match join_type {
217 vibesql_ast::JoinType::Inner => "Inner Join",
218 vibesql_ast::JoinType::LeftOuter => "Left Outer Join",
219 vibesql_ast::JoinType::RightOuter => "Right Outer Join",
220 vibesql_ast::JoinType::FullOuter => "Full Outer Join",
221 vibesql_ast::JoinType::Cross => "Cross Join",
222 vibesql_ast::JoinType::Semi => "Semi Join",
223 vibesql_ast::JoinType::Anti => "Anti Join",
224 };
225
226 let mut join_node = PlanNode::new(join_name);
227
228 if *natural {
229 join_node.details.push("NATURAL join".to_string());
230 }
231
232 if condition.is_some() {
233 join_node.details.push("Join condition: <on clause>".to_string());
234 }
235
236 let left_child = Self::explain_from_clause(left, where_clause, order_by, database)?;
238 join_node.add_child(left_child);
239
240 let right_child = Self::explain_from_clause(right, &None, &None, database)?;
242 join_node.add_child(right_child);
243
244 Ok(join_node)
245 }
246 vibesql_ast::FromClause::Subquery { query, alias, .. } => {
247 let mut subquery_node = PlanNode::new("Subquery");
248 subquery_node.object = Some(format!("AS {}", alias));
249
250 let child = Self::explain_select(query, database)?;
251 subquery_node.add_child(child);
252
253 Ok(subquery_node)
254 }
255 }
256 }
257
258 fn explain_table_scan(
260 table_name: &str,
261 alias: Option<&str>,
262 where_clause: &Option<vibesql_ast::Expression>,
263 order_by: &Option<Vec<vibesql_ast::OrderByItem>>,
264 database: &Database,
265 ) -> Result<PlanNode, ExecutorError> {
266 let index_info = cost_based_index_selection(
268 table_name,
269 where_clause.as_ref(),
270 order_by.as_ref().map(|v| v.as_slice()),
271 database,
272 );
273
274 let mut node = if let Some((index_name, sorted_cols)) = index_info {
275 let mut idx_node = PlanNode::new("Index Scan").with_object(table_name);
276 idx_node.details.push(format!("Using index: {}", index_name));
277
278 if let Some(cols) = sorted_cols {
279 let col_strs: Vec<String> = cols
280 .iter()
281 .map(|(col, dir)| {
282 format!(
283 "{} {}",
284 col,
285 match dir {
286 vibesql_ast::OrderDirection::Asc => "ASC",
287 vibesql_ast::OrderDirection::Desc => "DESC",
288 }
289 )
290 })
291 .collect();
292 idx_node.details.push(format!("Sorted by: {}", col_strs.join(", ")));
293 }
294
295 idx_node
296 } else {
297 PlanNode::new("Seq Scan").with_object(table_name)
298 };
299
300 if let Some(a) = alias {
302 node.details.push(format!("Alias: {}", a));
303 }
304
305 if let Some(table) = database.get_table(table_name) {
307 let row_count = table.row_count();
308 node = node.with_estimated_rows(row_count as f64);
309 }
310
311 Ok(node)
312 }
313}