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