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        let mut trace = if let Some(ctx) = debug_context {
106            QueryTrace::new(ctx)
107        } else {
108            QueryTrace::new(DebugContext::new(DebugLevel::Off))
109        };
110        // Check if query is using DUAL table or has no FROM clause
111        use crate::query_plan::{CTEHoister, ExpressionLifter};
112        use crate::sql::recursive_parser::Parser;
113
114        let parse_timer = ScopedTimer::new();
115        trace.log("PARSER", "START", format!("Parsing query: {}", query));
116
117        let mut parser = Parser::new(query);
118        let mut statement = parser
119            .parse()
120            .map_err(|e| anyhow::anyhow!("Parse error: {}", e))?;
121
122        trace.log_timed(
123            "PARSER",
124            "COMPLETE",
125            format!(
126                "Parsed {} CTEs, FROM: {:?}",
127                statement.ctes.len(),
128                statement.from_table
129            ),
130            parse_timer.elapsed_us(),
131        );
132
133        // Apply expression lifting for column alias dependencies
134        let lift_timer = ScopedTimer::new();
135        trace.log("OPTIMIZER", "LIFT_START", "Starting expression lifting");
136
137        let mut expr_lifter = ExpressionLifter::new();
138        let lifted = expr_lifter.lift_expressions(&mut statement);
139        if !lifted.is_empty() {
140            info!(
141                "Applied expression lifting - {} CTEs generated",
142                lifted.len()
143            );
144            trace.log_timed(
145                "OPTIMIZER",
146                "LIFT_COMPLETE",
147                format!("Generated {} lifted CTEs", lifted.len()),
148                lift_timer.elapsed_us(),
149            );
150        } else {
151            trace.log_timed(
152                "OPTIMIZER",
153                "LIFT_COMPLETE",
154                "No expressions needed lifting",
155                lift_timer.elapsed_us(),
156            );
157        }
158
159        // Apply CTE hoisting to handle nested CTEs
160        let hoist_timer = ScopedTimer::new();
161        trace.log("OPTIMIZER", "HOIST_START", "Starting CTE hoisting");
162
163        let original_cte_count = statement.ctes.len();
164        statement = CTEHoister::hoist_ctes(statement);
165
166        trace.log_timed(
167            "OPTIMIZER",
168            "HOIST_COMPLETE",
169            format!(
170                "CTEs after hoisting: {} (was {})",
171                statement.ctes.len(),
172                original_cte_count
173            ),
174            hoist_timer.elapsed_us(),
175        );
176
177        let uses_dual = statement
178            .from_table
179            .as_ref()
180            .is_some_and(|t| t.to_uppercase() == "DUAL");
181
182        let no_from_clause = statement.from_table.is_none();
183
184        // 1. Get the source DataTable - use DUAL for special cases
185        let source_table = if uses_dual || no_from_clause {
186            info!("QueryExecutionService: Using DUAL table for expression evaluation");
187            crate::data::datatable::DataTable::dual()
188        } else if let Some(original) = original_source {
189            // Use the original unmodified DataTable for queries
190            info!(
191                "QueryExecutionService: Using original source with {} columns: {:?}",
192                original.column_count(),
193                original.column_names()
194            );
195            debug!(
196                "QueryExecutionService: DEBUG - Using original source with {} columns for query",
197                original.column_count()
198            );
199            original.clone()
200        } else if let Some(view) = current_dataview {
201            // Fallback to current view's source if no original available
202            info!(
203                "QueryExecutionService: WARNING - No original source, using current view's source with {} columns: {:?}",
204                view.source().column_count(),
205                view.source().column_names()
206            );
207            debug!(
208                "QueryExecutionService: DEBUG WARNING - No original source, using view source with {} columns",
209                view.source().column_count()
210            );
211            view.source().clone()
212        } else {
213            return Err(anyhow::anyhow!("No data loaded"));
214        };
215
216        // Clone the Arc to the DataTable (cheap - just increments ref count)
217        let table_arc = Arc::new(source_table);
218
219        // 2. Execute the query
220        let query_start = std::time::Instant::now();
221        trace.log(
222            "EXECUTOR",
223            "START",
224            format!(
225                "Executing with {} rows, {} columns",
226                table_arc.row_count(),
227                table_arc.column_count()
228            ),
229        );
230        let engine = if let Some(ref config) = self.behavior_config {
231            QueryEngine::with_behavior_config(config.clone())
232        } else {
233            QueryEngine::with_case_insensitive_and_date_notation(
234                self.case_insensitive,
235                self.date_notation.clone(),
236            )
237        };
238        let mut new_dataview = engine.execute(table_arc, query)?;
239        let query_engine_time = query_start.elapsed();
240
241        trace.log_timed(
242            "EXECUTOR",
243            "COMPLETE",
244            format!("Query returned {} rows", new_dataview.row_count()),
245            query_engine_time.as_micros() as u64,
246        );
247
248        // 3. Auto-hide empty columns if configured
249        let mut hidden_columns = Vec::new();
250        if self.auto_hide_empty {
251            let hidden = new_dataview.hide_empty_columns();
252            if hidden > 0 {
253                info!("Auto-hidden {} empty columns after query execution", hidden);
254                // Collect the hidden column names (we'd need to track this in hide_empty_columns)
255                // For now, just track the count
256                hidden_columns = vec![format!("{} columns", hidden)];
257            }
258        }
259
260        // 4. Build the result
261        let stats = QueryStats {
262            row_count: new_dataview.row_count(),
263            column_count: new_dataview.column_count(),
264            execution_time: query_start.elapsed(),
265            query_engine_time,
266        };
267
268        let debug_output = if trace.is_enabled() {
269            Some(trace.format_output())
270        } else {
271            None
272        };
273
274        Ok(QueryExecutionResult {
275            dataview: new_dataview,
276            stats,
277            hidden_columns,
278            query: query.to_string(),
279            execution_plan: None,
280            debug_trace: debug_output,
281        })
282    }
283
284    /// Update configuration
285    pub fn set_case_insensitive(&mut self, case_insensitive: bool) {
286        self.case_insensitive = case_insensitive;
287    }
288
289    pub fn set_auto_hide_empty(&mut self, auto_hide: bool) {
290        self.auto_hide_empty = auto_hide;
291    }
292
293    pub fn set_date_notation(&mut self, date_notation: String) {
294        self.date_notation = date_notation;
295    }
296}
297
298impl QueryExecutionResult {
299    /// Generate a user-friendly status message
300    #[must_use]
301    pub fn status_message(&self) -> String {
302        let hidden_msg = if self.hidden_columns.is_empty() {
303            String::new()
304        } else {
305            format!(" ({} auto-hidden)", self.hidden_columns.len())
306        };
307
308        format!(
309            "Query executed: {} rows, {} columns{} ({} ms)",
310            self.stats.row_count,
311            self.stats.column_count,
312            hidden_msg,
313            self.stats.execution_time.as_millis()
314        )
315    }
316
317    /// Get column names for history tracking
318    #[must_use]
319    pub fn column_names(&self) -> Vec<String> {
320        self.dataview.column_names()
321    }
322
323    /// Get table name for history tracking
324    #[must_use]
325    pub fn table_name(&self) -> String {
326        self.dataview.source().name.clone()
327    }
328}