sql_cli/services/
query_orchestrator.rs

1use crate::app_state_container::AppStateContainer;
2use crate::config::config::BehaviorConfig;
3use crate::data::data_view::DataView;
4use crate::services::{QueryExecutionResult, QueryExecutionService};
5use crate::ui::search::vim_search_adapter::VimSearchAdapter;
6use anyhow::Result;
7use std::cell::RefCell;
8use tracing::{debug, info};
9
10/// Orchestrates the entire query execution flow
11/// This handles all the side effects and state management around query execution
12pub struct QueryOrchestrator {
13    query_execution_service: QueryExecutionService,
14}
15
16impl QueryOrchestrator {
17    #[must_use]
18    pub fn new(case_insensitive: bool, auto_hide_empty: bool) -> Self {
19        Self {
20            query_execution_service: QueryExecutionService::new(case_insensitive, auto_hide_empty),
21        }
22    }
23
24    #[must_use]
25    pub fn with_behavior_config(behavior_config: BehaviorConfig) -> Self {
26        Self {
27            query_execution_service: QueryExecutionService::with_behavior_config(behavior_config),
28        }
29    }
30
31    /// Execute a query with all necessary state management
32    pub fn execute_query(
33        &mut self,
34        query: &str,
35        state_container: &mut AppStateContainer,
36        vim_search_adapter: &RefCell<VimSearchAdapter>,
37    ) -> Result<QueryExecutionContext> {
38        info!(target: "query", "Executing query: {}", query);
39
40        // 1. Clear all search-related state before executing new query
41        self.clear_all_search_state(state_container, vim_search_adapter);
42
43        // 2. Record the query being executed
44        self.record_query_execution(query, state_container);
45
46        // 3. Set executing status
47        state_container.set_status_message(format!("Executing query: '{query}'..."));
48
49        // 4. Execute the query
50        let current_dataview = state_container.get_buffer_dataview();
51        let original_source = state_container.get_original_source();
52
53        // Debug: log what we're passing to the query service
54        if let Some(orig) = original_source {
55            debug!(
56                "QueryOrchestrator: Have original source with {} columns",
57                orig.column_count()
58            );
59        } else {
60            debug!("QueryOrchestrator: WARNING - No original source available!");
61        }
62
63        if let Some(view) = current_dataview {
64            debug!(
65                "QueryOrchestrator: Current view has {} columns, source has {} columns",
66                view.column_count(),
67                view.source().column_count()
68            );
69        }
70
71        let result =
72            self.query_execution_service
73                .execute(query, current_dataview, original_source)?;
74
75        // 5. Clear any active filters (new query should start with clean state)
76        self.clear_all_filters(state_container);
77
78        // 6. Return the context for the TUI to apply
79        Ok(QueryExecutionContext {
80            result,
81            query: query.to_string(),
82        })
83    }
84
85    /// Clear all search-related state
86    fn clear_all_search_state(
87        &self,
88        state_container: &mut AppStateContainer,
89        vim_search_adapter: &RefCell<VimSearchAdapter>,
90    ) {
91        // Clear container search states
92        state_container.clear_search();
93        state_container.clear_column_search();
94
95        // Clear vim search adapter state
96        vim_search_adapter.borrow_mut().cancel_search();
97    }
98
99    /// Record that a query is being executed
100    fn record_query_execution(&self, query: &str, state_container: &mut AppStateContainer) {
101        state_container.set_last_query(query.to_string());
102        state_container.set_last_executed_query(query.to_string());
103    }
104
105    /// Clear all filter-related state
106    fn clear_all_filters(&self, state_container: &mut AppStateContainer) {
107        state_container.set_filter_pattern(String::new());
108        state_container.set_fuzzy_filter_pattern(String::new());
109        state_container.set_filter_active(false);
110        state_container.set_fuzzy_filter_active(false);
111    }
112
113    /// Update service configuration
114    pub fn set_case_insensitive(&mut self, case_insensitive: bool) {
115        self.query_execution_service
116            .set_case_insensitive(case_insensitive);
117    }
118
119    pub fn set_auto_hide_empty(&mut self, auto_hide: bool) {
120        self.query_execution_service.set_auto_hide_empty(auto_hide);
121    }
122}
123
124/// Context returned from query execution
125pub struct QueryExecutionContext {
126    pub result: QueryExecutionResult,
127    pub query: String,
128}
129
130impl QueryExecutionContext {
131    /// Apply this context to the TUI state
132    /// This is what the TUI will call to update its state after query execution
133    pub fn apply_to_tui(
134        self,
135        state_container: &mut AppStateContainer,
136        update_viewport: impl FnOnce(DataView),
137        calculate_widths: impl FnOnce(),
138        reset_table: impl FnOnce(),
139    ) -> Result<()> {
140        // Apply the new DataView
141        state_container.set_dataview(Some(self.result.dataview.clone()));
142
143        // Update viewport (delegate to TUI)
144        update_viewport(self.result.dataview.clone());
145
146        // Update navigation state
147        state_container
148            .update_data_size(self.result.stats.row_count, self.result.stats.column_count);
149
150        // Calculate column widths (delegate to TUI)
151        calculate_widths();
152
153        // Update status message
154        state_container.set_status_message(self.result.status_message());
155
156        // Add to history
157        state_container
158            .command_history_mut()
159            .add_entry_with_schema(
160                self.query.clone(),
161                true,
162                Some(self.result.stats.execution_time.as_millis() as u64),
163                self.result.column_names(),
164                Some(self.result.table_name()),
165            )?;
166
167        // Switch to results mode
168        state_container.set_mode(crate::buffer::AppMode::Results);
169
170        // Reset table (delegate to TUI)
171        reset_table();
172
173        Ok(())
174    }
175}