1use kyu_types::{LogicalType, TypedValue};
8use smol_str::SmolStr;
9use std::fmt;
10
11use crate::value_vector::{FlatVector, ValueVector};
12
13#[derive(Clone, Debug)]
19pub struct QueryResult {
20 pub column_names: Vec<SmolStr>,
21 pub column_types: Vec<LogicalType>,
22 data: Vec<TypedValue>,
23 num_rows_count: usize,
24}
25
26impl QueryResult {
27 pub fn new(column_names: Vec<SmolStr>, column_types: Vec<LogicalType>) -> Self {
28 Self {
29 column_names,
30 column_types,
31 data: Vec::new(),
32 num_rows_count: 0,
33 }
34 }
35
36 pub fn num_columns(&self) -> usize {
37 self.column_names.len()
38 }
39
40 pub fn num_rows(&self) -> usize {
41 self.num_rows_count
42 }
43
44 pub fn row(&self, idx: usize) -> &[TypedValue] {
46 let nc = self.column_names.len();
47 let start = idx * nc;
48 &self.data[start..start + nc]
49 }
50
51 pub fn iter_rows(&self) -> impl Iterator<Item = &[TypedValue]> {
53 let nc = self.column_names.len();
54 (0..self.num_rows_count).map(move |i| {
55 let start = i * nc;
56 &self.data[start..start + nc]
57 })
58 }
59
60 pub fn push_row(&mut self, row: Vec<TypedValue>) {
61 debug_assert_eq!(row.len(), self.column_names.len());
62 self.data.extend(row);
63 self.num_rows_count += 1;
64 }
65
66 pub fn push_chunk(&mut self, chunk: &crate::data_chunk::DataChunk) {
72 let n = chunk.num_rows();
73 if n == 0 {
74 return;
75 }
76 let num_cols = self.column_names.len();
77
78 self.data.reserve(n * num_cols);
80
81 if chunk.selection().is_identity() {
82 push_chunk_identity(&mut self.data, chunk, n, num_cols);
83 } else {
84 for row_idx in 0..n {
85 for col_idx in 0..num_cols {
86 self.data.push(chunk.get_value(row_idx, col_idx));
87 }
88 }
89 }
90 self.num_rows_count += n;
91 }
92}
93
94fn push_chunk_identity(
97 data: &mut Vec<TypedValue>,
98 chunk: &crate::data_chunk::DataChunk,
99 n: usize,
100 num_cols: usize,
101) {
102 let base = data.len();
104 data.resize(base + n * num_cols, TypedValue::Null);
105 let dest = &mut data[base..];
106
107 for col_idx in 0..num_cols {
108 let col = chunk.column(col_idx);
109 match col {
110 ValueVector::Flat(flat) => {
111 push_flat_strided(dest, col_idx, num_cols, flat, n);
112 }
113 ValueVector::String(sv) => {
114 let sdata = sv.data();
115 for i in 0..n {
116 dest[i * num_cols + col_idx] = match &sdata[i] {
117 Some(s) => TypedValue::String(s.clone()),
118 None => TypedValue::Null,
119 };
120 }
121 }
122 ValueVector::Bool(bv) => {
123 for i in 0..n {
124 dest[i * num_cols + col_idx] = bv.get_value(i);
125 }
126 }
127 ValueVector::Owned(v) => {
128 for i in 0..n {
129 dest[i * num_cols + col_idx] = v[i].clone();
130 }
131 }
132 }
133 }
134}
135
136fn push_flat_strided(
138 dest: &mut [TypedValue],
139 col_idx: usize,
140 stride: usize,
141 flat: &FlatVector,
142 n: usize,
143) {
144 let nm = flat.null_mask();
145 match flat.logical_type() {
146 LogicalType::Int64 | LogicalType::Serial => {
147 let slice = flat.data_as_i64_slice();
148 for i in 0..n {
149 if !nm.is_null(i as u64) {
150 dest[i * stride + col_idx] = TypedValue::Int64(slice[i]);
151 }
152 }
153 }
154 LogicalType::Int32 => {
155 let slice = flat.data_as_i32_slice();
156 for i in 0..n {
157 if !nm.is_null(i as u64) {
158 dest[i * stride + col_idx] = TypedValue::Int32(slice[i]);
159 }
160 }
161 }
162 LogicalType::Double => {
163 let slice = flat.data_as_f64_slice();
164 for i in 0..n {
165 if !nm.is_null(i as u64) {
166 dest[i * stride + col_idx] = TypedValue::Double(slice[i]);
167 }
168 }
169 }
170 LogicalType::Float => {
171 let slice = flat.data_as_f32_slice();
172 for i in 0..n {
173 if !nm.is_null(i as u64) {
174 dest[i * stride + col_idx] = TypedValue::Float(slice[i]);
175 }
176 }
177 }
178 _ => {
179 for i in 0..n {
180 dest[i * stride + col_idx] = flat.get_value(i);
181 }
182 }
183 }
184}
185
186impl fmt::Display for QueryResult {
187 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
188 let headers: Vec<&str> = self.column_names.iter().map(|n| n.as_str()).collect();
190 writeln!(f, "| {} |", headers.join(" | "))?;
191 writeln!(
192 f,
193 "|{}|",
194 headers
195 .iter()
196 .map(|h| "-".repeat(h.len() + 2))
197 .collect::<Vec<_>>()
198 .join("|")
199 )?;
200 for row in self.iter_rows() {
202 let cells: Vec<String> = row
203 .iter()
204 .zip(&self.column_names)
205 .map(|(val, name)| format!("{:>width$}", format_value(val), width = name.len()))
206 .collect();
207 writeln!(f, "| {} |", cells.join(" | "))?;
208 }
209 writeln!(
210 f,
211 "({} row{})",
212 self.num_rows_count,
213 if self.num_rows_count == 1 { "" } else { "s" }
214 )
215 }
216}
217
218fn format_value(val: &TypedValue) -> String {
219 match val {
220 TypedValue::Null => "NULL".to_string(),
221 TypedValue::Bool(b) => b.to_string(),
222 TypedValue::Int8(v) => v.to_string(),
223 TypedValue::Int16(v) => v.to_string(),
224 TypedValue::Int32(v) => v.to_string(),
225 TypedValue::Int64(v) => v.to_string(),
226 TypedValue::Float(v) => format!("{v:.1}"),
227 TypedValue::Double(v) => format!("{v:.1}"),
228 TypedValue::String(s) => s.to_string(),
229 _ => format!("{val:?}"),
230 }
231}
232
233#[cfg(test)]
234mod tests {
235 use super::*;
236
237 #[test]
238 fn empty_result() {
239 let result = QueryResult::new(vec![SmolStr::new("x")], vec![LogicalType::Int64]);
240 assert_eq!(result.num_rows(), 0);
241 assert_eq!(result.num_columns(), 1);
242 }
243
244 #[test]
245 fn push_rows() {
246 let mut result = QueryResult::new(
247 vec![SmolStr::new("a"), SmolStr::new("b")],
248 vec![LogicalType::Int64, LogicalType::String],
249 );
250 result.push_row(vec![
251 TypedValue::Int64(1),
252 TypedValue::String(SmolStr::new("hello")),
253 ]);
254 result.push_row(vec![
255 TypedValue::Int64(2),
256 TypedValue::String(SmolStr::new("world")),
257 ]);
258 assert_eq!(result.num_rows(), 2);
259 }
260
261 #[test]
262 fn row_access() {
263 let mut result = QueryResult::new(
264 vec![SmolStr::new("a"), SmolStr::new("b")],
265 vec![LogicalType::Int64, LogicalType::String],
266 );
267 result.push_row(vec![
268 TypedValue::Int64(1),
269 TypedValue::String(SmolStr::new("hello")),
270 ]);
271 assert_eq!(result.row(0)[0], TypedValue::Int64(1));
272 assert_eq!(result.row(0)[1], TypedValue::String(SmolStr::new("hello")));
273 }
274
275 #[test]
276 fn iter_rows_works() {
277 let mut result = QueryResult::new(vec![SmolStr::new("x")], vec![LogicalType::Int64]);
278 result.push_row(vec![TypedValue::Int64(1)]);
279 result.push_row(vec![TypedValue::Int64(2)]);
280 let rows: Vec<_> = result.iter_rows().collect();
281 assert_eq!(rows.len(), 2);
282 assert_eq!(rows[0][0], TypedValue::Int64(1));
283 assert_eq!(rows[1][0], TypedValue::Int64(2));
284 }
285
286 #[test]
287 fn display_format() {
288 let mut result = QueryResult::new(vec![SmolStr::new("x")], vec![LogicalType::Int64]);
289 result.push_row(vec![TypedValue::Int64(42)]);
290 let output = format!("{result}");
291 assert!(output.contains("42"));
292 assert!(output.contains("1 row"));
293 }
294
295 #[test]
296 fn display_plural() {
297 let mut result = QueryResult::new(vec![SmolStr::new("x")], vec![LogicalType::Int64]);
298 result.push_row(vec![TypedValue::Int64(1)]);
299 result.push_row(vec![TypedValue::Int64(2)]);
300 let output = format!("{result}");
301 assert!(output.contains("2 rows"));
302 }
303}