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::execution_plan::ExecutionPlan;
5use anyhow::Result;
6use std::sync::Arc;
7use std::time::Duration;
8use tracing::{debug, info};
9
10/// Result of executing a query
11pub struct QueryExecutionResult {
12    /// The resulting `DataView` to display
13    pub dataview: DataView,
14
15    /// Execution statistics
16    pub stats: QueryStats,
17
18    /// Columns that were auto-hidden (if any)
19    pub hidden_columns: Vec<String>,
20
21    /// The query that was executed
22    pub query: String,
23
24    /// The execution plan (if tracked)
25    pub execution_plan: Option<ExecutionPlan>,
26}
27
28/// Statistics about query execution
29pub struct QueryStats {
30    pub row_count: usize,
31    pub column_count: usize,
32    pub execution_time: Duration,
33    pub query_engine_time: Duration,
34}
35
36/// Service responsible for executing queries and managing the resulting `DataView`
37pub struct QueryExecutionService {
38    case_insensitive: bool,
39    auto_hide_empty: bool,
40    date_notation: String,
41    behavior_config: Option<BehaviorConfig>,
42}
43
44impl QueryExecutionService {
45    #[must_use]
46    pub fn new(case_insensitive: bool, auto_hide_empty: bool) -> Self {
47        Self {
48            case_insensitive,
49            auto_hide_empty,
50            date_notation: "us".to_string(),
51            behavior_config: None,
52        }
53    }
54
55    #[must_use]
56    pub fn with_behavior_config(behavior_config: BehaviorConfig) -> Self {
57        let case_insensitive = behavior_config.case_insensitive_default;
58        let auto_hide_empty = behavior_config.hide_empty_columns;
59        let date_notation = behavior_config.default_date_notation.clone();
60        Self {
61            case_insensitive,
62            auto_hide_empty,
63            date_notation,
64            behavior_config: Some(behavior_config),
65        }
66    }
67
68    #[must_use]
69    pub fn with_date_notation(
70        case_insensitive: bool,
71        auto_hide_empty: bool,
72        date_notation: String,
73    ) -> Self {
74        Self {
75            case_insensitive,
76            auto_hide_empty,
77            date_notation,
78            behavior_config: None,
79        }
80    }
81
82    /// Execute a query and return the result
83    /// This encapsulates all the query execution logic that was previously in `EnhancedTui`
84    pub fn execute(
85        &self,
86        query: &str,
87        current_dataview: Option<&DataView>,
88        original_source: Option<&crate::data::datatable::DataTable>,
89    ) -> Result<QueryExecutionResult> {
90        // Check if query is using DUAL table or has no FROM clause
91        use crate::sql::recursive_parser::Parser;
92        let mut parser = Parser::new(query);
93        let statement = parser
94            .parse()
95            .map_err(|e| anyhow::anyhow!("Parse error: {}", e))?;
96
97        let uses_dual = statement
98            .from_table
99            .as_ref()
100            .is_some_and(|t| t.to_uppercase() == "DUAL");
101
102        let no_from_clause = statement.from_table.is_none();
103
104        // 1. Get the source DataTable - use DUAL for special cases
105        let source_table = if uses_dual || no_from_clause {
106            info!("QueryExecutionService: Using DUAL table for expression evaluation");
107            crate::data::datatable::DataTable::dual()
108        } else if let Some(original) = original_source {
109            // Use the original unmodified DataTable for queries
110            info!(
111                "QueryExecutionService: Using original source with {} columns: {:?}",
112                original.column_count(),
113                original.column_names()
114            );
115            debug!(
116                "QueryExecutionService: DEBUG - Using original source with {} columns for query",
117                original.column_count()
118            );
119            original.clone()
120        } else if let Some(view) = current_dataview {
121            // Fallback to current view's source if no original available
122            info!(
123                "QueryExecutionService: WARNING - No original source, using current view's source with {} columns: {:?}",
124                view.source().column_count(),
125                view.source().column_names()
126            );
127            debug!(
128                "QueryExecutionService: DEBUG WARNING - No original source, using view source with {} columns",
129                view.source().column_count()
130            );
131            view.source().clone()
132        } else {
133            return Err(anyhow::anyhow!("No data loaded"));
134        };
135
136        // Clone the Arc to the DataTable (cheap - just increments ref count)
137        let table_arc = Arc::new(source_table);
138
139        // 2. Execute the query
140        let query_start = std::time::Instant::now();
141        let engine = if let Some(ref config) = self.behavior_config {
142            QueryEngine::with_behavior_config(config.clone())
143        } else {
144            QueryEngine::with_case_insensitive_and_date_notation(
145                self.case_insensitive,
146                self.date_notation.clone(),
147            )
148        };
149        let mut new_dataview = engine.execute(table_arc, query)?;
150        let query_engine_time = query_start.elapsed();
151
152        // 3. Auto-hide empty columns if configured
153        let mut hidden_columns = Vec::new();
154        if self.auto_hide_empty {
155            let hidden = new_dataview.hide_empty_columns();
156            if hidden > 0 {
157                info!("Auto-hidden {} empty columns after query execution", hidden);
158                // Collect the hidden column names (we'd need to track this in hide_empty_columns)
159                // For now, just track the count
160                hidden_columns = vec![format!("{} columns", hidden)];
161            }
162        }
163
164        // 4. Build the result
165        let stats = QueryStats {
166            row_count: new_dataview.row_count(),
167            column_count: new_dataview.column_count(),
168            execution_time: query_start.elapsed(),
169            query_engine_time,
170        };
171
172        Ok(QueryExecutionResult {
173            dataview: new_dataview,
174            stats,
175            hidden_columns,
176            query: query.to_string(),
177            execution_plan: None,
178        })
179    }
180
181    /// Update configuration
182    pub fn set_case_insensitive(&mut self, case_insensitive: bool) {
183        self.case_insensitive = case_insensitive;
184    }
185
186    pub fn set_auto_hide_empty(&mut self, auto_hide: bool) {
187        self.auto_hide_empty = auto_hide;
188    }
189
190    pub fn set_date_notation(&mut self, date_notation: String) {
191        self.date_notation = date_notation;
192    }
193}
194
195impl QueryExecutionResult {
196    /// Generate a user-friendly status message
197    #[must_use]
198    pub fn status_message(&self) -> String {
199        let hidden_msg = if self.hidden_columns.is_empty() {
200            String::new()
201        } else {
202            format!(" ({} auto-hidden)", self.hidden_columns.len())
203        };
204
205        format!(
206            "Query executed: {} rows, {} columns{} ({} ms)",
207            self.stats.row_count,
208            self.stats.column_count,
209            hidden_msg,
210            self.stats.execution_time.as_millis()
211        )
212    }
213
214    /// Get column names for history tracking
215    #[must_use]
216    pub fn column_names(&self) -> Vec<String> {
217        self.dataview.column_names()
218    }
219
220    /// Get table name for history tracking
221    #[must_use]
222    pub fn table_name(&self) -> String {
223        self.dataview.source().name.clone()
224    }
225}