1use akar_common::vector::DataChunk;
4use std::fmt;
5use std::time::Duration;
6
7#[derive(Debug, Clone)]
9pub struct QuerySummary {
10 pub elapsed: Duration,
12 pub compile_time: Duration,
14 pub execution_time: Duration,
16}
17
18impl Default for QuerySummary {
19 fn default() -> Self {
20 Self {
21 elapsed: Duration::ZERO,
22 compile_time: Duration::ZERO,
23 execution_time: Duration::ZERO,
24 }
25 }
26}
27
28impl fmt::Display for QuerySummary {
29 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
30 write!(
31 f,
32 "Query executed in {:.2}ms (compile: {:.2}ms, execution: {:.2}ms)",
33 self.elapsed.as_secs_f64() * 1000.0,
34 self.compile_time.as_secs_f64() * 1000.0,
35 self.execution_time.as_secs_f64() * 1000.0,
36 )
37 }
38}
39
40#[derive(Debug, Clone)]
66pub struct QueryResult {
67 pub chunks: Vec<DataChunk>,
68 pub num_rows: usize,
69 pub num_columns: usize,
70 pub success: bool,
71 pub error_message: Option<String>,
72 pub message: Option<String>,
74 pub summary: Option<QuerySummary>,
76}
77
78impl QueryResult {
79 pub fn new(chunks: Vec<DataChunk>) -> Self {
80 let num_rows = chunks.iter().map(|c| c.size).sum();
81 let num_columns = chunks.first().map(|c| c.num_fields()).unwrap_or(0);
82 Self {
83 chunks,
84 num_rows,
85 num_columns,
86 success: true,
87 error_message: None,
88 message: None,
89 summary: None,
90 }
91 }
92
93 pub fn success_message(msg: String) -> Self {
95 Self {
96 chunks: Vec::new(),
97 num_rows: 0,
98 num_columns: 0,
99 success: true,
100 error_message: None,
101 message: Some(msg),
102 summary: None,
103 }
104 }
105
106 pub fn with_summary(mut self, summary: QuerySummary) -> Self {
108 self.summary = Some(summary);
109 self
110 }
111
112 pub fn error(msg: String) -> Self {
113 Self {
114 chunks: Vec::new(),
115 num_rows: 0,
116 num_columns: 0,
117 success: false,
118 error_message: Some(msg),
119 message: None,
120 summary: None,
121 }
122 }
123
124 pub fn is_success(&self) -> bool {
125 self.success
126 }
127
128 pub fn num_rows(&self) -> usize {
129 self.num_rows
130 }
131
132 pub fn num_columns(&self) -> usize {
133 self.num_columns
134 }
135
136 pub fn result_summary(&self) -> String {
138 if let Some(head) = result_summary_head(
139 self.message.as_deref(),
140 self.success,
141 self.error_message.as_deref(),
142 self.num_rows != 0,
143 ) {
144 return head;
145 }
146 format!("Returned {} rows in {} columns", self.num_rows, self.num_columns)
147 }
148}
149
150pub(crate) fn result_summary_head(
157 message: Option<&str>,
158 success: bool,
159 error_message: Option<&str>,
160 has_rows: bool,
161) -> Option<String> {
162 if let Some(msg) = message {
163 return Some(msg.to_string());
164 }
165 if !success {
166 return Some(format!("Error: {}", error_message.unwrap_or("Unknown error")));
167 }
168 if !has_rows {
169 return Some("(empty result)".into());
170 }
171 None
172}
173
174impl fmt::Display for QueryResult {
175 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
176 if let Some(head) = result_summary_head(
177 self.message.as_deref(),
178 self.success,
179 self.error_message.as_deref(),
180 !self.chunks.is_empty(),
181 ) {
182 return write!(f, "{head}");
183 }
184 for (i, chunk) in self.chunks.iter().enumerate() {
185 if i > 0 {
186 writeln!(f)?;
187 }
188 write!(f, "Chunk {}: {} rows, {} columns", i, chunk.size, chunk.num_fields())?;
189 }
190 Ok(())
191 }
192}
193
194#[cfg(test)]
195mod tests {
196 use super::*;
197
198 #[test]
199 fn test_query_result_summary_cases() {
200 let mut r = QueryResult::new(Vec::new());
202 r.message = Some("Table created".into());
203 assert_eq!(r.result_summary(), "Table created");
204
205 let err = QueryResult::error("boom".into());
207 assert_eq!(err.result_summary(), "Error: boom");
208
209 assert_eq!(QueryResult::new(Vec::new()).result_summary(), "(empty result)");
211
212 let mut rows = QueryResult::new(Vec::new());
214 rows.num_rows = 3;
215 rows.num_columns = 2;
216 assert_eq!(rows.result_summary(), "Returned 3 rows in 2 columns");
217 }
218
219 fn remote(
220 success: bool,
221 message: Option<&str>,
222 error: Option<&str>,
223 columns: usize,
224 row_count: usize,
225 ) -> crate::remote::WireResponse {
226 crate::remote::WireResponse {
227 success,
228 message: message.map(str::to_string),
229 error_message: error.map(str::to_string),
230 column_names: (0..columns).map(|i| format!("c{i}")).collect(),
231 rows: vec![Vec::new(); row_count],
232 stats: None,
233 }
234 }
235
236 #[test]
239 fn test_local_remote_summary_parity() {
240 assert_eq!(
242 QueryResult::success_message("Table created".into()).result_summary(),
243 remote(true, Some("Table created"), None, 0, 0).result_summary()
244 );
245 assert_eq!(
247 QueryResult::error("boom".into()).result_summary(),
248 remote(false, None, Some("boom"), 0, 0).result_summary()
249 );
250 assert_eq!(
252 QueryResult::new(Vec::new()).result_summary(),
253 remote(true, None, None, 0, 0).result_summary()
254 );
255 let mut local = QueryResult::new(Vec::new());
257 local.num_rows = 2;
258 local.num_columns = 3;
259 assert_eq!(local.result_summary(), remote(true, None, None, 3, 2).result_summary());
260 }
261}