1use crate::optimizer::OptimizedQuery;
4use crate::optimizer::cost_model::Cost;
5use crate::parser::ast::*;
6use serde::{Deserialize, Serialize};
7use std::fmt;
8
9#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct ExplainPlan {
12 pub nodes: Vec<ExplainNode>,
14 pub total_cost: Cost,
16 pub statistics: Option<ExecutionStatistics>,
18}
19
20impl ExplainPlan {
21 pub fn from_optimized(query: &OptimizedQuery) -> Self {
23 let nodes = Self::build_nodes(&query.statement);
24 Self {
25 nodes,
26 total_cost: query.optimized_cost,
27 statistics: None,
28 }
29 }
30
31 fn build_nodes(stmt: &Statement) -> Vec<ExplainNode> {
33 match stmt {
34 Statement::Select(select) => Self::build_select_nodes(select),
35 }
36 }
37
38 fn build_select_nodes(select: &SelectStatement) -> Vec<ExplainNode> {
40 let mut nodes = Vec::new();
41 let mut node_id = 0;
42
43 if let Some(ref table_ref) = select.from {
45 Self::build_table_nodes(table_ref, &mut nodes, &mut node_id, 0);
46 }
47
48 if select.selection.is_some() {
50 nodes.push(ExplainNode {
51 id: node_id,
52 node_type: NodeType::Filter,
53 description: "Filter".to_string(),
54 details: format!("Predicate: {:?}", select.selection),
55 cost: Cost::zero(),
56 rows: None,
57 depth: 0,
58 });
59 node_id += 1;
60 }
61
62 if !select.group_by.is_empty() {
64 nodes.push(ExplainNode {
65 id: node_id,
66 node_type: NodeType::Aggregate,
67 description: "Aggregate".to_string(),
68 details: format!("Group by: {:?}", select.group_by),
69 cost: Cost::zero(),
70 rows: None,
71 depth: 0,
72 });
73 node_id += 1;
74 }
75
76 if !select.order_by.is_empty() {
78 nodes.push(ExplainNode {
79 id: node_id,
80 node_type: NodeType::Sort,
81 description: "Sort".to_string(),
82 details: format!("Order by: {:?}", select.order_by),
83 cost: Cost::zero(),
84 rows: None,
85 depth: 0,
86 });
87 node_id += 1;
88 }
89
90 if select.limit.is_some() {
92 nodes.push(ExplainNode {
93 id: node_id,
94 node_type: NodeType::Limit,
95 description: "Limit".to_string(),
96 details: format!("Limit: {:?}", select.limit),
97 cost: Cost::zero(),
98 rows: select.limit,
99 depth: 0,
100 });
101 }
102
103 nodes
104 }
105
106 fn build_table_nodes(
108 table_ref: &TableReference,
109 nodes: &mut Vec<ExplainNode>,
110 node_id: &mut usize,
111 depth: usize,
112 ) {
113 match table_ref {
114 TableReference::Table { name, .. } => {
115 nodes.push(ExplainNode {
116 id: *node_id,
117 node_type: NodeType::TableScan,
118 description: "Table Scan".to_string(),
119 details: format!("Table: {}", name),
120 cost: Cost::zero(),
121 rows: None,
122 depth,
123 });
124 *node_id += 1;
125 }
126 TableReference::Join {
127 left,
128 right,
129 join_type,
130 ..
131 } => {
132 Self::build_table_nodes(left, nodes, node_id, depth + 1);
133 Self::build_table_nodes(right, nodes, node_id, depth + 1);
134
135 nodes.push(ExplainNode {
136 id: *node_id,
137 node_type: NodeType::Join,
138 description: format!("{:?} Join", join_type),
139 details: String::new(),
140 cost: Cost::zero(),
141 rows: None,
142 depth,
143 });
144 *node_id += 1;
145 }
146 TableReference::Subquery { query, alias } => {
147 let subquery_nodes = Self::build_select_nodes(query);
148 nodes.extend(subquery_nodes);
149
150 nodes.push(ExplainNode {
151 id: *node_id,
152 node_type: NodeType::Subquery,
153 description: "Subquery".to_string(),
154 details: format!("Alias: {}", alias),
155 cost: Cost::zero(),
156 rows: None,
157 depth,
158 });
159 *node_id += 1;
160 }
161 }
162 }
163
164 pub fn format_text(&self) -> String {
166 let mut output = String::new();
167 output.push_str("Query Execution Plan:\n");
168 output.push_str(&format!("Total Cost: {:.2}\n", self.total_cost.total()));
169 output.push('\n');
170
171 for node in &self.nodes {
172 let indent = " ".repeat(node.depth);
173 output.push_str(&format!(
174 "{}[{}] {}: {}\n",
175 indent, node.id, node.description, node.details
176 ));
177 if let Some(rows) = node.rows {
178 output.push_str(&format!("{} Rows: {}\n", indent, rows));
179 }
180 output.push_str(&format!("{} Cost: {:.2}\n", indent, node.cost.total()));
181 }
182
183 if let Some(ref stats) = self.statistics {
184 output.push_str("\nExecution Statistics:\n");
185 output.push_str(&format!(" Execution Time: {:?}\n", stats.execution_time));
186 output.push_str(&format!(" Rows Processed: {}\n", stats.rows_processed));
187 output.push_str(&format!(" Rows Returned: {}\n", stats.rows_returned));
188 }
189
190 output
191 }
192
193 pub fn format_json(&self) -> Result<String, serde_json::Error> {
195 serde_json::to_string_pretty(self)
196 }
197}
198
199#[derive(Debug, Clone, Serialize, Deserialize)]
201pub struct ExplainNode {
202 pub id: usize,
204 pub node_type: NodeType,
206 pub description: String,
208 pub details: String,
210 pub cost: Cost,
212 pub rows: Option<usize>,
214 pub depth: usize,
216}
217
218#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
220pub enum NodeType {
221 TableScan,
223 IndexScan,
225 Filter,
227 Join,
229 Aggregate,
231 Sort,
233 Limit,
235 Subquery,
237}
238
239#[derive(Debug, Clone, Serialize, Deserialize)]
241pub struct ExecutionStatistics {
242 pub execution_time: std::time::Duration,
244 pub rows_processed: usize,
246 pub rows_returned: usize,
248 pub peak_memory: Option<usize>,
250}
251
252impl fmt::Display for ExplainPlan {
253 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
254 write!(f, "{}", self.format_text())
255 }
256}
257
258#[cfg(test)]
259mod tests {
260 use super::*;
261 use crate::parser::sql::parse_sql;
262
263 #[test]
264 fn test_explain_plan() {
265 let sql = "SELECT id, name FROM users WHERE age > 18 ORDER BY name LIMIT 10";
266 let stmt = parse_sql(sql).ok();
267
268 if let Some(stmt) = stmt {
269 let nodes = ExplainPlan::build_nodes(&stmt);
270 assert!(!nodes.is_empty());
271 }
272 }
273
274 #[test]
275 fn test_explain_format_text() {
276 let plan = ExplainPlan {
277 nodes: vec![ExplainNode {
278 id: 0,
279 node_type: NodeType::TableScan,
280 description: "Table Scan".to_string(),
281 details: "Table: users".to_string(),
282 cost: Cost::zero(),
283 rows: Some(1000),
284 depth: 0,
285 }],
286 total_cost: Cost::new(100.0, 1000.0, 100.0, 0.0),
287 statistics: None,
288 };
289
290 let text = plan.format_text();
291 assert!(text.contains("Query Execution Plan"));
292 assert!(text.contains("Table Scan"));
293 }
294}