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