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