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