sql_cli/
execution_plan.rs

1// Execution Plan Module
2// Provides detailed tracing and timing for query execution
3
4use crate::sql::parser::ast::{SqlExpression, WhereClause};
5use std::fmt;
6use std::time::{Duration, Instant};
7
8/// Represents a single step in the execution plan
9#[derive(Debug, Clone)]
10pub struct ExecutionStep {
11    pub step_type: StepType,
12    pub description: String,
13    pub details: Vec<String>,
14    pub rows_in: Option<usize>,
15    pub rows_out: Option<usize>,
16    pub duration: Option<Duration>,
17    pub children: Vec<ExecutionStep>,
18}
19
20/// Types of execution steps
21#[derive(Debug, Clone)]
22pub enum StepType {
23    Parse,
24    LoadData,
25    TableScan,
26    Filter,
27    Sort,
28    GroupBy,
29    Having,
30    Select,
31    Limit,
32    Output,
33    Join,
34    WindowFunction,
35    Expression,
36}
37
38impl fmt::Display for StepType {
39    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40        match self {
41            StepType::Parse => write!(f, "PARSE"),
42            StepType::LoadData => write!(f, "LOAD_DATA"),
43            StepType::TableScan => write!(f, "TABLE_SCAN"),
44            StepType::Filter => write!(f, "FILTER"),
45            StepType::Sort => write!(f, "SORT"),
46            StepType::GroupBy => write!(f, "GROUP_BY"),
47            StepType::Having => write!(f, "HAVING"),
48            StepType::Select => write!(f, "SELECT"),
49            StepType::Limit => write!(f, "LIMIT"),
50            StepType::Output => write!(f, "OUTPUT"),
51            StepType::Join => write!(f, "JOIN"),
52            StepType::WindowFunction => write!(f, "WINDOW"),
53            StepType::Expression => write!(f, "EXPR"),
54        }
55    }
56}
57
58/// Execution plan builder that tracks all steps
59pub struct ExecutionPlanBuilder {
60    steps: Vec<ExecutionStep>,
61    current_step: Option<ExecutionStep>,
62    start_time: Option<Instant>,
63}
64
65impl ExecutionPlanBuilder {
66    pub fn new() -> Self {
67        Self {
68            steps: Vec::new(),
69            current_step: None,
70            start_time: None,
71        }
72    }
73
74    /// Start a new execution step
75    pub fn begin_step(&mut self, step_type: StepType, description: String) {
76        if let Some(current) = self.current_step.take() {
77            self.steps.push(current);
78        }
79
80        self.current_step = Some(ExecutionStep {
81            step_type,
82            description,
83            details: Vec::new(),
84            rows_in: None,
85            rows_out: None,
86            duration: None,
87            children: Vec::new(),
88        });
89        self.start_time = Some(Instant::now());
90    }
91
92    /// Add a detail to the current step
93    pub fn add_detail(&mut self, detail: String) {
94        if let Some(ref mut step) = self.current_step {
95            step.details.push(detail);
96        }
97    }
98
99    /// Set input row count for current step
100    pub fn set_rows_in(&mut self, count: usize) {
101        if let Some(ref mut step) = self.current_step {
102            step.rows_in = Some(count);
103        }
104    }
105
106    /// Set output row count for current step
107    pub fn set_rows_out(&mut self, count: usize) {
108        if let Some(ref mut step) = self.current_step {
109            step.rows_out = Some(count);
110        }
111    }
112
113    /// End the current step and record duration
114    pub fn end_step(&mut self) {
115        if let Some(ref mut step) = self.current_step {
116            if let Some(start) = self.start_time {
117                step.duration = Some(start.elapsed());
118            }
119        }
120
121        if let Some(step) = self.current_step.take() {
122            self.steps.push(step);
123        }
124        self.start_time = None;
125    }
126
127    /// Add a child step to the current step
128    pub fn add_child_step(&mut self, child: ExecutionStep) {
129        if let Some(ref mut step) = self.current_step {
130            step.children.push(child);
131        }
132    }
133
134    /// Build the final execution plan
135    pub fn build(mut self) -> ExecutionPlan {
136        if let Some(current) = self.current_step.take() {
137            self.steps.push(current);
138        }
139
140        let total_duration = self.steps.iter().filter_map(|s| s.duration).sum();
141
142        ExecutionPlan {
143            steps: self.steps,
144            total_duration,
145        }
146    }
147}
148
149/// Complete execution plan
150pub struct ExecutionPlan {
151    pub steps: Vec<ExecutionStep>,
152    pub total_duration: Duration,
153}
154
155impl ExecutionPlan {
156    /// Format the execution plan as a tree
157    pub fn format_tree(&self) -> String {
158        let mut output = String::new();
159        output.push_str("\n");
160        output
161            .push_str("╔══════════════════════════════════════════════════════════════════════╗\n");
162        output
163            .push_str("║                        EXECUTION PLAN                               ║\n");
164        output.push_str(
165            "╚══════════════════════════════════════════════════════════════════════╝\n\n",
166        );
167
168        for (i, step) in self.steps.iter().enumerate() {
169            self.format_step(&mut output, step, 0, i == self.steps.len() - 1);
170        }
171
172        output.push_str("\n");
173        output.push_str(&format!(
174            "Total Execution Time: {:.3}ms\n",
175            self.total_duration.as_secs_f64() * 1000.0
176        ));
177
178        output
179    }
180
181    fn format_step(&self, output: &mut String, step: &ExecutionStep, indent: usize, is_last: bool) {
182        let prefix = if indent == 0 {
183            if is_last {
184                "└─".to_string()
185            } else {
186                "├─".to_string()
187            }
188        } else {
189            format!(
190                "{}{}",
191                "  ".repeat(indent),
192                if is_last { "└─" } else { "├─" }
193            )
194        };
195
196        // Format main step line
197        let time_str = step
198            .duration
199            .map(|d| format!(" [{:.3}ms]", d.as_secs_f64() * 1000.0))
200            .unwrap_or_default();
201
202        let rows_str = match (step.rows_in, step.rows_out) {
203            (Some(i), Some(o)) if i != o => format!(" (rows: {} → {})", i, o),
204            (_, Some(o)) => format!(" (rows: {})", o),
205            _ => String::new(),
206        };
207
208        output.push_str(&format!(
209            "{} {} {}{}{}\n",
210            prefix, step.step_type, step.description, rows_str, time_str
211        ));
212
213        // Format details
214        let detail_indent = if indent == 0 {
215            "  ".to_string()
216        } else {
217            "  ".repeat(indent + 1)
218        };
219        for detail in &step.details {
220            output.push_str(&format!("{}  • {}\n", detail_indent, detail));
221        }
222
223        // Format children
224        for (i, child) in step.children.iter().enumerate() {
225            self.format_step(output, child, indent + 1, i == step.children.len() - 1);
226        }
227    }
228
229    /// Format the execution plan as a table
230    pub fn format_table(&self) -> String {
231        let mut output = String::new();
232        output.push_str("\n");
233        output.push_str(
234            "┌────────────────┬──────────────────────────────┬──────────┬──────────┬──────────┐\n",
235        );
236        output.push_str(
237            "│ Step           │ Description                  │ Rows In  │ Rows Out │ Time(ms) │\n",
238        );
239        output.push_str(
240            "├────────────────┼──────────────────────────────┼──────────┼──────────┼──────────┤\n",
241        );
242
243        for step in &self.steps {
244            self.format_step_table(&mut output, step, 0);
245        }
246
247        output.push_str(
248            "└────────────────┴──────────────────────────────┴──────────┴──────────┴──────────┘\n",
249        );
250        output.push_str(&format!(
251            "\nTotal Time: {:.3}ms\n",
252            self.total_duration.as_secs_f64() * 1000.0
253        ));
254
255        output
256    }
257
258    fn format_step_table(&self, output: &mut String, step: &ExecutionStep, indent: usize) {
259        let step_name = format!("{}{}", "  ".repeat(indent), step.step_type);
260        let desc = if step.description.len() > 28 {
261            format!("{}...", &step.description[..25])
262        } else {
263            step.description.clone()
264        };
265
266        let rows_in = step
267            .rows_in
268            .map(|r| r.to_string())
269            .unwrap_or_else(|| "-".to_string());
270
271        let rows_out = step
272            .rows_out
273            .map(|r| r.to_string())
274            .unwrap_or_else(|| "-".to_string());
275
276        let time = step
277            .duration
278            .map(|d| format!("{:.3}", d.as_secs_f64() * 1000.0))
279            .unwrap_or_else(|| "-".to_string());
280
281        output.push_str(&format!(
282            "│ {:<14} │ {:<28} │ {:>8} │ {:>8} │ {:>8} │\n",
283            step_name, desc, rows_in, rows_out, time
284        ));
285
286        for child in &step.children {
287            self.format_step_table(output, child, indent + 1);
288        }
289    }
290
291    /// Get performance insights
292    pub fn get_insights(&self) -> Vec<String> {
293        let mut insights = Vec::new();
294
295        // Find slowest steps
296        let mut steps_with_time: Vec<_> = self
297            .steps
298            .iter()
299            .filter_map(|s| s.duration.map(|d| (s, d)))
300            .collect();
301        steps_with_time.sort_by_key(|(_, d)| std::cmp::Reverse(*d));
302
303        if let Some((slowest, duration)) = steps_with_time.first() {
304            let percentage = (duration.as_secs_f64() / self.total_duration.as_secs_f64()) * 100.0;
305            if percentage > 50.0 {
306                insights.push(format!(
307                    "⚠️  {} step took {:.1}% of total execution time ({:.3}ms)",
308                    slowest.step_type,
309                    percentage,
310                    duration.as_secs_f64() * 1000.0
311                ));
312            }
313        }
314
315        // Check for filter efficiency
316        for step in &self.steps {
317            if matches!(step.step_type, StepType::Filter) {
318                if let (Some(rows_in), Some(rows_out)) = (step.rows_in, step.rows_out) {
319                    if rows_in > 0 {
320                        let selectivity = (rows_out as f64 / rows_in as f64) * 100.0;
321                        if selectivity < 10.0 {
322                            insights.push(format!(
323                                "✓ Highly selective filter: {:.1}% of rows passed ({}→{})",
324                                selectivity, rows_in, rows_out
325                            ));
326                        } else if selectivity > 90.0 {
327                            insights.push(format!(
328                                "⚠️  Low selectivity filter: {:.1}% of rows passed ({}→{})",
329                                selectivity, rows_in, rows_out
330                            ));
331                        }
332                    }
333                }
334            }
335        }
336
337        // Check for large sorts
338        for step in &self.steps {
339            if matches!(step.step_type, StepType::Sort) {
340                if let Some(rows) = step.rows_in {
341                    if rows > 10000 {
342                        insights.push(format!("⚠️  Sorting large dataset: {} rows", rows));
343                    }
344                }
345            }
346        }
347
348        insights
349    }
350}
351
352/// Helper to analyze WHERE clause complexity
353pub fn analyze_where_clause(where_clause: &WhereClause) -> Vec<String> {
354    let mut details = Vec::new();
355
356    // Count conditions
357    let condition_count = where_clause.conditions.len();
358    details.push(format!("Conditions: {}", condition_count));
359
360    // Analyze each condition
361    for (i, condition) in where_clause.conditions.iter().enumerate() {
362        let expr_detail = analyze_expression(&condition.expr);
363        details.push(format!("  Condition {}: {}", i + 1, expr_detail));
364
365        if let Some(connector) = &condition.connector {
366            details.push(format!("  Connector: {:?}", connector));
367        }
368    }
369
370    details
371}
372
373/// Helper to analyze SQL expressions
374pub fn analyze_expression(expr: &SqlExpression) -> String {
375    match expr {
376        SqlExpression::Column(name) => format!("Column({})", name),
377        SqlExpression::BinaryOp { left, op, right } => {
378            format!(
379                "BinaryOp({} {} {})",
380                analyze_expression(left),
381                op,
382                analyze_expression(right)
383            )
384        }
385        SqlExpression::FunctionCall { name, args, .. } => {
386            format!("Function({}, {} args)", name, args.len())
387        }
388        SqlExpression::Between { expr, .. } => {
389            format!("Between({})", analyze_expression(expr))
390        }
391        SqlExpression::InList { expr, values } => {
392            format!(
393                "InList({}, {} values)",
394                analyze_expression(expr),
395                values.len()
396            )
397        }
398        SqlExpression::NotInList { expr, values } => {
399            format!(
400                "NotInList({}, {} values)",
401                analyze_expression(expr),
402                values.len()
403            )
404        }
405        _ => format!("{:?}", expr),
406    }
407}