sql_cli/services/
query_execution_service.rs

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