sql_cli/services/
query_execution_service.rs1use 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
8pub struct QueryExecutionResult {
10 pub dataview: DataView,
12
13 pub stats: QueryStats,
15
16 pub hidden_columns: Vec<String>,
18
19 pub query: String,
21}
22
23pub 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
31pub 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 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 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 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 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 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 let table_arc = Arc::new(source_table);
102
103 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 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 hidden_columns = vec![format!("{} columns", hidden)];
118 }
119 }
120
121 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 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 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 pub fn column_names(&self) -> Vec<String> {
167 self.dataview.column_names()
168 }
169
170 pub fn table_name(&self) -> String {
172 self.dataview.source().name.clone()
173 }
174}