1use crate::error::{QueryError, Result};
4use crate::executor::filter::{Value, evaluate_expr_for_row};
5use crate::executor::scan::{ColumnData, DataType, Field, RecordBatch, Schema};
6use crate::parser::ast::Expr;
7use std::collections::HashMap;
8use std::sync::Arc;
9
10pub struct Aggregate {
12 pub group_by: Vec<Expr>,
14 pub aggregates: Vec<AggregateFunction>,
16}
17
18impl Aggregate {
19 pub fn new(group_by: Vec<Expr>, aggregates: Vec<AggregateFunction>) -> Self {
21 Self {
22 group_by,
23 aggregates,
24 }
25 }
26
27 pub fn execute(&self, batch: &RecordBatch) -> Result<RecordBatch> {
29 if self.group_by.is_empty() {
30 self.execute_global_aggregate(batch)
32 } else {
33 self.execute_grouped_aggregate(batch)
35 }
36 }
37
38 fn execute_global_aggregate(&self, batch: &RecordBatch) -> Result<RecordBatch> {
40 let mut result_fields = Vec::new();
41 let mut result_columns = Vec::new();
42
43 for agg in &self.aggregates {
44 let value = if agg.column == "*" {
45 if matches!(agg.func, AggregateFunc::Count) {
47 Some(batch.num_rows as f64)
48 } else {
49 return Err(QueryError::semantic(
50 "Wildcard (*) can only be used with COUNT function",
51 ));
52 }
53 } else {
54 let column = batch
55 .column_by_name(&agg.column)
56 .ok_or_else(|| QueryError::ColumnNotFound(agg.column.clone()))?;
57 self.compute_aggregate(agg.func, column)?
58 };
59
60 result_fields.push(Field::new(
61 agg.alias.clone().unwrap_or_else(|| {
62 if agg.column == "*" {
63 "count".to_string()
64 } else {
65 agg.column.clone()
66 }
67 }),
68 crate::executor::scan::DataType::Float64,
69 true,
70 ));
71 result_columns.push(ColumnData::Float64(vec![value]));
72 }
73
74 let schema = Arc::new(Schema::new(result_fields));
75 RecordBatch::new(schema, result_columns, 1)
76 }
77
78 fn execute_grouped_aggregate(&self, batch: &RecordBatch) -> Result<RecordBatch> {
88 let num_rows = batch.num_rows;
89
90 let mut group_values: Vec<Vec<Value>> = Vec::with_capacity(self.group_by.len());
92 for expr in &self.group_by {
93 let mut vals = Vec::with_capacity(num_rows);
94 for row in 0..num_rows {
95 let value = evaluate_expr_for_row(expr, batch, row)?;
96 if matches!(value, Value::Geometry(_)) {
97 return Err(QueryError::unsupported(
98 "GROUP BY on a geometry value is not supported",
99 ));
100 }
101 vals.push(value);
102 }
103 group_values.push(vals);
104 }
105
106 let mut key_to_group: HashMap<Vec<String>, usize> = HashMap::new();
108 let mut group_rows: Vec<Vec<usize>> = Vec::new();
109 let mut group_repr: Vec<Vec<Value>> = Vec::new();
110
111 for row in 0..num_rows {
112 let key: Vec<String> = group_values
113 .iter()
114 .map(|gv| group_key_component(&gv[row]))
115 .collect();
116 let gid = match key_to_group.get(&key) {
117 Some(&g) => g,
118 None => {
119 let g = group_rows.len();
120 key_to_group.insert(key, g);
121 group_rows.push(Vec::new());
122 group_repr.push(group_values.iter().map(|gv| gv[row].clone()).collect());
123 g
124 }
125 };
126 group_rows[gid].push(row);
127 }
128
129 let num_groups = group_rows.len();
130
131 let mut result_fields = Vec::new();
132 let mut result_columns = Vec::new();
133
134 for (gi, expr) in self.group_by.iter().enumerate() {
136 let repr_vals: Vec<Value> =
137 (0..num_groups).map(|g| group_repr[g][gi].clone()).collect();
138 let column = values_to_column(&repr_vals);
139 let data_type = column_data_type(&column);
140 let name = match expr {
141 Expr::Column { name, .. } => name.clone(),
142 _ => format!("group_{}", gi),
143 };
144 result_fields.push(Field::new(name, data_type, true));
145 result_columns.push(column);
146 }
147
148 for agg in &self.aggregates {
150 let mut agg_vals: Vec<Option<f64>> = Vec::with_capacity(num_groups);
151 for rows in group_rows.iter() {
152 let value = if agg.column == "*" {
153 if matches!(agg.func, AggregateFunc::Count) {
154 Some(rows.len() as f64)
155 } else {
156 return Err(QueryError::semantic(
157 "Wildcard (*) can only be used with COUNT function",
158 ));
159 }
160 } else {
161 let column = batch
162 .column_by_name(&agg.column)
163 .ok_or_else(|| QueryError::ColumnNotFound(agg.column.clone()))?;
164 let sub = gather_column(column, rows);
165 self.compute_aggregate(agg.func, &sub)?
166 };
167 agg_vals.push(value);
168 }
169 result_fields.push(Field::new(
170 agg.alias.clone().unwrap_or_else(|| {
171 if agg.column == "*" {
172 "count".to_string()
173 } else {
174 agg.column.clone()
175 }
176 }),
177 DataType::Float64,
178 true,
179 ));
180 result_columns.push(ColumnData::Float64(agg_vals));
181 }
182
183 let schema = Arc::new(Schema::new(result_fields));
184 RecordBatch::new(schema, result_columns, num_groups)
185 }
186
187 fn compute_aggregate(&self, func: AggregateFunc, column: &ColumnData) -> Result<Option<f64>> {
189 match func {
190 AggregateFunc::Count => Ok(Some(self.count(column))),
191 AggregateFunc::Sum => self.sum(column),
192 AggregateFunc::Avg => self.avg(column),
193 AggregateFunc::Min => self.min(column),
194 AggregateFunc::Max => self.max(column),
195 }
196 }
197
198 fn count(&self, column: &ColumnData) -> f64 {
200 let non_null_count = match column {
201 ColumnData::Boolean(data) => data.iter().filter(|v| v.is_some()).count(),
202 ColumnData::Int32(data) => data.iter().filter(|v| v.is_some()).count(),
203 ColumnData::Int64(data) => data.iter().filter(|v| v.is_some()).count(),
204 ColumnData::Float32(data) => data.iter().filter(|v| v.is_some()).count(),
205 ColumnData::Float64(data) => data.iter().filter(|v| v.is_some()).count(),
206 ColumnData::String(data) => data.iter().filter(|v| v.is_some()).count(),
207 ColumnData::Binary(data) => data.iter().filter(|v| v.is_some()).count(),
208 };
209 non_null_count as f64
210 }
211
212 fn sum(&self, column: &ColumnData) -> Result<Option<f64>> {
214 match column {
215 ColumnData::Int32(data) => {
216 let sum: i64 = data.iter().filter_map(|v| v.map(|i| i as i64)).sum();
217 Ok(Some(sum as f64))
218 }
219 ColumnData::Int64(data) => {
220 let sum: i64 = data.iter().filter_map(|v| *v).sum();
221 Ok(Some(sum as f64))
222 }
223 ColumnData::Float32(data) => {
224 let sum: f32 = data.iter().filter_map(|v| *v).sum();
225 Ok(Some(sum as f64))
226 }
227 ColumnData::Float64(data) => {
228 let sum: f64 = data.iter().filter_map(|v| *v).sum();
229 Ok(Some(sum))
230 }
231 _ => Err(QueryError::type_mismatch("numeric", "non-numeric")),
232 }
233 }
234
235 fn avg(&self, column: &ColumnData) -> Result<Option<f64>> {
237 let sum = self.sum(column)?;
238 let count = self.count(column);
239 if count > 0.0 {
240 Ok(sum.map(|s| s / count))
241 } else {
242 Ok(None)
243 }
244 }
245
246 fn min(&self, column: &ColumnData) -> Result<Option<f64>> {
248 match column {
249 ColumnData::Int32(data) => {
250 let min = data.iter().filter_map(|v| *v).min();
251 Ok(min.map(|m| m as f64))
252 }
253 ColumnData::Int64(data) => {
254 let min = data.iter().filter_map(|v| *v).min();
255 Ok(min.map(|m| m as f64))
256 }
257 ColumnData::Float32(data) => {
258 let min = data
259 .iter()
260 .filter_map(|v| *v)
261 .min_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
262 Ok(min.map(|m| m as f64))
263 }
264 ColumnData::Float64(data) => {
265 let min = data
266 .iter()
267 .filter_map(|v| *v)
268 .min_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
269 Ok(min)
270 }
271 _ => Err(QueryError::type_mismatch("numeric", "non-numeric")),
272 }
273 }
274
275 fn max(&self, column: &ColumnData) -> Result<Option<f64>> {
277 match column {
278 ColumnData::Int32(data) => {
279 let max = data.iter().filter_map(|v| *v).max();
280 Ok(max.map(|m| m as f64))
281 }
282 ColumnData::Int64(data) => {
283 let max = data.iter().filter_map(|v| *v).max();
284 Ok(max.map(|m| m as f64))
285 }
286 ColumnData::Float32(data) => {
287 let max = data
288 .iter()
289 .filter_map(|v| *v)
290 .max_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
291 Ok(max.map(|m| m as f64))
292 }
293 ColumnData::Float64(data) => {
294 let max = data
295 .iter()
296 .filter_map(|v| *v)
297 .max_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
298 Ok(max)
299 }
300 _ => Err(QueryError::type_mismatch("numeric", "non-numeric")),
301 }
302 }
303}
304
305fn group_key_component(value: &Value) -> String {
310 match value {
311 Value::Null => "N".to_string(),
312 Value::Boolean(b) => format!("b{}", b),
313 Value::Int32(i) => format!("i{}", i),
314 Value::Int64(i) => format!("l{}", i),
315 Value::Float32(f) => format!("f{}", f.to_bits()),
316 Value::Float64(f) => format!("d{}", f.to_bits()),
317 Value::String(s) => format!("s{}", s),
318 Value::Geometry(_) => "g".to_string(),
320 }
321}
322
323fn values_to_column(values: &[Value]) -> ColumnData {
328 let first_non_null = values.iter().find(|v| !matches!(v, Value::Null));
329 match first_non_null {
330 Some(Value::Boolean(_)) => ColumnData::Boolean(
331 values
332 .iter()
333 .map(|v| match v {
334 Value::Boolean(b) => Some(*b),
335 _ => None,
336 })
337 .collect(),
338 ),
339 Some(Value::Int32(_)) => ColumnData::Int32(
340 values
341 .iter()
342 .map(|v| match v {
343 Value::Int32(i) => Some(*i),
344 _ => None,
345 })
346 .collect(),
347 ),
348 Some(Value::Int64(_)) => ColumnData::Int64(
349 values
350 .iter()
351 .map(|v| match v {
352 Value::Int64(i) => Some(*i),
353 _ => None,
354 })
355 .collect(),
356 ),
357 Some(Value::Float32(_)) => ColumnData::Float32(
358 values
359 .iter()
360 .map(|v| match v {
361 Value::Float32(f) => Some(*f),
362 _ => None,
363 })
364 .collect(),
365 ),
366 Some(Value::Float64(_)) => ColumnData::Float64(
367 values
368 .iter()
369 .map(|v| match v {
370 Value::Float64(f) => Some(*f),
371 _ => None,
372 })
373 .collect(),
374 ),
375 Some(Value::String(_)) => ColumnData::String(
376 values
377 .iter()
378 .map(|v| match v {
379 Value::String(s) => Some(s.clone()),
380 _ => None,
381 })
382 .collect(),
383 ),
384 _ => ColumnData::Int64(values.iter().map(|_| None).collect()),
386 }
387}
388
389fn column_data_type(column: &ColumnData) -> DataType {
391 match column {
392 ColumnData::Boolean(_) => DataType::Boolean,
393 ColumnData::Int32(_) => DataType::Int32,
394 ColumnData::Int64(_) => DataType::Int64,
395 ColumnData::Float32(_) => DataType::Float32,
396 ColumnData::Float64(_) => DataType::Float64,
397 ColumnData::String(_) => DataType::String,
398 ColumnData::Binary(_) => DataType::Binary,
399 }
400}
401
402fn gather_column(column: &ColumnData, indices: &[usize]) -> ColumnData {
404 match column {
405 ColumnData::Boolean(d) => {
406 ColumnData::Boolean(indices.iter().filter_map(|&i| d.get(i).copied()).collect())
407 }
408 ColumnData::Int32(d) => {
409 ColumnData::Int32(indices.iter().filter_map(|&i| d.get(i).copied()).collect())
410 }
411 ColumnData::Int64(d) => {
412 ColumnData::Int64(indices.iter().filter_map(|&i| d.get(i).copied()).collect())
413 }
414 ColumnData::Float32(d) => {
415 ColumnData::Float32(indices.iter().filter_map(|&i| d.get(i).copied()).collect())
416 }
417 ColumnData::Float64(d) => {
418 ColumnData::Float64(indices.iter().filter_map(|&i| d.get(i).copied()).collect())
419 }
420 ColumnData::String(d) => {
421 ColumnData::String(indices.iter().filter_map(|&i| d.get(i).cloned()).collect())
422 }
423 ColumnData::Binary(d) => {
424 ColumnData::Binary(indices.iter().filter_map(|&i| d.get(i).cloned()).collect())
425 }
426 }
427}
428
429#[derive(Debug, Clone)]
431pub struct AggregateFunction {
432 pub func: AggregateFunc,
434 pub column: String,
436 pub alias: Option<String>,
438}
439
440#[derive(Debug, Clone, Copy, PartialEq, Eq)]
442pub enum AggregateFunc {
443 Count,
445 Sum,
447 Avg,
449 Min,
451 Max,
453}
454
455#[cfg(test)]
456#[allow(clippy::panic)]
457mod tests {
458 use super::*;
459 use crate::executor::scan::DataType;
460
461 #[test]
462 fn test_global_aggregate() -> Result<()> {
463 let schema = Arc::new(Schema::new(vec![Field::new(
464 "value".to_string(),
465 DataType::Int64,
466 false,
467 )]));
468
469 let columns = vec![ColumnData::Int64(vec![
470 Some(10),
471 Some(20),
472 Some(30),
473 Some(40),
474 Some(50),
475 ])];
476
477 let batch = RecordBatch::new(schema, columns, 5)?;
478
479 let agg = Aggregate::new(
480 vec![],
481 vec![
482 AggregateFunction {
483 func: AggregateFunc::Sum,
484 column: "value".to_string(),
485 alias: Some("sum".to_string()),
486 },
487 AggregateFunction {
488 func: AggregateFunc::Avg,
489 column: "value".to_string(),
490 alias: Some("avg".to_string()),
491 },
492 ],
493 );
494
495 let result = agg.execute(&batch)?;
496 assert_eq!(result.num_rows, 1);
497 assert_eq!(result.columns.len(), 2);
498
499 Ok(())
500 }
501
502 #[test]
503 fn test_grouped_aggregate() -> Result<()> {
504 let schema = Arc::new(Schema::new(vec![
505 Field::new("k".to_string(), DataType::Int64, false),
506 Field::new("v".to_string(), DataType::Int64, false),
507 ]));
508 let columns = vec![
510 ColumnData::Int64(vec![Some(1), Some(2), Some(1)]),
511 ColumnData::Int64(vec![Some(10), Some(20), Some(30)]),
512 ];
513 let batch = RecordBatch::new(schema, columns, 3)?;
514
515 let agg = Aggregate::new(
516 vec![Expr::Column {
517 table: None,
518 name: "k".to_string(),
519 }],
520 vec![
521 AggregateFunction {
522 func: AggregateFunc::Sum,
523 column: "v".to_string(),
524 alias: Some("sum_v".to_string()),
525 },
526 AggregateFunction {
527 func: AggregateFunc::Count,
528 column: "*".to_string(),
529 alias: Some("cnt".to_string()),
530 },
531 ],
532 );
533
534 let result = agg.execute(&batch)?;
535 assert_eq!(result.num_rows, 2); assert_eq!(result.columns.len(), 3); let ColumnData::Int64(k) = &result.columns[0] else {
540 panic!("expected int64 group key");
541 };
542 assert_eq!(k[0], Some(1));
543 assert_eq!(k[1], Some(2));
544
545 let ColumnData::Float64(sum_v) = &result.columns[1] else {
546 panic!("expected float64 sum");
547 };
548 assert_eq!(sum_v[0], Some(40.0));
549 assert_eq!(sum_v[1], Some(20.0));
550
551 let ColumnData::Float64(cnt) = &result.columns[2] else {
552 panic!("expected float64 count");
553 };
554 assert_eq!(cnt[0], Some(2.0));
555 assert_eq!(cnt[1], Some(1.0));
556
557 Ok(())
558 }
559
560 #[test]
561 fn test_grouped_aggregate_null_key_groups_together() -> Result<()> {
562 let schema = Arc::new(Schema::new(vec![
563 Field::new("k".to_string(), DataType::Int64, true),
564 Field::new("v".to_string(), DataType::Int64, false),
565 ]));
566 let columns = vec![
567 ColumnData::Int64(vec![None, Some(1), None]),
568 ColumnData::Int64(vec![Some(5), Some(7), Some(9)]),
569 ];
570 let batch = RecordBatch::new(schema, columns, 3)?;
571 let agg = Aggregate::new(
572 vec![Expr::Column {
573 table: None,
574 name: "k".to_string(),
575 }],
576 vec![AggregateFunction {
577 func: AggregateFunc::Count,
578 column: "*".to_string(),
579 alias: Some("cnt".to_string()),
580 }],
581 );
582 let result = agg.execute(&batch)?;
583 assert_eq!(result.num_rows, 2);
585 let ColumnData::Float64(cnt) = &result.columns[1] else {
586 panic!("expected float64 count");
587 };
588 assert_eq!(cnt[0], Some(2.0)); assert_eq!(cnt[1], Some(1.0));
590 Ok(())
591 }
592}