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        // 1. Get the source DataTable - prefer original source if available
54        let source_table = if let Some(original) = original_source {
55            // Use the original unmodified DataTable for queries
56            info!(
57                "QueryExecutionService: Using original source with {} columns: {:?}",
58                original.column_count(),
59                original.column_names()
60            );
61            debug!(
62                "QueryExecutionService: DEBUG - Using original source with {} columns for query",
63                original.column_count()
64            );
65            original.clone()
66        } else if let Some(view) = current_dataview {
67            // Fallback to current view's source if no original available
68            info!(
69                "QueryExecutionService: WARNING - No original source, using current view's source with {} columns: {:?}",
70                view.source().column_count(),
71                view.source().column_names()
72            );
73            debug!(
74                "QueryExecutionService: DEBUG WARNING - No original source, using view source with {} columns",
75                view.source().column_count()
76            );
77            view.source().clone()
78        } else {
79            return Err(anyhow::anyhow!("No data loaded"));
80        };
81
82        // Clone the Arc to the DataTable (cheap - just increments ref count)
83        let table_arc = Arc::new(source_table);
84
85        // 2. Execute the query
86        let query_start = std::time::Instant::now();
87        let engine = QueryEngine::with_case_insensitive(self.case_insensitive);
88        let mut new_dataview = engine.execute(table_arc, query)?;
89        let query_engine_time = query_start.elapsed();
90
91        // 3. Auto-hide empty columns if configured
92        let mut hidden_columns = Vec::new();
93        if self.auto_hide_empty {
94            let hidden = new_dataview.hide_empty_columns();
95            if hidden > 0 {
96                info!("Auto-hidden {} empty columns after query execution", hidden);
97                // Collect the hidden column names (we'd need to track this in hide_empty_columns)
98                // For now, just track the count
99                hidden_columns = vec![format!("{} columns", hidden)];
100            }
101        }
102
103        // 4. Build the result
104        let stats = QueryStats {
105            row_count: new_dataview.row_count(),
106            column_count: new_dataview.column_count(),
107            execution_time: query_start.elapsed(),
108            query_engine_time,
109        };
110
111        Ok(QueryExecutionResult {
112            dataview: new_dataview,
113            stats,
114            hidden_columns,
115            query: query.to_string(),
116        })
117    }
118
119    /// Update configuration
120    pub fn set_case_insensitive(&mut self, case_insensitive: bool) {
121        self.case_insensitive = case_insensitive;
122    }
123
124    pub fn set_auto_hide_empty(&mut self, auto_hide: bool) {
125        self.auto_hide_empty = auto_hide;
126    }
127}
128
129impl QueryExecutionResult {
130    /// Generate a user-friendly status message
131    pub fn status_message(&self) -> String {
132        let hidden_msg = if !self.hidden_columns.is_empty() {
133            format!(" ({} auto-hidden)", self.hidden_columns.len())
134        } else {
135            String::new()
136        };
137
138        format!(
139            "Query executed: {} rows, {} columns{} ({} ms)",
140            self.stats.row_count,
141            self.stats.column_count,
142            hidden_msg,
143            self.stats.execution_time.as_millis()
144        )
145    }
146
147    /// Get column names for history tracking
148    pub fn column_names(&self) -> Vec<String> {
149        self.dataview.column_names()
150    }
151
152    /// Get table name for history tracking
153    pub fn table_name(&self) -> String {
154        self.dataview.source().name.clone()
155    }
156}