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