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> =
132 node.children.iter().map(format_node_json).collect();
133 parts.push(format!("\"children\": [{}]", children.join(", ")));
134 }
135
136 format!("{{{}}}", parts.join(", "))
137}
138
139pub struct ExplainExecutor;
141
142impl ExplainExecutor {
143 pub fn execute(stmt: &ExplainStmt, database: &Database) -> Result<ExplainResult, ExecutorError> {
145 let plan = match stmt.statement.as_ref() {
146 Statement::Select(select_stmt) => Self::explain_select(select_stmt, database)?,
147 Statement::Insert(_) => {
148 PlanNode::new("Insert").with_detail("Inserts rows into target table".to_string())
149 }
150 Statement::Update(_) => PlanNode::new("Update")
151 .with_detail("Updates rows matching WHERE clause".to_string()),
152 Statement::Delete(_) => PlanNode::new("Delete")
153 .with_detail("Deletes rows matching WHERE clause".to_string()),
154 _ => {
155 return Err(ExecutorError::Other(
156 "EXPLAIN only supports SELECT, INSERT, UPDATE, DELETE statements".to_string(),
157 ));
158 }
159 };
160
161 Ok(ExplainResult { plan, format: stmt.format.clone() })
162 }
163
164 fn explain_select(stmt: &SelectStmt, database: &Database) -> Result<PlanNode, ExecutorError> {
166 let mut root = PlanNode::new("Select");
167
168 if let Some(ref from_clause) = stmt.from {
170 let scan_node =
171 Self::explain_from_clause(from_clause, &stmt.where_clause, &stmt.order_by, database)?;
172 root.add_child(scan_node);
173 }
174
175 if stmt.where_clause.is_some() {
177 root.details.push("Filter: <where clause>".to_string());
178 }
179
180 if stmt.group_by.is_some() {
182 root.details.push("Group: <group by clause>".to_string());
183 }
184
185 if stmt.order_by.is_some() {
187 root.details.push("Sort: <order by clause>".to_string());
188 }
189
190 if let Some(limit) = stmt.limit {
192 root.details.push(format!("Limit: {}", limit));
193 }
194
195 Ok(root)
196 }
197
198 fn explain_from_clause(
200 from: &vibesql_ast::FromClause,
201 where_clause: &Option<vibesql_ast::Expression>,
202 order_by: &Option<Vec<vibesql_ast::OrderByItem>>,
203 database: &Database,
204 ) -> Result<PlanNode, ExecutorError> {
205 match from {
206 vibesql_ast::FromClause::Table { name, alias, .. } => {
207 Self::explain_table_scan(name, alias.as_deref(), where_clause, order_by, database)
208 }
209 vibesql_ast::FromClause::Join { left, right, join_type, condition, natural } => {
210 let join_name = match join_type {
211 vibesql_ast::JoinType::Inner => "Inner Join",
212 vibesql_ast::JoinType::LeftOuter => "Left Outer Join",
213 vibesql_ast::JoinType::RightOuter => "Right Outer Join",
214 vibesql_ast::JoinType::FullOuter => "Full Outer Join",
215 vibesql_ast::JoinType::Cross => "Cross Join",
216 vibesql_ast::JoinType::Semi => "Semi Join",
217 vibesql_ast::JoinType::Anti => "Anti Join",
218 };
219
220 let mut join_node = PlanNode::new(join_name);
221
222 if *natural {
223 join_node.details.push("NATURAL join".to_string());
224 }
225
226 if condition.is_some() {
227 join_node.details.push("Join condition: <on clause>".to_string());
228 }
229
230 let left_child = Self::explain_from_clause(left, where_clause, order_by, database)?;
232 join_node.add_child(left_child);
233
234 let right_child = Self::explain_from_clause(right, &None, &None, database)?;
236 join_node.add_child(right_child);
237
238 Ok(join_node)
239 }
240 vibesql_ast::FromClause::Subquery { query, alias, .. } => {
241 let mut subquery_node = PlanNode::new("Subquery");
242 subquery_node.object = Some(format!("AS {}", alias));
243
244 let child = Self::explain_select(query, database)?;
245 subquery_node.add_child(child);
246
247 Ok(subquery_node)
248 }
249 }
250 }
251
252 fn explain_table_scan(
254 table_name: &str,
255 alias: Option<&str>,
256 where_clause: &Option<vibesql_ast::Expression>,
257 order_by: &Option<Vec<vibesql_ast::OrderByItem>>,
258 database: &Database,
259 ) -> Result<PlanNode, ExecutorError> {
260 let index_info = cost_based_index_selection(
262 table_name,
263 where_clause.as_ref(),
264 order_by.as_ref().map(|v| v.as_slice()),
265 database,
266 );
267
268 let mut node = if let Some((index_name, sorted_cols)) = index_info {
269 let mut idx_node = PlanNode::new("Index Scan").with_object(table_name);
270 idx_node.details.push(format!("Using index: {}", index_name));
271
272 if let Some(cols) = sorted_cols {
273 let col_strs: Vec<String> = cols
274 .iter()
275 .map(|(col, dir)| {
276 format!(
277 "{} {}",
278 col,
279 match dir {
280 vibesql_ast::OrderDirection::Asc => "ASC",
281 vibesql_ast::OrderDirection::Desc => "DESC",
282 }
283 )
284 })
285 .collect();
286 idx_node.details.push(format!("Sorted by: {}", col_strs.join(", ")));
287 }
288
289 idx_node
290 } else {
291 PlanNode::new("Seq Scan").with_object(table_name)
292 };
293
294 if let Some(a) = alias {
296 node.details.push(format!("Alias: {}", a));
297 }
298
299 if let Some(table) = database.get_table(table_name) {
301 let row_count = table.row_count();
302 node = node.with_estimated_rows(row_count as f64);
303 }
304
305 Ok(node)
306 }
307}