sql_cli/services/
query_execution_service.rs

1use crate::config::config::BehaviorConfig;
2use crate::data::data_view::DataView;
3use crate::data::query_engine::QueryEngine;
4use crate::debug_trace::{DebugContext, DebugLevel, QueryTrace, ScopedTimer};
5use crate::execution_plan::ExecutionPlan;
6use anyhow::Result;
7use std::sync::Arc;
8use std::time::Duration;
9use tracing::{debug, info};
10
11/// Result of executing a query
12pub struct QueryExecutionResult {
13    /// The resulting `DataView` to display
14    pub dataview: DataView,
15
16    /// Execution statistics
17    pub stats: QueryStats,
18
19    /// Columns that were auto-hidden (if any)
20    pub hidden_columns: Vec<String>,
21
22    /// The query that was executed
23    pub query: String,
24
25    /// The execution plan (if tracked)
26    pub execution_plan: Option<ExecutionPlan>,
27
28    /// Debug trace output (if debug was enabled)
29    pub debug_trace: Option<String>,
30}
31
32/// Statistics about query execution
33pub struct QueryStats {
34    pub row_count: usize,
35    pub column_count: usize,
36    pub execution_time: Duration,
37    pub query_engine_time: Duration,
38}
39
40/// Service responsible for executing queries and managing the resulting `DataView`
41pub struct QueryExecutionService {
42    case_insensitive: bool,
43    auto_hide_empty: bool,
44    date_notation: String,
45    behavior_config: Option<BehaviorConfig>,
46}
47
48impl QueryExecutionService {
49    #[must_use]
50    pub fn new(case_insensitive: bool, auto_hide_empty: bool) -> Self {
51        Self {
52            case_insensitive,
53            auto_hide_empty,
54            date_notation: "us".to_string(),
55            behavior_config: None,
56        }
57    }
58
59    #[must_use]
60    pub fn with_behavior_config(behavior_config: BehaviorConfig) -> Self {
61        let case_insensitive = behavior_config.case_insensitive_default;
62        let auto_hide_empty = behavior_config.hide_empty_columns;
63        let date_notation = behavior_config.default_date_notation.clone();
64        Self {
65            case_insensitive,
66            auto_hide_empty,
67            date_notation,
68            behavior_config: Some(behavior_config),
69        }
70    }
71
72    #[must_use]
73    pub fn with_date_notation(
74        case_insensitive: bool,
75        auto_hide_empty: bool,
76        date_notation: String,
77    ) -> Self {
78        Self {
79            case_insensitive,
80            auto_hide_empty,
81            date_notation,
82            behavior_config: None,
83        }
84    }
85
86    /// Execute a query and return the result
87    /// This encapsulates all the query execution logic that was previously in `EnhancedTui`
88    pub fn execute(
89        &self,
90        query: &str,
91        current_dataview: Option<&DataView>,
92        original_source: Option<&crate::data::datatable::DataTable>,
93    ) -> Result<QueryExecutionResult> {
94        self.execute_with_debug(query, current_dataview, original_source, None)
95    }
96
97    /// Execute a query with optional debug tracing
98    pub fn execute_with_debug(
99        &self,
100        query: &str,
101        current_dataview: Option<&DataView>,
102        original_source: Option<&crate::data::datatable::DataTable>,
103        debug_context: Option<DebugContext>,
104    ) -> Result<QueryExecutionResult> {
105        // Check if debug context is enabled before moving it
106        let debug_enabled = debug_context.is_some();
107
108        let mut trace = if let Some(ctx) = debug_context {
109            QueryTrace::new(ctx)
110        } else {
111            QueryTrace::new(DebugContext::new(DebugLevel::Off))
112        };
113        // Check if query is using DUAL table or has no FROM clause
114        use crate::query_plan::{CTEHoister, ExpressionLifter};
115        use crate::sql::recursive_parser::Parser;
116
117        let parse_timer = ScopedTimer::new();
118        trace.log("PARSER", "START", format!("Parsing query: {}", query));
119
120        // Enable parser's internal debug trace if debug context is active
121        let mut parser = if debug_enabled {
122            Parser::new(query).with_debug_trace(true)
123        } else {
124            Parser::new(query)
125        };
126        let mut statement = parser
127            .parse()
128            .map_err(|e| anyhow::anyhow!("Parse error: {}", e))?;
129
130        trace.log_timed(
131            "PARSER",
132            "COMPLETE",
133            format!(
134                "Parsed {} CTEs, FROM: {:?}",
135                statement.ctes.len(),
136                statement.from_table
137            ),
138            parse_timer.elapsed_us(),
139        );
140
141        // Apply expression lifting for column alias dependencies
142        let lift_timer = ScopedTimer::new();
143        trace.log("OPTIMIZER", "LIFT_START", "Starting expression lifting");
144
145        let mut expr_lifter = ExpressionLifter::new();
146        let lifted = expr_lifter.lift_expressions(&mut statement);
147        if !lifted.is_empty() {
148            info!(
149                "Applied expression lifting - {} CTEs generated",
150                lifted.len()
151            );
152            trace.log_timed(
153                "OPTIMIZER",
154                "LIFT_COMPLETE",
155                format!("Generated {} lifted CTEs", lifted.len()),
156                lift_timer.elapsed_us(),
157            );
158        } else {
159            trace.log_timed(
160                "OPTIMIZER",
161                "LIFT_COMPLETE",
162                "No expressions needed lifting",
163                lift_timer.elapsed_us(),
164            );
165        }
166
167        // Apply CTE hoisting to handle nested CTEs
168        let hoist_timer = ScopedTimer::new();
169        trace.log("OPTIMIZER", "HOIST_START", "Starting CTE hoisting");
170
171        let original_cte_count = statement.ctes.len();
172        statement = CTEHoister::hoist_ctes(statement);
173
174        trace.log_timed(
175            "OPTIMIZER",
176            "HOIST_COMPLETE",
177            format!(
178                "CTEs after hoisting: {} (was {})",
179                statement.ctes.len(),
180                original_cte_count
181            ),
182            hoist_timer.elapsed_us(),
183        );
184
185        let uses_dual = statement
186            .from_table
187            .as_ref()
188            .is_some_and(|t| t.to_uppercase() == "DUAL");
189
190        let no_from_clause = statement.from_table.is_none();
191
192        // 1. Get the source DataTable - use DUAL for special cases
193        let source_table = if uses_dual || no_from_clause {
194            info!("QueryExecutionService: Using DUAL table for expression evaluation");
195            crate::data::datatable::DataTable::dual()
196        } else if let Some(original) = original_source {
197            // Use the original unmodified DataTable for queries
198            info!(
199                "QueryExecutionService: Using original source with {} columns: {:?}",
200                original.column_count(),
201                original.column_names()
202            );
203            debug!(
204                "QueryExecutionService: DEBUG - Using original source with {} columns for query",
205                original.column_count()
206            );
207            original.clone()
208        } else if let Some(view) = current_dataview {
209            // Fallback to current view's source if no original available
210            info!(
211                "QueryExecutionService: WARNING - No original source, using current view's source with {} columns: {:?}",
212                view.source().column_count(),
213                view.source().column_names()
214            );
215            debug!(
216                "QueryExecutionService: DEBUG WARNING - No original source, using view source with {} columns",
217                view.source().column_count()
218            );
219            view.source().clone()
220        } else {
221            return Err(anyhow::anyhow!("No data loaded"));
222        };
223
224        // Clone the Arc to the DataTable (cheap - just increments ref count)
225        let table_arc = Arc::new(source_table);
226
227        // 2. Execute the query
228        let query_start = std::time::Instant::now();
229        trace.log(
230            "EXECUTOR",
231            "START",
232            format!(
233                "Executing with {} rows, {} columns",
234                table_arc.row_count(),
235                table_arc.column_count()
236            ),
237        );
238        let engine = if let Some(ref config) = self.behavior_config {
239            QueryEngine::with_behavior_config(config.clone())
240        } else {
241            QueryEngine::with_case_insensitive_and_date_notation(
242                self.case_insensitive,
243                self.date_notation.clone(),
244            )
245        };
246        let mut new_dataview = engine.execute(table_arc, query)?;
247        let query_engine_time = query_start.elapsed();
248
249        trace.log_timed(
250            "EXECUTOR",
251            "COMPLETE",
252            format!("Query returned {} rows", new_dataview.row_count()),
253            query_engine_time.as_micros() as u64,
254        );
255
256        // 3. Auto-hide empty columns if configured
257        let mut hidden_columns = Vec::new();
258        if self.auto_hide_empty {
259            let hidden = new_dataview.hide_empty_columns();
260            if hidden > 0 {
261                info!("Auto-hidden {} empty columns after query execution", hidden);
262                // Collect the hidden column names (we'd need to track this in hide_empty_columns)
263                // For now, just track the count
264                hidden_columns = vec![format!("{} columns", hidden)];
265            }
266        }
267
268        // 4. Build the result
269        let stats = QueryStats {
270            row_count: new_dataview.row_count(),
271            column_count: new_dataview.column_count(),
272            execution_time: query_start.elapsed(),
273            query_engine_time,
274        };
275
276        let debug_output = if trace.is_enabled() {
277            Some(trace.format_output())
278        } else {
279            None
280        };
281
282        Ok(QueryExecutionResult {
283            dataview: new_dataview,
284            stats,
285            hidden_columns,
286            query: query.to_string(),
287            execution_plan: None,
288            debug_trace: debug_output,
289        })
290    }
291
292    /// Update configuration
293    pub fn set_case_insensitive(&mut self, case_insensitive: bool) {
294        self.case_insensitive = case_insensitive;
295    }
296
297    pub fn set_auto_hide_empty(&mut self, auto_hide: bool) {
298        self.auto_hide_empty = auto_hide;
299    }
300
301    pub fn set_date_notation(&mut self, date_notation: String) {
302        self.date_notation = date_notation;
303    }
304}
305
306impl QueryExecutionResult {
307    /// Generate a user-friendly status message
308    #[must_use]
309    pub fn status_message(&self) -> String {
310        let hidden_msg = if self.hidden_columns.is_empty() {
311            String::new()
312        } else {
313            format!(" ({} auto-hidden)", self.hidden_columns.len())
314        };
315
316        format!(
317            "Query executed: {} rows, {} columns{} ({} ms)",
318            self.stats.row_count,
319            self.stats.column_count,
320            hidden_msg,
321            self.stats.execution_time.as_millis()
322        )
323    }
324
325    /// Get column names for history tracking
326    #[must_use]
327    pub fn column_names(&self) -> Vec<String> {
328        self.dataview.column_names()
329    }
330
331    /// Get table name for history tracking
332    #[must_use]
333    pub fn table_name(&self) -> String {
334        self.dataview.source().name.clone()
335    }
336}