1use std::cmp::Ordering;
2use std::collections::{HashMap, HashSet};
3use std::sync::Arc;
4use std::sync::atomic::{AtomicU64, AtomicUsize};
5
6use crate::catalog::ColumnMetadata;
7use crate::executor::evaluator::EvalContext;
8use crate::executor::memory::{MemoryPolicy, MemoryTracker, map_core_memory_error};
9use crate::executor::{ExecutorError, Result};
10use crate::planner::aggregate_expr::{AggregateExpr, AggregateFunction};
11use crate::planner::typed_expr::TypedExpr;
12use crate::planner::types::ResolvedType;
13use crate::storage::SqlValue;
14use alopex_core::sql::stream::ByteSized;
15
16use super::{Row, RowIterator, iterator::VecIterator};
17
18pub type GroupKeyBytes = Vec<u8>;
20
21fn encode_group_value(value: &SqlValue, buf: &mut Vec<u8>) -> Result<()> {
22 buf.push(value.type_tag());
23 match value {
24 SqlValue::Null => Ok(()),
25 SqlValue::Integer(v) => {
26 buf.extend_from_slice(&v.to_le_bytes());
27 Ok(())
28 }
29 SqlValue::BigInt(v) => {
30 buf.extend_from_slice(&v.to_le_bytes());
31 Ok(())
32 }
33 SqlValue::Float(v) => {
34 buf.extend_from_slice(&v.to_bits().to_le_bytes());
35 Ok(())
36 }
37 SqlValue::Double(v) => {
38 buf.extend_from_slice(&v.to_bits().to_le_bytes());
39 Ok(())
40 }
41 SqlValue::Text(s) => {
42 let len = u32::try_from(s.len()).map_err(|_| ExecutorError::InvalidOperation {
43 operation: "aggregate".into(),
44 reason: "text length exceeds u32::MAX".into(),
45 })?;
46 buf.extend_from_slice(&len.to_le_bytes());
47 buf.extend_from_slice(s.as_bytes());
48 Ok(())
49 }
50 SqlValue::Blob(bytes) => {
51 let len = u32::try_from(bytes.len()).map_err(|_| ExecutorError::InvalidOperation {
52 operation: "aggregate".into(),
53 reason: "blob length exceeds u32::MAX".into(),
54 })?;
55 buf.extend_from_slice(&len.to_le_bytes());
56 buf.extend_from_slice(bytes);
57 Ok(())
58 }
59 SqlValue::Boolean(b) => {
60 buf.push(u8::from(*b));
61 Ok(())
62 }
63 SqlValue::Timestamp(v) => {
64 buf.extend_from_slice(&v.to_le_bytes());
65 Ok(())
66 }
67 SqlValue::Vector(values) => {
68 let len = u32::try_from(values.len()).map_err(|_| ExecutorError::InvalidOperation {
69 operation: "aggregate".into(),
70 reason: "vector length exceeds u32::MAX".into(),
71 })?;
72 buf.extend_from_slice(&len.to_le_bytes());
73 for f in values {
74 buf.extend_from_slice(&f.to_bits().to_le_bytes());
75 }
76 Ok(())
77 }
78 }
79}
80
81pub fn encode_group_key(values: &[SqlValue]) -> Result<GroupKeyBytes> {
83 let mut buf = Vec::new();
84 for value in values {
85 encode_group_value(value, &mut buf)?;
86 }
87 Ok(buf)
88}
89
90pub trait Accumulator: Send {
92 fn update(&mut self, value: Option<SqlValue>) -> Result<()>;
94 fn state(&self) -> Result<Vec<SqlValue>>;
96 fn merge(&mut self, state: &[SqlValue]) -> Result<()>;
98 fn finalize(&self) -> Result<SqlValue>;
100 fn clone_box(&self) -> Box<dyn Accumulator>;
102}
103
104impl Clone for Box<dyn Accumulator> {
105 fn clone(&self) -> Self {
106 self.clone_box()
107 }
108}
109
110fn invalid_aggregate_state(function: &str, reason: impl Into<String>) -> ExecutorError {
111 ExecutorError::InvalidOperation {
112 operation: function.into(),
113 reason: reason.into(),
114 }
115}
116
117fn expect_state_arity(function: &str, state: &[SqlValue], expected: usize) -> Result<()> {
118 if state.len() == expected {
119 Ok(())
120 } else {
121 Err(invalid_aggregate_state(
122 function,
123 format!("expected {expected} state value(s), got {}", state.len()),
124 ))
125 }
126}
127
128fn state_bigint(function: &str, value: &SqlValue, index: usize) -> Result<i64> {
129 match value {
130 SqlValue::BigInt(v) => Ok(*v),
131 other => Err(invalid_aggregate_state(
132 function,
133 format!(
134 "state value {index} expected BigInt, got {}",
135 other.type_name()
136 ),
137 )),
138 }
139}
140
141fn state_double(function: &str, value: &SqlValue, index: usize) -> Result<f64> {
142 match value {
143 SqlValue::Double(v) => Ok(*v),
144 other => Err(invalid_aggregate_state(
145 function,
146 format!(
147 "state value {index} expected Double, got {}",
148 other.type_name()
149 ),
150 )),
151 }
152}
153
154fn state_text<'a>(function: &str, value: &'a SqlValue, index: usize) -> Result<&'a str> {
155 match value {
156 SqlValue::Text(v) => Ok(v),
157 other => Err(invalid_aggregate_state(
158 function,
159 format!(
160 "state value {index} expected Text, got {}",
161 other.type_name()
162 ),
163 )),
164 }
165}
166
167fn distinct_allows(
168 distinct_values: &mut Option<HashSet<Vec<u8>>>,
169 value: &SqlValue,
170) -> Result<bool> {
171 if value.is_null() {
172 return Ok(false);
173 }
174 let Some(distinct) = distinct_values else {
175 return Ok(true);
176 };
177 let encoded = encode_group_key(std::slice::from_ref(value))?;
178 Ok(distinct.insert(encoded))
179}
180
181#[derive(Debug, Clone)]
183pub struct CountAccumulator {
184 count: usize,
185 distinct_values: Option<HashSet<Vec<u8>>>,
186}
187
188impl CountAccumulator {
189 pub fn new(distinct: bool) -> Self {
191 Self {
192 count: 0,
193 distinct_values: if distinct { Some(HashSet::new()) } else { None },
194 }
195 }
196}
197
198impl Accumulator for CountAccumulator {
199 fn update(&mut self, value: Option<SqlValue>) -> Result<()> {
200 match (&mut self.distinct_values, value) {
201 (Some(distinct), Some(value)) => {
202 if value.is_null() {
203 return Ok(());
204 }
205 let encoded = encode_group_key(std::slice::from_ref(&value))?;
206 if distinct.insert(encoded) {
207 self.count += 1;
208 }
209 }
210 (Some(_), None) => {
211 self.count += 1;
212 }
213 (None, Some(value)) => {
214 if !value.is_null() {
215 self.count += 1;
216 }
217 }
218 (None, None) => {
219 self.count += 1;
220 }
221 }
222 Ok(())
223 }
224
225 fn finalize(&self) -> Result<SqlValue> {
226 Ok(SqlValue::BigInt(self.count as i64))
227 }
228
229 fn state(&self) -> Result<Vec<SqlValue>> {
230 Ok(vec![SqlValue::BigInt(self.count as i64)])
231 }
232
233 fn merge(&mut self, state: &[SqlValue]) -> Result<()> {
234 expect_state_arity("count", state, 1)?;
235 let count = state_bigint("count", &state[0], 0)?;
236 if count < 0 {
237 return Err(invalid_aggregate_state(
238 "count",
239 "state count must be non-negative",
240 ));
241 }
242 self.count = self.count.saturating_add(count as usize);
243 Ok(())
244 }
245
246 fn clone_box(&self) -> Box<dyn Accumulator> {
247 Box::new(self.clone())
248 }
249}
250
251#[derive(Debug, Clone)]
253pub struct SumAccumulator {
254 sum: Option<f64>,
255 distinct_values: Option<HashSet<Vec<u8>>>,
256}
257
258impl SumAccumulator {
259 pub fn new() -> Self {
261 Self::with_distinct(false)
262 }
263
264 pub fn with_distinct(distinct: bool) -> Self {
265 Self {
266 sum: None,
267 distinct_values: if distinct { Some(HashSet::new()) } else { None },
268 }
269 }
270}
271
272impl Default for SumAccumulator {
273 fn default() -> Self {
274 Self::new()
275 }
276}
277
278impl Accumulator for SumAccumulator {
279 fn update(&mut self, value: Option<SqlValue>) -> Result<()> {
280 let Some(value) = value else {
281 return Ok(());
282 };
283 if value.is_null() {
284 return Ok(());
285 }
286 if !distinct_allows(&mut self.distinct_values, &value)? {
287 return Ok(());
288 }
289 let numeric = numeric_to_f64(&value)?;
290 self.sum = Some(self.sum.unwrap_or(0.0) + numeric);
291 Ok(())
292 }
293
294 fn finalize(&self) -> Result<SqlValue> {
295 Ok(self.sum.map_or(SqlValue::Null, SqlValue::Double))
296 }
297
298 fn state(&self) -> Result<Vec<SqlValue>> {
299 Ok(vec![self.sum.map_or(SqlValue::Null, SqlValue::Double)])
300 }
301
302 fn merge(&mut self, state: &[SqlValue]) -> Result<()> {
303 expect_state_arity("sum", state, 1)?;
304 if state[0].is_null() {
305 return Ok(());
306 }
307 let numeric = numeric_to_f64(&state[0])?;
308 self.sum = Some(self.sum.unwrap_or(0.0) + numeric);
309 Ok(())
310 }
311
312 fn clone_box(&self) -> Box<dyn Accumulator> {
313 Box::new(self.clone())
314 }
315}
316
317#[derive(Debug, Clone)]
319pub struct TotalAccumulator {
320 sum: Option<f64>,
321}
322
323impl TotalAccumulator {
324 pub fn new() -> Self {
326 Self { sum: None }
327 }
328}
329
330impl Default for TotalAccumulator {
331 fn default() -> Self {
332 Self::new()
333 }
334}
335
336impl Accumulator for TotalAccumulator {
337 fn update(&mut self, value: Option<SqlValue>) -> Result<()> {
338 let Some(value) = value else {
339 return Ok(());
340 };
341 if value.is_null() {
342 return Ok(());
343 }
344 let numeric = numeric_to_f64(&value)?;
345 self.sum = Some(self.sum.unwrap_or(0.0) + numeric);
346 Ok(())
347 }
348
349 fn finalize(&self) -> Result<SqlValue> {
350 Ok(SqlValue::Double(self.sum.unwrap_or(0.0)))
351 }
352
353 fn state(&self) -> Result<Vec<SqlValue>> {
354 Ok(vec![SqlValue::Double(self.sum.unwrap_or(0.0))])
355 }
356
357 fn merge(&mut self, state: &[SqlValue]) -> Result<()> {
358 expect_state_arity("total", state, 1)?;
359 let value = state_double("total", &state[0], 0)?;
360 self.sum = Some(self.sum.unwrap_or(0.0) + value);
361 Ok(())
362 }
363
364 fn clone_box(&self) -> Box<dyn Accumulator> {
365 Box::new(self.clone())
366 }
367}
368
369#[derive(Debug, Clone)]
371pub struct AvgAccumulator {
372 sum: Option<f64>,
373 count: usize,
374 distinct_values: Option<HashSet<Vec<u8>>>,
375}
376
377impl AvgAccumulator {
378 pub fn new() -> Self {
380 Self::with_distinct(false)
381 }
382
383 pub fn with_distinct(distinct: bool) -> Self {
384 Self {
385 sum: None,
386 count: 0,
387 distinct_values: if distinct { Some(HashSet::new()) } else { None },
388 }
389 }
390}
391
392impl Default for AvgAccumulator {
393 fn default() -> Self {
394 Self::new()
395 }
396}
397
398impl Accumulator for AvgAccumulator {
399 fn update(&mut self, value: Option<SqlValue>) -> Result<()> {
400 let Some(value) = value else {
401 return Ok(());
402 };
403 if value.is_null() {
404 return Ok(());
405 }
406 if !distinct_allows(&mut self.distinct_values, &value)? {
407 return Ok(());
408 }
409 let numeric = numeric_to_f64(&value)?;
410 self.sum = Some(self.sum.unwrap_or(0.0) + numeric);
411 self.count += 1;
412 Ok(())
413 }
414
415 fn finalize(&self) -> Result<SqlValue> {
416 if self.count == 0 {
417 return Ok(SqlValue::Null);
418 }
419 let sum = self.sum.unwrap_or(0.0);
420 Ok(SqlValue::Double(sum / self.count as f64))
421 }
422
423 fn state(&self) -> Result<Vec<SqlValue>> {
424 Ok(vec![
425 SqlValue::Double(self.sum.unwrap_or(0.0)),
426 SqlValue::BigInt(self.count as i64),
427 ])
428 }
429
430 fn merge(&mut self, state: &[SqlValue]) -> Result<()> {
431 expect_state_arity("avg", state, 2)?;
432 let sum = state_double("avg", &state[0], 0)?;
433 let count = state_bigint("avg", &state[1], 1)?;
434 if count < 0 {
435 return Err(invalid_aggregate_state(
436 "avg",
437 "state count must be non-negative",
438 ));
439 }
440 self.sum = Some(self.sum.unwrap_or(0.0) + sum);
441 self.count = self.count.saturating_add(count as usize);
442 Ok(())
443 }
444
445 fn clone_box(&self) -> Box<dyn Accumulator> {
446 Box::new(self.clone())
447 }
448}
449
450fn numeric_to_f64(value: &SqlValue) -> Result<f64> {
451 match value {
452 SqlValue::Integer(v) => Ok(*v as f64),
453 SqlValue::BigInt(v) => Ok(*v as f64),
454 SqlValue::Float(v) => Ok(*v as f64),
455 SqlValue::Double(v) => Ok(*v),
456 _ => Err(ExecutorError::Evaluation(
457 crate::executor::EvaluationError::TypeMismatch {
458 expected: "numeric".into(),
459 actual: value.type_name().into(),
460 },
461 )),
462 }
463}
464
465#[derive(Debug, Clone)]
467pub struct MinMaxAccumulator {
468 value: Option<SqlValue>,
469 is_min: bool,
470 distinct_values: Option<HashSet<Vec<u8>>>,
471}
472
473impl MinMaxAccumulator {
474 pub fn new(is_min: bool) -> Self {
476 Self::with_distinct(is_min, false)
477 }
478
479 pub fn with_distinct(is_min: bool, distinct: bool) -> Self {
480 Self {
481 value: None,
482 is_min,
483 distinct_values: if distinct { Some(HashSet::new()) } else { None },
484 }
485 }
486}
487
488impl Accumulator for MinMaxAccumulator {
489 fn update(&mut self, value: Option<SqlValue>) -> Result<()> {
490 let Some(value) = value else {
491 return Ok(());
492 };
493 if value.is_null() {
494 return Ok(());
495 }
496 if !distinct_allows(&mut self.distinct_values, &value)? {
497 return Ok(());
498 }
499
500 match &self.value {
501 None => {
502 self.value = Some(value);
503 }
504 Some(current) => {
505 if std::mem::discriminant(current) != std::mem::discriminant(&value) {
506 return Err(ExecutorError::Evaluation(
507 crate::executor::EvaluationError::TypeMismatch {
508 expected: current.type_name().into(),
509 actual: value.type_name().into(),
510 },
511 ));
512 }
513 let ordering = value.partial_cmp(current).ok_or_else(|| {
514 ExecutorError::Evaluation(crate::executor::EvaluationError::TypeMismatch {
515 expected: current.type_name().into(),
516 actual: value.type_name().into(),
517 })
518 })?;
519 let should_replace = matches!(
520 (self.is_min, ordering),
521 (true, Ordering::Less) | (false, Ordering::Greater)
522 );
523 if should_replace {
524 self.value = Some(value);
525 }
526 }
527 }
528 Ok(())
529 }
530
531 fn finalize(&self) -> Result<SqlValue> {
532 Ok(self.value.clone().unwrap_or(SqlValue::Null))
533 }
534
535 fn state(&self) -> Result<Vec<SqlValue>> {
536 Ok(vec![self.value.clone().unwrap_or(SqlValue::Null)])
537 }
538
539 fn merge(&mut self, state: &[SqlValue]) -> Result<()> {
540 expect_state_arity(if self.is_min { "min" } else { "max" }, state, 1)?;
541 if state[0].is_null() {
542 return Ok(());
543 }
544 self.update(Some(state[0].clone()))
545 }
546
547 fn clone_box(&self) -> Box<dyn Accumulator> {
548 Box::new(self.clone())
549 }
550}
551
552#[derive(Debug, Clone)]
554pub struct GroupConcatAccumulator {
555 values: Vec<String>,
556 separator: String,
557 distinct_values: Option<HashSet<Vec<u8>>>,
558}
559
560impl GroupConcatAccumulator {
561 pub fn new(separator: String) -> Self {
563 Self::with_distinct(separator, false)
564 }
565
566 pub fn with_distinct(separator: String, distinct: bool) -> Self {
567 Self {
568 values: Vec::new(),
569 separator,
570 distinct_values: if distinct { Some(HashSet::new()) } else { None },
571 }
572 }
573}
574
575impl Accumulator for GroupConcatAccumulator {
576 fn update(&mut self, value: Option<SqlValue>) -> Result<()> {
577 let Some(value) = value else {
578 return Ok(());
579 };
580 match value {
581 SqlValue::Null => Ok(()),
582 SqlValue::Text(text) => {
583 let value = SqlValue::Text(text.clone());
584 if !distinct_allows(&mut self.distinct_values, &value)? {
585 return Ok(());
586 }
587 self.values.push(text);
588 Ok(())
589 }
590 other => Err(ExecutorError::Evaluation(
591 crate::executor::EvaluationError::TypeMismatch {
592 expected: "Text".into(),
593 actual: other.type_name().into(),
594 },
595 )),
596 }
597 }
598
599 fn finalize(&self) -> Result<SqlValue> {
600 if self.values.is_empty() {
601 return Ok(SqlValue::Null);
602 }
603 Ok(SqlValue::Text(self.values.join(&self.separator)))
604 }
605
606 fn state(&self) -> Result<Vec<SqlValue>> {
607 Ok(vec![
608 if self.values.is_empty() {
609 SqlValue::Null
610 } else {
611 SqlValue::Text(self.values.join(&self.separator))
612 },
613 SqlValue::Text(self.separator.clone()),
614 ])
615 }
616
617 fn merge(&mut self, state: &[SqlValue]) -> Result<()> {
618 expect_state_arity("group_concat", state, 2)?;
619 let separator = state_text("group_concat", &state[1], 1)?;
620 if separator != self.separator {
621 return Err(invalid_aggregate_state(
622 "group_concat",
623 "state separator differs from accumulator separator",
624 ));
625 }
626 match &state[0] {
627 SqlValue::Null => Ok(()),
628 SqlValue::Text(text) => {
629 self.values.push(text.clone());
630 Ok(())
631 }
632 other => Err(invalid_aggregate_state(
633 "group_concat",
634 format!(
635 "state value 0 expected Text or Null, got {}",
636 other.type_name()
637 ),
638 )),
639 }
640 }
641
642 fn clone_box(&self) -> Box<dyn Accumulator> {
643 Box::new(self.clone())
644 }
645}
646
647#[derive(Debug, Clone)]
649pub struct StringAggAccumulator {
650 values: Vec<String>,
651 separator: String,
652 distinct_values: Option<HashSet<Vec<u8>>>,
653}
654
655impl StringAggAccumulator {
656 pub fn new(separator: String) -> Self {
658 Self::with_distinct(separator, false)
659 }
660
661 pub fn with_distinct(separator: String, distinct: bool) -> Self {
662 Self {
663 values: Vec::new(),
664 separator,
665 distinct_values: if distinct { Some(HashSet::new()) } else { None },
666 }
667 }
668}
669
670impl Accumulator for StringAggAccumulator {
671 fn update(&mut self, value: Option<SqlValue>) -> Result<()> {
672 let Some(value) = value else {
673 return Ok(());
674 };
675 match value {
676 SqlValue::Null => Ok(()),
677 SqlValue::Text(s) => {
678 let value = SqlValue::Text(s.clone());
679 if !distinct_allows(&mut self.distinct_values, &value)? {
680 return Ok(());
681 }
682 self.values.push(s);
683 Ok(())
684 }
685 other => Err(ExecutorError::Evaluation(
686 crate::executor::EvaluationError::TypeMismatch {
687 expected: "Text".into(),
688 actual: other.type_name().into(),
689 },
690 )),
691 }
692 }
693
694 fn finalize(&self) -> Result<SqlValue> {
695 if self.values.is_empty() {
696 return Ok(SqlValue::Null);
697 }
698 Ok(SqlValue::Text(self.values.join(&self.separator)))
699 }
700
701 fn state(&self) -> Result<Vec<SqlValue>> {
702 Ok(vec![
703 if self.values.is_empty() {
704 SqlValue::Null
705 } else {
706 SqlValue::Text(self.values.join(&self.separator))
707 },
708 SqlValue::Text(self.separator.clone()),
709 ])
710 }
711
712 fn merge(&mut self, state: &[SqlValue]) -> Result<()> {
713 expect_state_arity("string_agg", state, 2)?;
714 let separator = state_text("string_agg", &state[1], 1)?;
715 if separator != self.separator {
716 return Err(invalid_aggregate_state(
717 "string_agg",
718 "state separator differs from accumulator separator",
719 ));
720 }
721 match &state[0] {
722 SqlValue::Null => Ok(()),
723 SqlValue::Text(text) => {
724 self.values.push(text.clone());
725 Ok(())
726 }
727 other => Err(invalid_aggregate_state(
728 "string_agg",
729 format!(
730 "state value 0 expected Text or Null, got {}",
731 other.type_name()
732 ),
733 )),
734 }
735 }
736
737 fn clone_box(&self) -> Box<dyn Accumulator> {
738 Box::new(self.clone())
739 }
740}
741
742pub fn create_accumulator(function: &AggregateFunction, distinct: bool) -> Box<dyn Accumulator> {
744 match function {
745 AggregateFunction::Count => Box::new(CountAccumulator::new(distinct)),
746 AggregateFunction::Sum => Box::new(SumAccumulator::with_distinct(distinct)),
747 AggregateFunction::Total => Box::new(TotalAccumulator::new()),
748 AggregateFunction::Avg => Box::new(AvgAccumulator::with_distinct(distinct)),
749 AggregateFunction::Min => Box::new(MinMaxAccumulator::with_distinct(true, distinct)),
750 AggregateFunction::Max => Box::new(MinMaxAccumulator::with_distinct(false, distinct)),
751 AggregateFunction::GroupConcat { separator } => {
752 let sep = separator.clone().unwrap_or_else(|| ",".to_string());
753 Box::new(GroupConcatAccumulator::with_distinct(sep, distinct))
754 }
755 AggregateFunction::StringAgg { separator } => {
756 let sep = separator.clone().unwrap_or_else(|| ",".to_string());
757 Box::new(StringAggAccumulator::with_distinct(sep, distinct))
758 }
759 }
760}
761
762pub fn exact_partial_aggregate_is_proven(aggregate: &AggregateExpr) -> bool {
766 !aggregate.distinct
767 && matches!(
768 aggregate.function,
769 AggregateFunction::Count | AggregateFunction::Min | AggregateFunction::Max
770 )
771}
772
773pub fn merge_exact_aggregate_states(
777 aggregates: &[AggregateExpr],
778 partial_rows: impl IntoIterator<Item = Vec<Vec<SqlValue>>>,
779) -> Result<Vec<SqlValue>> {
780 if let Some(aggregate) = aggregates
781 .iter()
782 .find(|aggregate| !exact_partial_aggregate_is_proven(aggregate))
783 {
784 return Err(ExecutorError::InvalidOperation {
785 operation: "distributed aggregate merge".into(),
786 reason: format!(
787 "{:?} requires ordered input replay rather than an exact partial merge",
788 aggregate.function
789 ),
790 });
791 }
792
793 let mut accumulators = aggregates
794 .iter()
795 .map(|aggregate| create_accumulator(&aggregate.function, false))
796 .collect::<Vec<_>>();
797 for states in partial_rows {
798 if states.len() != accumulators.len() {
799 return Err(ExecutorError::InvalidOperation {
800 operation: "distributed aggregate merge".into(),
801 reason: format!(
802 "partial state has {} aggregate(s), expected {}",
803 states.len(),
804 accumulators.len()
805 ),
806 });
807 }
808 for (accumulator, state) in accumulators.iter_mut().zip(states) {
809 accumulator.merge(&state)?;
810 }
811 }
812 accumulators
813 .iter()
814 .map(|accumulator| accumulator.finalize())
815 .collect()
816}
817
818const DEFAULT_GROUP_LIMIT: usize = 1_000_000;
819const AGGREGATE_ACCUMULATOR_OVERHEAD_BYTES: u64 = 32;
820
821#[derive(Debug, Clone, Copy, PartialEq, Eq)]
823pub enum AggregateMode {
824 Partial,
826 Final,
828 Single,
830}
831
832struct AggregateGroup {
833 key_values: Vec<SqlValue>,
834 accumulators: Vec<Box<dyn Accumulator>>,
835}
836
837pub struct AggregateIterator<'a> {
839 input: Box<dyn RowIterator + 'a>,
840 group_keys: Vec<TypedExpr>,
841 aggregates: Vec<AggregateExpr>,
842 having: Option<TypedExpr>,
843 mode: AggregateMode,
844 hash_table: Option<HashMap<GroupKeyBytes, AggregateGroup>>,
845 result_rows: Vec<Row>,
846 index: usize,
847 schema: Vec<ColumnMetadata>,
848 group_limit: usize,
849 memory_tracker: Option<MemoryTracker>,
850 shared_group_counter: Option<Arc<AtomicUsize>>,
851}
852
853impl<'a> AggregateIterator<'a> {
854 pub fn new(
856 input: Box<dyn RowIterator + 'a>,
857 group_keys: Vec<TypedExpr>,
858 aggregates: Vec<AggregateExpr>,
859 having: Option<TypedExpr>,
860 schema: Vec<ColumnMetadata>,
861 ) -> Self {
862 Self {
863 input,
864 group_keys,
865 aggregates,
866 having,
867 mode: AggregateMode::Single,
868 hash_table: None,
869 result_rows: Vec::new(),
870 index: 0,
871 schema,
872 group_limit: DEFAULT_GROUP_LIMIT,
873 memory_tracker: None,
874 shared_group_counter: None,
875 }
876 }
877
878 pub fn with_group_limit(mut self, limit: usize) -> Self {
880 self.group_limit = limit;
881 self
882 }
883
884 pub fn with_mode(mut self, mode: AggregateMode) -> Self {
886 self.mode = mode;
887 self
888 }
889
890 pub fn with_memory_policy(mut self, policy: Option<MemoryPolicy>) -> Self {
892 self.memory_tracker = policy.map(MemoryTracker::new);
893 self
894 }
895
896 pub fn with_shared_group_counter(mut self, counter: Option<Arc<AtomicUsize>>) -> Self {
898 self.shared_group_counter = counter;
899 self
900 }
901
902 fn build_hash_table(&mut self) -> Result<()> {
903 let mut table: HashMap<GroupKeyBytes, AggregateGroup> = HashMap::new();
904 let mut next_row_id = 0u64;
905
906 while let Some(result) = self.input.next_row() {
907 let row = result?;
908 let (key_values, key_bytes) = match self.mode {
909 AggregateMode::Final => {
910 let key_values = row
911 .values
912 .get(..self.group_keys.len())
913 .ok_or_else(|| {
914 invalid_aggregate_state(
915 "aggregate",
916 "partial state row is missing group key values",
917 )
918 })?
919 .to_vec();
920 let key_bytes = encode_group_key(&key_values)?;
921 (key_values, key_bytes)
922 }
923 AggregateMode::Partial | AggregateMode::Single => {
924 let ctx = EvalContext::new(&row.values);
925 let mut key_values = Vec::with_capacity(self.group_keys.len());
926 for expr in &self.group_keys {
927 key_values.push(crate::executor::evaluator::evaluate(expr, &ctx)?);
928 }
929 let key_bytes = encode_group_key(&key_values)?;
930 (key_values, key_bytes)
931 }
932 };
933
934 if !table.contains_key(&key_bytes) {
935 self.reserve_group_slot(table.len())?;
936 if let Some(tracker) = &mut self.memory_tracker {
937 tracker
938 .add_values(&key_values)
939 .map_err(map_core_memory_error)?;
940 tracker
941 .add_bytes(
942 self.aggregates.len() as u64 * AGGREGATE_ACCUMULATOR_OVERHEAD_BYTES,
943 )
944 .map_err(map_core_memory_error)?;
945 }
946 let accumulators = self
947 .aggregates
948 .iter()
949 .map(|agg| {
950 create_accumulator(
951 &agg.function,
952 matches!(self.mode, AggregateMode::Single) && agg.distinct,
953 )
954 })
955 .collect::<Vec<_>>();
956 table.insert(
957 key_bytes.clone(),
958 AggregateGroup {
959 key_values: key_values.clone(),
960 accumulators,
961 },
962 );
963 }
964
965 if let Some(group) = table.get_mut(&key_bytes) {
966 match self.mode {
967 AggregateMode::Final => {
968 let mut offset = self.group_keys.len();
969 for (idx, agg) in self.aggregates.iter().enumerate() {
970 let arity = aggregate_state_types(agg).len();
971 let state = row.values.get(offset..offset + arity).ok_or_else(|| {
972 invalid_aggregate_state(
973 "aggregate",
974 format!(
975 "partial state row is missing state values for aggregate {idx}"
976 ),
977 )
978 })?;
979 group.accumulators[idx].merge(state)?;
980 offset += arity;
981 }
982 if offset != row.values.len() {
983 return Err(invalid_aggregate_state(
984 "aggregate",
985 format!(
986 "partial state row has {} trailing value(s)",
987 row.values.len() - offset
988 ),
989 ));
990 }
991 }
992 AggregateMode::Partial | AggregateMode::Single => {
993 let ctx = EvalContext::new(&row.values);
994 for (idx, agg) in self.aggregates.iter().enumerate() {
995 let value = match &agg.arg {
996 None => None,
997 Some(expr) => {
998 Some(crate::executor::evaluator::evaluate(expr, &ctx)?)
999 }
1000 };
1001 if let Some(tracker) = &mut self.memory_tracker
1002 && matches!(
1003 agg.function,
1004 AggregateFunction::GroupConcat { .. }
1005 | AggregateFunction::StringAgg { .. }
1006 )
1007 && let Some(value_ref) = value.as_ref()
1008 {
1009 tracker
1010 .add_value(value_ref)
1011 .map_err(map_core_memory_error)?;
1012 }
1013 group.accumulators[idx].update(value)?;
1014 }
1015 }
1016 }
1017 }
1018 }
1019
1020 if table.is_empty() && self.group_keys.is_empty() {
1021 if let Some(tracker) = &mut self.memory_tracker {
1022 tracker
1023 .add_bytes(self.aggregates.len() as u64 * AGGREGATE_ACCUMULATOR_OVERHEAD_BYTES)
1024 .map_err(map_core_memory_error)?;
1025 }
1026 let accumulators = self
1027 .aggregates
1028 .iter()
1029 .map(|agg| {
1030 create_accumulator(
1031 &agg.function,
1032 matches!(self.mode, AggregateMode::Single) && agg.distinct,
1033 )
1034 })
1035 .collect::<Vec<_>>();
1036 table.insert(
1037 Vec::new(),
1038 AggregateGroup {
1039 key_values: Vec::new(),
1040 accumulators,
1041 },
1042 );
1043 }
1044
1045 let mut rows = Vec::with_capacity(table.len());
1046 for group in table.values() {
1047 let mut values = Vec::with_capacity(self.group_keys.len() + self.aggregates.len());
1048 values.extend(group.key_values.iter().cloned());
1049 for acc in &group.accumulators {
1050 match self.mode {
1051 AggregateMode::Partial => values.extend(acc.state()?),
1052 AggregateMode::Final | AggregateMode::Single => values.push(acc.finalize()?),
1053 }
1054 }
1055 let row = Row::new(next_row_id, values);
1056 next_row_id += 1;
1057 if let Some(tracker) = &mut self.memory_tracker {
1058 tracker
1059 .add_row(&row.values)
1060 .map_err(map_core_memory_error)?;
1061 }
1062
1063 if self.mode != AggregateMode::Partial
1064 && let Some(having) = &self.having
1065 {
1066 let ctx = EvalContext::new(&row.values);
1067 match crate::executor::evaluator::evaluate(having, &ctx)? {
1068 SqlValue::Boolean(true) => rows.push(row),
1069 SqlValue::Boolean(false) | SqlValue::Null => {}
1070 other => {
1071 return Err(ExecutorError::Evaluation(
1072 crate::executor::EvaluationError::TypeMismatch {
1073 expected: "Boolean".into(),
1074 actual: other.type_name().into(),
1075 },
1076 ));
1077 }
1078 }
1079 } else {
1080 rows.push(row);
1081 }
1082 }
1083
1084 self.hash_table = Some(table);
1085 self.result_rows = rows;
1086 Ok(())
1087 }
1088
1089 fn reserve_group_slot(&self, local_group_count: usize) -> Result<()> {
1090 let next_count = if let Some(counter) = &self.shared_group_counter {
1091 counter.fetch_add(1, std::sync::atomic::Ordering::SeqCst) + 1
1092 } else {
1093 local_group_count + 1
1094 };
1095 if next_count > self.group_limit {
1096 return Err(ExecutorError::ResourceExhausted {
1097 message: format!(
1098 "GROUP BY result exceeds memory limit (max groups: {})",
1099 self.group_limit
1100 ),
1101 });
1102 }
1103 Ok(())
1104 }
1105}
1106
1107impl<'a> RowIterator for AggregateIterator<'a> {
1108 fn next_row(&mut self) -> Option<Result<Row>> {
1109 if self.hash_table.is_none()
1110 && let Err(err) = self.build_hash_table()
1111 {
1112 return Some(Err(err));
1113 }
1114
1115 if self.index >= self.result_rows.len() {
1116 return None;
1117 }
1118 let row = self.result_rows[self.index].clone();
1119 self.index += 1;
1120 Some(Ok(row))
1121 }
1122
1123 fn schema(&self) -> &[ColumnMetadata] {
1124 &self.schema
1125 }
1126}
1127
1128pub struct StreamingAggregateIterator<'a> {
1130 input: Box<dyn RowIterator + 'a>,
1131 group_keys: Vec<TypedExpr>,
1132 aggregates: Vec<AggregateExpr>,
1133 having: Option<TypedExpr>,
1134 schema: Vec<ColumnMetadata>,
1135 current_key: Option<Vec<SqlValue>>,
1136 accumulators: Vec<Box<dyn Accumulator>>,
1137 pending_row: Option<Row>,
1138 finished: bool,
1139 next_row_id: u64,
1140 saw_row: bool,
1141}
1142
1143impl<'a> StreamingAggregateIterator<'a> {
1144 pub fn new(
1145 input: Box<dyn RowIterator + 'a>,
1146 group_keys: Vec<TypedExpr>,
1147 aggregates: Vec<AggregateExpr>,
1148 having: Option<TypedExpr>,
1149 schema: Vec<ColumnMetadata>,
1150 ) -> Self {
1151 Self {
1152 input,
1153 group_keys,
1154 aggregates,
1155 having,
1156 schema,
1157 current_key: None,
1158 accumulators: Vec::new(),
1159 pending_row: None,
1160 finished: false,
1161 next_row_id: 0,
1162 saw_row: false,
1163 }
1164 }
1165
1166 fn init_accumulators(&self) -> Vec<Box<dyn Accumulator>> {
1167 self.aggregates
1168 .iter()
1169 .map(|agg| create_accumulator(&agg.function, agg.distinct))
1170 .collect()
1171 }
1172
1173 fn update_accumulators(&mut self, ctx: &EvalContext<'_>) -> Result<()> {
1174 for (idx, agg) in self.aggregates.iter().enumerate() {
1175 let value = match &agg.arg {
1176 None => None,
1177 Some(expr) => Some(crate::executor::evaluator::evaluate(expr, ctx)?),
1178 };
1179 self.accumulators[idx].update(value)?;
1180 }
1181 Ok(())
1182 }
1183
1184 fn finalize_group(&mut self, key_values: &[SqlValue]) -> Result<Option<Row>> {
1185 let mut values = Vec::with_capacity(self.group_keys.len() + self.aggregates.len());
1186 values.extend(key_values.iter().cloned());
1187 for acc in &self.accumulators {
1188 values.push(acc.finalize()?);
1189 }
1190 let row = Row::new(self.next_row_id, values);
1191 self.next_row_id = self.next_row_id.saturating_add(1);
1192
1193 if let Some(having) = &self.having {
1194 let ctx = EvalContext::new(&row.values);
1195 match crate::executor::evaluator::evaluate(having, &ctx)? {
1196 SqlValue::Boolean(true) => Ok(Some(row)),
1197 SqlValue::Boolean(false) | SqlValue::Null => Ok(None),
1198 other => Err(ExecutorError::Evaluation(
1199 crate::executor::EvaluationError::TypeMismatch {
1200 expected: "Boolean".into(),
1201 actual: other.type_name().into(),
1202 },
1203 )),
1204 }
1205 } else {
1206 Ok(Some(row))
1207 }
1208 }
1209}
1210
1211impl<'a> RowIterator for StreamingAggregateIterator<'a> {
1212 fn next_row(&mut self) -> Option<Result<Row>> {
1213 if let Some(row) = self.pending_row.take() {
1214 return Some(Ok(row));
1215 }
1216 if self.finished {
1217 return None;
1218 }
1219
1220 loop {
1221 match self.input.next_row() {
1222 Some(Ok(row)) => {
1223 self.saw_row = true;
1224 let ctx = EvalContext::new(&row.values);
1225 let mut key_values = Vec::with_capacity(self.group_keys.len());
1226 for expr in &self.group_keys {
1227 match crate::executor::evaluator::evaluate(expr, &ctx) {
1228 Ok(value) => key_values.push(value),
1229 Err(err) => return Some(Err(err)),
1230 }
1231 }
1232
1233 match &self.current_key {
1234 None => {
1235 self.current_key = Some(key_values);
1236 self.accumulators = self.init_accumulators();
1237 if let Err(err) = self.update_accumulators(&ctx) {
1238 return Some(Err(err));
1239 }
1240 }
1241 Some(current_key) if *current_key == key_values => {
1242 if let Err(err) = self.update_accumulators(&ctx) {
1243 return Some(Err(err));
1244 }
1245 }
1246 Some(_) => {
1247 let current_key = self.current_key.clone().unwrap_or_default();
1248 let output = match self.finalize_group(¤t_key) {
1249 Ok(value) => value,
1250 Err(err) => return Some(Err(err)),
1251 };
1252 self.current_key = Some(key_values);
1253 self.accumulators = self.init_accumulators();
1254 if let Err(err) = self.update_accumulators(&ctx) {
1255 return Some(Err(err));
1256 }
1257 if let Some(row) = output {
1258 return Some(Ok(row));
1259 }
1260 }
1261 }
1262 }
1263 Some(Err(err)) => return Some(Err(err)),
1264 None => {
1265 self.finished = true;
1266 if let Some(current_key) = self.current_key.take() {
1267 return match self.finalize_group(¤t_key) {
1268 Ok(Some(row)) => Some(Ok(row)),
1269 Ok(None) => None,
1270 Err(err) => Some(Err(err)),
1271 };
1272 }
1273
1274 if self.group_keys.is_empty() && !self.saw_row {
1275 self.accumulators = self.init_accumulators();
1276 return match self.finalize_group(&[]) {
1277 Ok(Some(row)) => Some(Ok(row)),
1278 Ok(None) => None,
1279 Err(err) => Some(Err(err)),
1280 };
1281 }
1282
1283 return None;
1284 }
1285 }
1286 }
1287 }
1288
1289 fn schema(&self) -> &[ColumnMetadata] {
1290 &self.schema
1291 }
1292}
1293
1294fn aggregate_state_types(agg: &AggregateExpr) -> Vec<ResolvedType> {
1295 match &agg.function {
1296 AggregateFunction::Count => vec![ResolvedType::BigInt],
1297 AggregateFunction::Sum => vec![ResolvedType::Double],
1298 AggregateFunction::Total => vec![ResolvedType::Double],
1299 AggregateFunction::Avg => vec![ResolvedType::Double, ResolvedType::BigInt],
1300 AggregateFunction::Min | AggregateFunction::Max => vec![agg.result_type.clone()],
1301 AggregateFunction::GroupConcat { .. } | AggregateFunction::StringAgg { .. } => {
1302 vec![ResolvedType::Text, ResolvedType::Text]
1303 }
1304 }
1305}
1306
1307pub fn build_aggregate_schema(
1309 group_keys: &[TypedExpr],
1310 aggregates: &[AggregateExpr],
1311) -> Vec<ColumnMetadata> {
1312 let mut schema = Vec::new();
1313 for (idx, key) in group_keys.iter().enumerate() {
1314 let name = match &key.kind {
1315 crate::planner::typed_expr::TypedExprKind::ColumnRef { column, .. } => column.clone(),
1316 _ => format!("group_{idx}"),
1317 };
1318 schema.push(ColumnMetadata::new(name, key.resolved_type.clone()));
1319 }
1320 for (idx, agg) in aggregates.iter().enumerate() {
1321 let name = match &agg.function {
1322 AggregateFunction::Count => format!("count_{idx}"),
1323 AggregateFunction::Sum => format!("sum_{idx}"),
1324 AggregateFunction::Total => format!("total_{idx}"),
1325 AggregateFunction::Avg => format!("avg_{idx}"),
1326 AggregateFunction::Min => format!("min_{idx}"),
1327 AggregateFunction::Max => format!("max_{idx}"),
1328 AggregateFunction::GroupConcat { .. } => format!("group_concat_{idx}"),
1329 AggregateFunction::StringAgg { .. } => format!("string_agg_{idx}"),
1330 };
1331 schema.push(ColumnMetadata::new(name, agg.result_type.clone()));
1332 }
1333 schema
1334}
1335
1336pub fn build_partial_aggregate_schema(
1338 group_keys: &[TypedExpr],
1339 aggregates: &[AggregateExpr],
1340) -> Vec<ColumnMetadata> {
1341 let mut schema = Vec::new();
1342 for (idx, key) in group_keys.iter().enumerate() {
1343 let name = match &key.kind {
1344 crate::planner::typed_expr::TypedExprKind::ColumnRef { column, .. } => column.clone(),
1345 _ => format!("group_{idx}"),
1346 };
1347 schema.push(ColumnMetadata::new(name, key.resolved_type.clone()));
1348 }
1349 for (agg_idx, agg) in aggregates.iter().enumerate() {
1350 for (state_idx, state_type) in aggregate_state_types(agg).into_iter().enumerate() {
1351 schema.push(ColumnMetadata::new(
1352 format!("__agg{agg_idx}_state{state_idx}"),
1353 state_type,
1354 ));
1355 }
1356 }
1357 schema
1358}
1359
1360pub fn should_use_single_for_parallel(parallelism: usize, aggregates: &[AggregateExpr]) -> bool {
1362 parallelism <= 1 || aggregates.iter().any(|agg| agg.distinct)
1363}
1364
1365fn collect_iterator_rows(iter: &mut dyn RowIterator) -> Result<Vec<Row>> {
1366 let mut rows = Vec::new();
1367 while let Some(result) = iter.next_row() {
1368 rows.push(result?);
1369 }
1370 Ok(rows)
1371}
1372
1373struct ChainRowIterator<'a> {
1374 prefix: std::vec::IntoIter<Row>,
1375 tail: Box<dyn RowIterator + 'a>,
1376 schema: Vec<ColumnMetadata>,
1377}
1378
1379impl<'a> ChainRowIterator<'a> {
1380 fn new(prefix: Vec<Row>, tail: Box<dyn RowIterator + 'a>, schema: Vec<ColumnMetadata>) -> Self {
1381 Self {
1382 prefix: prefix.into_iter(),
1383 tail,
1384 schema,
1385 }
1386 }
1387}
1388
1389impl RowIterator for ChainRowIterator<'_> {
1390 fn next_row(&mut self) -> Option<Result<Row>> {
1391 if let Some(row) = self.prefix.next() {
1392 return Some(Ok(row));
1393 }
1394 self.tail.next_row()
1395 }
1396
1397 fn schema(&self) -> &[ColumnMetadata] {
1398 &self.schema
1399 }
1400}
1401
1402fn estimate_row_bytes(row: &Row) -> u64 {
1403 row.values.iter().map(ByteSized::estimated_bytes).sum()
1404}
1405
1406fn split_contiguous_partitions(rows: Vec<Row>, parallelism: usize) -> Vec<Vec<Row>> {
1407 let requested = parallelism.max(1);
1408 if rows.is_empty() {
1409 return (0..requested).map(|_| Vec::new()).collect();
1410 }
1411 let partitions = requested.min(rows.len());
1412 let total = rows.len();
1413 let mut tail = rows;
1414 let mut output = Vec::with_capacity(partitions);
1415 for partition in (0..partitions).rev() {
1416 let start = partition * total / partitions;
1417 output.push(tail.split_off(start));
1418 }
1419 output.reverse();
1420 output
1421}
1422
1423#[allow(clippy::too_many_arguments)]
1424fn execute_partial_partition(
1425 partition_index: usize,
1426 rows: Vec<Row>,
1427 input_schema: Vec<ColumnMetadata>,
1428 group_keys: Vec<TypedExpr>,
1429 aggregates: Vec<AggregateExpr>,
1430 partial_schema: Vec<ColumnMetadata>,
1431 group_limit: usize,
1432 shared_group_counter: Arc<AtomicUsize>,
1433 shared_memory_counter: Arc<AtomicU64>,
1434 memory_limit: Option<u64>,
1435) -> Result<(usize, Vec<Row>)> {
1436 let input = VecIterator::new(rows, input_schema);
1437 let mut iter = AggregateIterator::new(
1438 Box::new(input),
1439 group_keys,
1440 aggregates,
1441 None,
1442 partial_schema,
1443 )
1444 .with_mode(AggregateMode::Partial)
1445 .with_group_limit(group_limit)
1446 .with_shared_group_counter(Some(shared_group_counter));
1447 let rows = collect_iterator_rows(&mut iter)?;
1448 for row in &rows {
1449 let used = shared_memory_counter
1450 .fetch_add(estimate_row_bytes(row), std::sync::atomic::Ordering::SeqCst)
1451 .saturating_add(estimate_row_bytes(row));
1452 if let Some(limit) = memory_limit
1453 && used > limit
1454 {
1455 return Err(ExecutorError::ResourceExhausted {
1456 message: format!(
1457 "parallel aggregate memory limit exceeded: {used} bytes (limit {limit})"
1458 ),
1459 });
1460 }
1461 }
1462 Ok((partition_index, rows))
1463}
1464
1465fn recv_partition_results(
1466 receiver: std::sync::mpsc::Receiver<Result<(usize, Vec<Row>)>>,
1467 expected: usize,
1468) -> Result<Vec<(usize, Vec<Row>)>> {
1469 let mut outputs = Vec::with_capacity(expected);
1470 for _ in 0..expected {
1471 let result = receiver
1472 .recv()
1473 .map_err(|err| ExecutorError::InvalidOperation {
1474 operation: "parallel aggregate".into(),
1475 reason: format!("partition worker failed to report result: {err}"),
1476 })?;
1477 outputs.push(result?);
1478 }
1479 outputs.sort_by_key(|(idx, _)| *idx);
1480 Ok(outputs)
1481}
1482
1483#[cfg(feature = "tokio")]
1484#[allow(clippy::too_many_arguments)]
1485fn run_partial_partitions(
1486 partitions: Vec<Vec<Row>>,
1487 input_schema: Vec<ColumnMetadata>,
1488 group_keys: Vec<TypedExpr>,
1489 aggregates: Vec<AggregateExpr>,
1490 partial_schema: Vec<ColumnMetadata>,
1491 group_limit: usize,
1492 shared_group_counter: Arc<AtomicUsize>,
1493 shared_memory_counter: Arc<AtomicU64>,
1494 memory_limit: Option<u64>,
1495) -> Result<Vec<(usize, Vec<Row>)>> {
1496 if let Ok(handle) = tokio::runtime::Handle::try_current() {
1497 let expected = partitions.len();
1498 let (sender, receiver) = std::sync::mpsc::channel();
1499 for (partition_index, rows) in partitions.into_iter().enumerate() {
1500 let sender = sender.clone();
1501 let input_schema = input_schema.clone();
1502 let group_keys = group_keys.clone();
1503 let aggregates = aggregates.clone();
1504 let partial_schema = partial_schema.clone();
1505 let shared_group_counter = Arc::clone(&shared_group_counter);
1506 let shared_memory_counter = Arc::clone(&shared_memory_counter);
1507 handle.spawn_blocking(move || {
1508 let result = execute_partial_partition(
1509 partition_index,
1510 rows,
1511 input_schema,
1512 group_keys,
1513 aggregates,
1514 partial_schema,
1515 group_limit,
1516 shared_group_counter,
1517 shared_memory_counter,
1518 memory_limit,
1519 );
1520 let _ = sender.send(result);
1521 });
1522 }
1523 drop(sender);
1524 return recv_partition_results(receiver, expected);
1525 }
1526
1527 run_partial_partitions_on_threads(
1528 partitions,
1529 input_schema,
1530 group_keys,
1531 aggregates,
1532 partial_schema,
1533 group_limit,
1534 shared_group_counter,
1535 shared_memory_counter,
1536 memory_limit,
1537 )
1538}
1539
1540#[cfg(not(feature = "tokio"))]
1541#[allow(clippy::too_many_arguments)]
1542fn run_partial_partitions(
1543 partitions: Vec<Vec<Row>>,
1544 input_schema: Vec<ColumnMetadata>,
1545 group_keys: Vec<TypedExpr>,
1546 aggregates: Vec<AggregateExpr>,
1547 partial_schema: Vec<ColumnMetadata>,
1548 group_limit: usize,
1549 shared_group_counter: Arc<AtomicUsize>,
1550 shared_memory_counter: Arc<AtomicU64>,
1551 memory_limit: Option<u64>,
1552) -> Result<Vec<(usize, Vec<Row>)>> {
1553 run_partial_partitions_on_threads(
1554 partitions,
1555 input_schema,
1556 group_keys,
1557 aggregates,
1558 partial_schema,
1559 group_limit,
1560 shared_group_counter,
1561 shared_memory_counter,
1562 memory_limit,
1563 )
1564}
1565
1566#[allow(clippy::too_many_arguments)]
1567fn run_partial_partitions_on_threads(
1568 partitions: Vec<Vec<Row>>,
1569 input_schema: Vec<ColumnMetadata>,
1570 group_keys: Vec<TypedExpr>,
1571 aggregates: Vec<AggregateExpr>,
1572 partial_schema: Vec<ColumnMetadata>,
1573 group_limit: usize,
1574 shared_group_counter: Arc<AtomicUsize>,
1575 shared_memory_counter: Arc<AtomicU64>,
1576 memory_limit: Option<u64>,
1577) -> Result<Vec<(usize, Vec<Row>)>> {
1578 let expected = partitions.len();
1579 let (sender, receiver) = std::sync::mpsc::channel();
1580 std::thread::scope(|scope| {
1581 for (partition_index, rows) in partitions.into_iter().enumerate() {
1582 let sender = sender.clone();
1583 let input_schema = input_schema.clone();
1584 let group_keys = group_keys.clone();
1585 let aggregates = aggregates.clone();
1586 let partial_schema = partial_schema.clone();
1587 let shared_group_counter = Arc::clone(&shared_group_counter);
1588 let shared_memory_counter = Arc::clone(&shared_memory_counter);
1589 scope.spawn(move || {
1590 let result = execute_partial_partition(
1591 partition_index,
1592 rows,
1593 input_schema,
1594 group_keys,
1595 aggregates,
1596 partial_schema,
1597 group_limit,
1598 shared_group_counter,
1599 shared_memory_counter,
1600 memory_limit,
1601 );
1602 let _ = sender.send(result);
1603 });
1604 }
1605 });
1606 drop(sender);
1607 recv_partition_results(receiver, expected)
1608}
1609
1610pub fn execute_parallel_aggregate_rows<'a>(
1612 input: Box<dyn RowIterator + 'a>,
1613 group_keys: Vec<TypedExpr>,
1614 aggregates: Vec<AggregateExpr>,
1615 having: Option<TypedExpr>,
1616 final_schema: Vec<ColumnMetadata>,
1617 parallelism: usize,
1618) -> Result<Vec<Row>> {
1619 execute_parallel_aggregate_rows_with_policy(
1620 input,
1621 group_keys,
1622 aggregates,
1623 having,
1624 final_schema,
1625 parallelism,
1626 None,
1627 DEFAULT_GROUP_LIMIT,
1628 )
1629}
1630
1631#[allow(clippy::too_many_arguments)]
1633pub fn execute_parallel_aggregate_rows_with_policy<'a>(
1634 mut input: Box<dyn RowIterator + 'a>,
1635 group_keys: Vec<TypedExpr>,
1636 aggregates: Vec<AggregateExpr>,
1637 having: Option<TypedExpr>,
1638 final_schema: Vec<ColumnMetadata>,
1639 parallelism: usize,
1640 memory: Option<MemoryPolicy>,
1641 group_limit: usize,
1642) -> Result<Vec<Row>> {
1643 if parallelism <= 1 {
1644 return execute_single_aggregate_rows(
1645 input,
1646 group_keys,
1647 aggregates,
1648 having,
1649 final_schema,
1650 memory,
1651 group_limit,
1652 );
1653 }
1654
1655 let input_schema = input.schema().to_vec();
1656 let mut input_rows = Vec::new();
1657 let mut materialized_bytes = 0u64;
1658 let materialize_threshold = memory
1659 .as_ref()
1660 .and_then(MemoryPolicy::limit_bytes)
1661 .map(|limit| (limit / 2).max(1));
1662
1663 while let Some(result) = input.next_row() {
1664 let row = result?;
1665 let row_bytes = materialize_threshold.map(|_| estimate_row_bytes(&row));
1666 if let (Some(threshold), Some(row_bytes)) = (materialize_threshold, row_bytes)
1667 && materialized_bytes.saturating_add(row_bytes) > threshold
1668 {
1669 input_rows.push(row);
1670 let chained = ChainRowIterator::new(input_rows, input, input_schema.clone());
1671 return execute_single_aggregate_rows(
1672 Box::new(chained),
1673 group_keys,
1674 aggregates,
1675 having,
1676 final_schema,
1677 memory,
1678 group_limit,
1679 );
1680 }
1681 if let Some(row_bytes) = row_bytes {
1682 materialized_bytes = materialized_bytes.saturating_add(row_bytes);
1683 }
1684 input_rows.push(row);
1685 }
1686
1687 let fallback_rows = if memory.is_some() || group_limit < input_rows.len() {
1688 Some(input_rows.clone())
1689 } else {
1690 None
1691 };
1692 let result = execute_parallel_aggregate_rows_from_materialized(
1693 input_rows,
1694 input_schema.clone(),
1695 group_keys.clone(),
1696 aggregates.clone(),
1697 having.clone(),
1698 final_schema.clone(),
1699 parallelism,
1700 group_limit,
1701 materialized_bytes,
1702 memory.as_ref().and_then(MemoryPolicy::limit_bytes),
1703 );
1704 match result {
1705 Ok(rows) => Ok(rows),
1706 Err(ExecutorError::ResourceExhausted { .. }) => {
1707 if let Some(fallback_rows) = fallback_rows {
1708 execute_single_aggregate_rows(
1709 Box::new(VecIterator::new(fallback_rows, input_schema)),
1710 group_keys,
1711 aggregates,
1712 having,
1713 final_schema,
1714 memory,
1715 group_limit,
1716 )
1717 } else {
1718 Err(ExecutorError::ResourceExhausted {
1719 message: format!(
1720 "parallel aggregate exceeded group limit {group_limit}; no fallback rows retained"
1721 ),
1722 })
1723 }
1724 }
1725 Err(err) => Err(err),
1726 }
1727}
1728
1729#[allow(clippy::too_many_arguments)]
1730fn execute_parallel_aggregate_rows_from_materialized(
1731 input_rows: Vec<Row>,
1732 input_schema: Vec<ColumnMetadata>,
1733 group_keys: Vec<TypedExpr>,
1734 aggregates: Vec<AggregateExpr>,
1735 having: Option<TypedExpr>,
1736 final_schema: Vec<ColumnMetadata>,
1737 parallelism: usize,
1738 group_limit: usize,
1739 materialized_bytes: u64,
1740 memory_limit: Option<u64>,
1741) -> Result<Vec<Row>> {
1742 let partial_schema = build_partial_aggregate_schema(&group_keys, &aggregates);
1743 let partitions = split_contiguous_partitions(input_rows, parallelism);
1744 let shared_group_counter = Arc::new(AtomicUsize::new(0));
1745 let shared_memory_counter = Arc::new(AtomicU64::new(materialized_bytes));
1746 let partial_results = run_partial_partitions(
1747 partitions,
1748 input_schema,
1749 group_keys.clone(),
1750 aggregates.clone(),
1751 partial_schema.clone(),
1752 group_limit,
1753 shared_group_counter,
1754 shared_memory_counter,
1755 memory_limit,
1756 )?;
1757
1758 let partial_rows = partial_results
1759 .into_iter()
1760 .flat_map(|(_, rows)| rows)
1761 .collect::<Vec<_>>();
1762 let final_input = VecIterator::new(partial_rows, partial_schema);
1763 let mut final_iter = AggregateIterator::new(
1764 Box::new(final_input),
1765 group_keys,
1766 aggregates,
1767 having,
1768 final_schema,
1769 )
1770 .with_mode(AggregateMode::Final)
1771 .with_group_limit(group_limit);
1772 collect_iterator_rows(&mut final_iter)
1773}
1774
1775fn execute_single_aggregate_rows<'a>(
1776 input: Box<dyn RowIterator + 'a>,
1777 group_keys: Vec<TypedExpr>,
1778 aggregates: Vec<AggregateExpr>,
1779 having: Option<TypedExpr>,
1780 final_schema: Vec<ColumnMetadata>,
1781 memory: Option<MemoryPolicy>,
1782 group_limit: usize,
1783) -> Result<Vec<Row>> {
1784 let mut iter = AggregateIterator::new(input, group_keys, aggregates, having, final_schema)
1785 .with_group_limit(group_limit)
1786 .with_memory_policy(memory);
1787 collect_iterator_rows(&mut iter)
1788}
1789
1790#[cfg(test)]
1791mod tests {
1792 use super::*;
1793 use crate::ast::span::Span;
1794 use crate::executor::memory::SpillPolicy;
1795 use crate::planner::typed_expr::TypedExprKind;
1796
1797 fn apply_values(acc: &mut dyn Accumulator, values: &[Option<SqlValue>]) {
1798 for value in values {
1799 acc.update(value.clone()).unwrap();
1800 }
1801 }
1802
1803 fn single_result(
1804 make_accumulator: impl Fn() -> Box<dyn Accumulator>,
1805 partitions: &[Vec<Option<SqlValue>>],
1806 ) -> SqlValue {
1807 let mut acc = make_accumulator();
1808 for partition in partitions {
1809 apply_values(acc.as_mut(), partition);
1810 }
1811 acc.finalize().unwrap()
1812 }
1813
1814 fn merged_result(
1815 make_partial: impl Fn() -> Box<dyn Accumulator>,
1816 make_final: impl Fn() -> Box<dyn Accumulator>,
1817 partitions: &[Vec<Option<SqlValue>>],
1818 merge_order: &[usize],
1819 ) -> SqlValue {
1820 let states = partitions
1821 .iter()
1822 .map(|partition| {
1823 let mut acc = make_partial();
1824 apply_values(acc.as_mut(), partition);
1825 acc.state().unwrap()
1826 })
1827 .collect::<Vec<_>>();
1828
1829 let mut final_acc = make_final();
1830 for idx in merge_order {
1831 final_acc.merge(&states[*idx]).unwrap();
1832 }
1833 final_acc.finalize().unwrap()
1834 }
1835
1836 fn assert_single_equals_merged(
1837 make_accumulator: impl Fn() -> Box<dyn Accumulator> + Copy,
1838 partitions: Vec<Vec<Option<SqlValue>>>,
1839 ) {
1840 let merge_order = (0..partitions.len()).collect::<Vec<_>>();
1841 let single = single_result(make_accumulator, &partitions);
1842 let merged = merged_result(
1843 make_accumulator,
1844 make_accumulator,
1845 &partitions,
1846 &merge_order,
1847 );
1848 assert_eq!(single, merged);
1849 }
1850
1851 fn assert_merge_order_invariant(
1852 make_accumulator: impl Fn() -> Box<dyn Accumulator> + Copy,
1853 partitions: Vec<Vec<Option<SqlValue>>>,
1854 merge_orders: &[Vec<usize>],
1855 ) {
1856 let single = single_result(make_accumulator, &partitions);
1857 for order in merge_orders {
1858 let merged = merged_result(make_accumulator, make_accumulator, &partitions, order);
1859 assert_eq!(single, merged, "merge order {order:?}");
1860 }
1861 }
1862
1863 fn column_expr(index: usize, name: &str, resolved_type: ResolvedType) -> TypedExpr {
1864 TypedExpr {
1865 kind: TypedExprKind::ColumnRef {
1866 table: "t".into(),
1867 column: name.into(),
1868 column_index: index,
1869 },
1870 resolved_type,
1871 span: Span::default(),
1872 }
1873 }
1874
1875 fn sample_aggregate_schema() -> Vec<ColumnMetadata> {
1876 vec![
1877 ColumnMetadata::new("category", ResolvedType::Text),
1878 ColumnMetadata::new("price", ResolvedType::Double),
1879 ColumnMetadata::new("label", ResolvedType::Text),
1880 ]
1881 }
1882
1883 fn sample_aggregate_rows() -> Vec<Row> {
1884 vec![
1885 Row::new(
1886 0,
1887 vec![
1888 SqlValue::Text("book".into()),
1889 SqlValue::Double(10.0),
1890 SqlValue::Text("a".into()),
1891 ],
1892 ),
1893 Row::new(
1894 1,
1895 vec![
1896 SqlValue::Text("book".into()),
1897 SqlValue::Double(15.0),
1898 SqlValue::Text("b".into()),
1899 ],
1900 ),
1901 Row::new(
1902 2,
1903 vec![
1904 SqlValue::Text("game".into()),
1905 SqlValue::Double(20.0),
1906 SqlValue::Text("c".into()),
1907 ],
1908 ),
1909 Row::new(
1910 3,
1911 vec![
1912 SqlValue::Text("book".into()),
1913 SqlValue::Null,
1914 SqlValue::Text("a".into()),
1915 ],
1916 ),
1917 Row::new(
1918 4,
1919 vec![
1920 SqlValue::Text("toy".into()),
1921 SqlValue::Double(3.0),
1922 SqlValue::Text("d".into()),
1923 ],
1924 ),
1925 ]
1926 }
1927
1928 fn sample_aggregates() -> Vec<AggregateExpr> {
1929 let price = column_expr(1, "price", ResolvedType::Double);
1930 let label = column_expr(2, "label", ResolvedType::Text);
1931 vec![
1932 AggregateExpr::count_star(),
1933 AggregateExpr::sum(price.clone()),
1934 AggregateExpr::avg(price),
1935 AggregateExpr {
1936 function: AggregateFunction::GroupConcat {
1937 separator: Some("|".into()),
1938 },
1939 arg: Some(label),
1940 distinct: false,
1941 result_type: ResolvedType::Text,
1942 },
1943 ]
1944 }
1945
1946 fn collect_single_aggregate(
1947 group_keys: Vec<TypedExpr>,
1948 aggregates: Vec<AggregateExpr>,
1949 ) -> Vec<Vec<SqlValue>> {
1950 let input = VecIterator::new(sample_aggregate_rows(), sample_aggregate_schema());
1951 let schema = build_aggregate_schema(&group_keys, &aggregates);
1952 let mut iter =
1953 AggregateIterator::new(Box::new(input), group_keys, aggregates, None, schema);
1954 collect_iterator_rows(&mut iter)
1955 .unwrap()
1956 .into_iter()
1957 .map(|row| row.values)
1958 .collect()
1959 }
1960
1961 fn collect_parallel_aggregate(
1962 group_keys: Vec<TypedExpr>,
1963 aggregates: Vec<AggregateExpr>,
1964 parallelism: usize,
1965 ) -> Vec<Vec<SqlValue>> {
1966 let input = VecIterator::new(sample_aggregate_rows(), sample_aggregate_schema());
1967 let schema = build_aggregate_schema(&group_keys, &aggregates);
1968 execute_parallel_aggregate_rows(
1969 Box::new(input),
1970 group_keys,
1971 aggregates,
1972 None,
1973 schema,
1974 parallelism,
1975 )
1976 .unwrap()
1977 .into_iter()
1978 .map(|row| row.values)
1979 .collect()
1980 }
1981
1982 fn sort_rows(mut rows: Vec<Vec<SqlValue>>) -> Vec<Vec<SqlValue>> {
1983 rows.sort_by(|left, right| format!("{left:?}").cmp(&format!("{right:?}")));
1984 rows
1985 }
1986
1987 #[test]
1988 fn partial_schema_uses_group_keys_and_state_columns() {
1989 let category = column_expr(0, "category", ResolvedType::Text);
1990 let price = column_expr(1, "price", ResolvedType::Double);
1991 let aggregates = vec![AggregateExpr::count_star(), AggregateExpr::avg(price)];
1992
1993 let schema = build_partial_aggregate_schema(&[category], &aggregates);
1994 let names = schema
1995 .iter()
1996 .map(|column| column.name.as_str())
1997 .collect::<Vec<_>>();
1998 assert_eq!(
1999 names,
2000 vec![
2001 "category",
2002 "__agg0_state0",
2003 "__agg1_state0",
2004 "__agg1_state1"
2005 ]
2006 );
2007 assert_eq!(schema[1].data_type, ResolvedType::BigInt);
2008 assert_eq!(schema[2].data_type, ResolvedType::Double);
2009 assert_eq!(schema[3].data_type, ResolvedType::BigInt);
2010 }
2011
2012 #[test]
2013 fn parallel_aggregate_matches_single_with_group_by() {
2014 let group_keys = vec![column_expr(0, "category", ResolvedType::Text)];
2015 let aggregates = sample_aggregates();
2016
2017 let single = sort_rows(collect_single_aggregate(
2018 group_keys.clone(),
2019 aggregates.clone(),
2020 ));
2021 let parallel = sort_rows(collect_parallel_aggregate(group_keys, aggregates, 3));
2022
2023 assert_eq!(parallel, single);
2024 }
2025
2026 #[test]
2027 fn parallel_aggregate_matches_single_without_group_by() {
2028 let aggregates = sample_aggregates();
2029
2030 let single = collect_single_aggregate(Vec::new(), aggregates.clone());
2031 let parallel = collect_parallel_aggregate(Vec::new(), aggregates, 4);
2032
2033 assert_eq!(parallel, single);
2034 }
2035
2036 #[test]
2037 fn distinct_aggregates_force_single_parallel_mode() {
2038 let price = column_expr(1, "price", ResolvedType::Double);
2039 let aggregates = vec![AggregateExpr {
2040 distinct: true,
2041 ..AggregateExpr::sum(price)
2042 }];
2043
2044 assert!(should_use_single_for_parallel(4, &aggregates));
2045 assert!(should_use_single_for_parallel(1, &sample_aggregates()));
2046 assert!(!should_use_single_for_parallel(2, &sample_aggregates()));
2047 }
2048
2049 #[test]
2050 fn parallel_group_counter_exhaustion_falls_back_to_single() {
2051 let schema = vec![ColumnMetadata::new("category", ResolvedType::Text)];
2052 let rows = vec![
2053 Row::new(0, vec![SqlValue::Text("a".into())]),
2054 Row::new(1, vec![SqlValue::Text("b".into())]),
2055 Row::new(2, vec![SqlValue::Text("a".into())]),
2056 Row::new(3, vec![SqlValue::Text("b".into())]),
2057 ];
2058 let group_keys = vec![column_expr(0, "category", ResolvedType::Text)];
2059 let aggregates = vec![AggregateExpr::count_star()];
2060 let final_schema = build_aggregate_schema(&group_keys, &aggregates);
2061
2062 let single_values = {
2063 let input = VecIterator::new(rows.clone(), schema.clone());
2064 execute_single_aggregate_rows(
2065 Box::new(input),
2066 group_keys.clone(),
2067 aggregates.clone(),
2068 None,
2069 final_schema.clone(),
2070 None,
2071 2,
2072 )
2073 .unwrap()
2074 .into_iter()
2075 .map(|row| row.values)
2076 .collect::<Vec<_>>()
2077 };
2078
2079 let parallel_values = execute_parallel_aggregate_rows_with_policy(
2080 Box::new(VecIterator::new(rows, schema)),
2081 group_keys,
2082 aggregates,
2083 None,
2084 final_schema,
2085 2,
2086 None,
2087 2,
2088 )
2089 .unwrap()
2090 .into_iter()
2091 .map(|row| row.values)
2092 .collect::<Vec<_>>();
2093
2094 assert_eq!(sort_rows(parallel_values), sort_rows(single_values));
2095 }
2096
2097 #[test]
2098 fn materialize_limit_exhaustion_falls_back_to_streaming_single() {
2099 let schema = vec![ColumnMetadata::new("payload", ResolvedType::Text)];
2100 let rows = (0..4)
2101 .map(|idx| Row::new(idx, vec![SqlValue::Text("x".repeat(40))]))
2102 .collect::<Vec<_>>();
2103 let aggregates = vec![AggregateExpr::count_star()];
2104 let final_schema = build_aggregate_schema(&[], &aggregates);
2105 let policy = MemoryPolicy::new(Some(100), SpillPolicy::FailFast);
2106
2107 let result = execute_parallel_aggregate_rows_with_policy(
2108 Box::new(VecIterator::new(rows, schema)),
2109 Vec::new(),
2110 aggregates,
2111 None,
2112 final_schema,
2113 4,
2114 Some(policy),
2115 DEFAULT_GROUP_LIMIT,
2116 )
2117 .unwrap();
2118
2119 assert_eq!(result.len(), 1);
2120 assert_eq!(result[0].values, vec![SqlValue::BigInt(4)]);
2121 }
2122
2123 #[test]
2124 fn streaming_aggregate_respects_distinct_accumulators() {
2125 let schema = sample_aggregate_schema();
2126 let rows = vec![
2127 Row::new(
2128 0,
2129 vec![
2130 SqlValue::Text("book".into()),
2131 SqlValue::Double(10.0),
2132 SqlValue::Text("a".into()),
2133 ],
2134 ),
2135 Row::new(
2136 1,
2137 vec![
2138 SqlValue::Text("book".into()),
2139 SqlValue::Double(10.0),
2140 SqlValue::Text("a".into()),
2141 ],
2142 ),
2143 Row::new(
2144 2,
2145 vec![
2146 SqlValue::Text("book".into()),
2147 SqlValue::Double(15.0),
2148 SqlValue::Text("b".into()),
2149 ],
2150 ),
2151 ];
2152 let group_keys = vec![column_expr(0, "category", ResolvedType::Text)];
2153 let price = column_expr(1, "price", ResolvedType::Double);
2154 let label = column_expr(2, "label", ResolvedType::Text);
2155 let aggregates = vec![
2156 AggregateExpr {
2157 distinct: true,
2158 ..AggregateExpr::sum(price)
2159 },
2160 AggregateExpr {
2161 function: AggregateFunction::GroupConcat {
2162 separator: Some("|".into()),
2163 },
2164 arg: Some(label),
2165 distinct: true,
2166 result_type: ResolvedType::Text,
2167 },
2168 ];
2169 let output_schema = build_aggregate_schema(&group_keys, &aggregates);
2170 let input = VecIterator::new(rows, schema);
2171 let mut iter = StreamingAggregateIterator::new(
2172 Box::new(input),
2173 group_keys,
2174 aggregates,
2175 None,
2176 output_schema,
2177 );
2178 let rows = collect_iterator_rows(&mut iter).unwrap();
2179
2180 assert_eq!(rows.len(), 1);
2181 assert_eq!(rows[0].values[1], SqlValue::Double(25.0));
2182 assert_eq!(rows[0].values[2], SqlValue::Text("a|b".into()));
2183 }
2184
2185 #[test]
2186 fn partial_state_matches_single_for_count_sum_total_avg_min_max() {
2187 assert_single_equals_merged(
2188 || Box::new(CountAccumulator::new(false)),
2189 vec![
2190 vec![
2191 Some(SqlValue::Integer(1)),
2192 Some(SqlValue::BigInt(2)),
2193 Some(SqlValue::Text("x".into())),
2194 Some(SqlValue::Null),
2195 ],
2196 vec![Some(SqlValue::Integer(3))],
2197 ],
2198 );
2199 assert_single_equals_merged(
2200 || Box::new(SumAccumulator::new()),
2201 vec![
2202 vec![
2203 Some(SqlValue::Integer(1)),
2204 Some(SqlValue::BigInt(2)),
2205 Some(SqlValue::Float(3.5)),
2206 ],
2207 vec![Some(SqlValue::Double(4.5)), Some(SqlValue::Null)],
2208 ],
2209 );
2210 assert_single_equals_merged(
2211 || Box::new(TotalAccumulator::new()),
2212 vec![
2213 vec![Some(SqlValue::Integer(1)), Some(SqlValue::Null)],
2214 vec![Some(SqlValue::Double(2.5))],
2215 ],
2216 );
2217 assert_single_equals_merged(
2218 || Box::new(AvgAccumulator::new()),
2219 vec![
2220 vec![Some(SqlValue::Integer(2)), Some(SqlValue::Double(4.0))],
2221 vec![Some(SqlValue::Null), Some(SqlValue::Double(6.0))],
2222 ],
2223 );
2224 assert_single_equals_merged(
2225 || Box::new(MinMaxAccumulator::new(true)),
2226 vec![
2227 vec![Some(SqlValue::Integer(3)), Some(SqlValue::Integer(1))],
2228 vec![Some(SqlValue::Integer(2)), Some(SqlValue::Null)],
2229 ],
2230 );
2231 assert_single_equals_merged(
2232 || Box::new(MinMaxAccumulator::new(false)),
2233 vec![
2234 vec![
2235 Some(SqlValue::Text("b".into())),
2236 Some(SqlValue::Text("a".into())),
2237 ],
2238 vec![Some(SqlValue::Text("c".into())), Some(SqlValue::Null)],
2239 ],
2240 );
2241 }
2242
2243 #[test]
2244 fn partial_state_matches_single_for_ordered_string_aggregates() {
2245 assert_single_equals_merged(
2246 || Box::new(GroupConcatAccumulator::new("|".into())),
2247 vec![
2248 vec![Some(SqlValue::Text("a".into())), Some(SqlValue::Null)],
2249 vec![
2250 Some(SqlValue::Text("b".into())),
2251 Some(SqlValue::Text("c".into())),
2252 ],
2253 ],
2254 );
2255 assert_single_equals_merged(
2256 || Box::new(StringAggAccumulator::new("::".into())),
2257 vec![
2258 vec![Some(SqlValue::Text("a".into()))],
2259 vec![Some(SqlValue::Text("b".into())), Some(SqlValue::Null)],
2260 ],
2261 );
2262 }
2263
2264 #[test]
2265 fn partial_state_handles_empty_all_null_single_and_mixed_boundaries() {
2266 assert_single_equals_merged(
2267 || Box::new(CountAccumulator::new(false)),
2268 vec![vec![], vec![]],
2269 );
2270 assert_single_equals_merged(|| Box::new(SumAccumulator::new()), vec![vec![], vec![]]);
2271 assert_single_equals_merged(|| Box::new(TotalAccumulator::new()), vec![vec![], vec![]]);
2272 assert_single_equals_merged(|| Box::new(AvgAccumulator::new()), vec![vec![], vec![]]);
2273 assert_single_equals_merged(
2274 || Box::new(MinMaxAccumulator::new(true)),
2275 vec![vec![], vec![]],
2276 );
2277 assert_single_equals_merged(
2278 || Box::new(MinMaxAccumulator::new(false)),
2279 vec![vec![], vec![]],
2280 );
2281 assert_single_equals_merged(
2282 || Box::new(GroupConcatAccumulator::new(",".into())),
2283 vec![vec![], vec![]],
2284 );
2285 assert_single_equals_merged(
2286 || Box::new(StringAggAccumulator::new(",".into())),
2287 vec![vec![], vec![]],
2288 );
2289 assert_single_equals_merged(
2290 || Box::new(CountAccumulator::new(false)),
2291 vec![vec![Some(SqlValue::Null)], vec![Some(SqlValue::Null)]],
2292 );
2293 assert_single_equals_merged(
2294 || Box::new(SumAccumulator::new()),
2295 vec![vec![Some(SqlValue::Null)], vec![Some(SqlValue::Null)]],
2296 );
2297 assert_single_equals_merged(
2298 || Box::new(TotalAccumulator::new()),
2299 vec![vec![Some(SqlValue::Null)], vec![Some(SqlValue::Null)]],
2300 );
2301 assert_single_equals_merged(
2302 || Box::new(AvgAccumulator::new()),
2303 vec![vec![Some(SqlValue::Null)], vec![Some(SqlValue::Null)]],
2304 );
2305 assert_single_equals_merged(
2306 || Box::new(MinMaxAccumulator::new(true)),
2307 vec![vec![Some(SqlValue::Null)], vec![Some(SqlValue::Null)]],
2308 );
2309 assert_single_equals_merged(
2310 || Box::new(MinMaxAccumulator::new(false)),
2311 vec![vec![Some(SqlValue::Null)], vec![Some(SqlValue::Null)]],
2312 );
2313 assert_single_equals_merged(
2314 || Box::new(GroupConcatAccumulator::new(",".into())),
2315 vec![vec![Some(SqlValue::Null)], vec![Some(SqlValue::Null)]],
2316 );
2317 assert_single_equals_merged(
2318 || Box::new(StringAggAccumulator::new(",".into())),
2319 vec![vec![Some(SqlValue::Null)], vec![Some(SqlValue::Null)]],
2320 );
2321 assert_single_equals_merged(
2322 || Box::new(SumAccumulator::new()),
2323 vec![vec![Some(SqlValue::Integer(7))]],
2324 );
2325 assert_single_equals_merged(
2326 || Box::new(AvgAccumulator::new()),
2327 vec![
2328 vec![Some(SqlValue::Null)],
2329 vec![Some(SqlValue::Double(8.0))],
2330 ],
2331 );
2332 }
2333
2334 #[test]
2335 fn commutative_accumulators_are_merge_order_invariant() {
2336 let orders = vec![
2337 vec![1, 3, 0, 2],
2338 vec![3, 2, 1, 0],
2339 vec![0, 1, 2, 3],
2340 vec![2, 0, 3, 1],
2341 ];
2342 assert_merge_order_invariant(
2343 || Box::new(CountAccumulator::new(false)),
2344 vec![vec![Some(SqlValue::Integer(1))]],
2345 &[vec![0]],
2346 );
2347 assert_merge_order_invariant(
2348 || Box::new(SumAccumulator::new()),
2349 vec![
2350 vec![Some(SqlValue::Integer(1))],
2351 vec![Some(SqlValue::Integer(2))],
2352 ],
2353 &[vec![0, 1], vec![1, 0]],
2354 );
2355 assert_merge_order_invariant(
2356 || Box::new(AvgAccumulator::new()),
2357 vec![
2358 vec![Some(SqlValue::Integer(1))],
2359 vec![Some(SqlValue::Integer(2))],
2360 vec![Some(SqlValue::Integer(3))],
2361 ],
2362 &[vec![0, 1, 2], vec![2, 1, 0]],
2363 );
2364 let numeric_partitions = vec![
2365 vec![Some(SqlValue::Integer(1)), Some(SqlValue::Null)],
2366 vec![Some(SqlValue::BigInt(2))],
2367 vec![],
2368 vec![Some(SqlValue::Double(3.0))],
2369 ];
2370 assert_merge_order_invariant(
2371 || Box::new(CountAccumulator::new(false)),
2372 numeric_partitions.clone(),
2373 &orders,
2374 );
2375 assert_merge_order_invariant(
2376 || Box::new(SumAccumulator::new()),
2377 numeric_partitions.clone(),
2378 &orders,
2379 );
2380 assert_merge_order_invariant(
2381 || Box::new(TotalAccumulator::new()),
2382 numeric_partitions.clone(),
2383 &orders,
2384 );
2385 assert_merge_order_invariant(
2386 || Box::new(AvgAccumulator::new()),
2387 numeric_partitions.clone(),
2388 &orders,
2389 );
2390 let integer_partitions = vec![
2391 vec![Some(SqlValue::Integer(3)), Some(SqlValue::Null)],
2392 vec![Some(SqlValue::Integer(1))],
2393 vec![],
2394 vec![Some(SqlValue::Integer(2))],
2395 ];
2396 assert_merge_order_invariant(
2397 || Box::new(MinMaxAccumulator::new(true)),
2398 integer_partitions.clone(),
2399 &orders,
2400 );
2401 assert_merge_order_invariant(
2402 || Box::new(MinMaxAccumulator::new(false)),
2403 integer_partitions,
2404 &orders,
2405 );
2406 }
2407
2408 #[test]
2409 fn avg_partial_state_uses_sum_count_and_never_divides_by_zero_during_merge() {
2410 let empty = {
2411 let acc = AvgAccumulator::new();
2412 acc.state().unwrap()
2413 };
2414 assert_eq!(empty, vec![SqlValue::Double(0.0), SqlValue::BigInt(0)]);
2415
2416 let mut partial = AvgAccumulator::new();
2417 partial.update(Some(SqlValue::Integer(2))).unwrap();
2418 partial.update(Some(SqlValue::Double(4.0))).unwrap();
2419 assert_eq!(
2420 partial.state().unwrap(),
2421 vec![SqlValue::Double(6.0), SqlValue::BigInt(2)]
2422 );
2423
2424 let mut final_acc = AvgAccumulator::new();
2425 final_acc.merge(&empty).unwrap();
2426 assert_eq!(final_acc.finalize().unwrap(), SqlValue::Null);
2427 final_acc.merge(&partial.state().unwrap()).unwrap();
2428 assert_eq!(final_acc.finalize().unwrap(), SqlValue::Double(3.0));
2429 }
2430
2431 #[test]
2432 fn merge_rejects_invalid_state_contracts_without_panicking() {
2433 let mut count = CountAccumulator::new(false);
2434 assert!(count.merge(&[]).is_err());
2435 assert!(count.merge(&[SqlValue::Text("bad".into())]).is_err());
2436
2437 let mut avg = AvgAccumulator::new();
2438 assert!(avg.merge(&[SqlValue::Double(1.0)]).is_err());
2439 assert!(
2440 avg.merge(&[SqlValue::Double(1.0), SqlValue::Text("bad".into())])
2441 .is_err()
2442 );
2443
2444 let mut concat = GroupConcatAccumulator::new("|".into());
2445 assert!(
2446 concat
2447 .merge(&[SqlValue::Text("a".into()), SqlValue::Text(",".into())])
2448 .is_err()
2449 );
2450 }
2451
2452 #[test]
2453 fn count_accumulator_counts_rows_and_skips_nulls() {
2454 let mut acc = CountAccumulator::new(false);
2455 acc.update(None).unwrap();
2456 acc.update(Some(SqlValue::Null)).unwrap();
2457 acc.update(Some(SqlValue::Integer(1))).unwrap();
2458 assert_eq!(acc.finalize().unwrap(), SqlValue::BigInt(2));
2459 }
2460
2461 #[test]
2462 fn count_accumulator_distinct_deduplicates() {
2463 let mut acc = CountAccumulator::new(true);
2464 acc.update(Some(SqlValue::Integer(1))).unwrap();
2465 acc.update(Some(SqlValue::Integer(1))).unwrap();
2466 acc.update(Some(SqlValue::Integer(2))).unwrap();
2467 assert_eq!(acc.finalize().unwrap(), SqlValue::BigInt(2));
2468 }
2469
2470 #[test]
2471 fn count_distinct_uses_group_key_equality_boundaries() {
2472 let mut acc = CountAccumulator::new(true);
2473 let nan_a = f64::from_bits(0x7ff8_0000_0000_0001);
2474 let nan_b = f64::from_bits(0x7ff8_0000_0000_0002);
2475 for value in [
2476 SqlValue::Null,
2477 SqlValue::Null,
2478 SqlValue::Integer(1),
2479 SqlValue::Integer(1),
2480 SqlValue::Double(1.0),
2481 SqlValue::Double(-0.0),
2482 SqlValue::Double(0.0),
2483 SqlValue::Double(nan_a),
2484 SqlValue::Double(nan_a),
2485 SqlValue::Double(nan_b),
2486 SqlValue::Text("same".into()),
2487 SqlValue::Text("same".into()),
2488 SqlValue::Blob(vec![1, 2]),
2489 SqlValue::Blob(vec![1, 2]),
2490 SqlValue::Blob(vec![1, 3]),
2491 ] {
2492 acc.update(Some(value)).unwrap();
2493 }
2494 assert_eq!(acc.finalize().unwrap(), SqlValue::BigInt(9));
2495 }
2496
2497 #[test]
2498 fn distinct_non_count_accumulators_deduplicate_non_null_values() {
2499 let mut sum = SumAccumulator::with_distinct(true);
2500 for value in [
2501 SqlValue::Integer(1),
2502 SqlValue::Integer(1),
2503 SqlValue::Double(1.0),
2504 SqlValue::Integer(2),
2505 SqlValue::Null,
2506 ] {
2507 sum.update(Some(value)).unwrap();
2508 }
2509 assert_eq!(sum.finalize().unwrap(), SqlValue::Double(4.0));
2510
2511 let mut avg = AvgAccumulator::with_distinct(true);
2512 for value in [
2513 SqlValue::Integer(1),
2514 SqlValue::Integer(1),
2515 SqlValue::Integer(3),
2516 SqlValue::Null,
2517 ] {
2518 avg.update(Some(value)).unwrap();
2519 }
2520 assert_eq!(avg.finalize().unwrap(), SqlValue::Double(2.0));
2521
2522 let mut min = MinMaxAccumulator::with_distinct(true, true);
2523 let mut max = MinMaxAccumulator::with_distinct(false, true);
2524 for value in [
2525 SqlValue::Text("b".into()),
2526 SqlValue::Text("a".into()),
2527 SqlValue::Text("a".into()),
2528 SqlValue::Text("c".into()),
2529 ] {
2530 min.update(Some(value.clone())).unwrap();
2531 max.update(Some(value)).unwrap();
2532 }
2533 assert_eq!(min.finalize().unwrap(), SqlValue::Text("a".into()));
2534 assert_eq!(max.finalize().unwrap(), SqlValue::Text("c".into()));
2535
2536 let mut group_concat = GroupConcatAccumulator::with_distinct("|".into(), true);
2537 let mut string_agg = StringAggAccumulator::with_distinct(";".into(), true);
2538 for value in [
2539 SqlValue::Text("a".into()),
2540 SqlValue::Text("a".into()),
2541 SqlValue::Null,
2542 SqlValue::Text("b".into()),
2543 ] {
2544 group_concat.update(Some(value.clone())).unwrap();
2545 string_agg.update(Some(value)).unwrap();
2546 }
2547 assert_eq!(
2548 group_concat.finalize().unwrap(),
2549 SqlValue::Text("a|b".into())
2550 );
2551 assert_eq!(string_agg.finalize().unwrap(), SqlValue::Text("a;b".into()));
2552 }
2553
2554 #[test]
2555 fn sum_accumulator_aggregates_numeric_values() {
2556 let mut acc = SumAccumulator::new();
2557 acc.update(Some(SqlValue::Integer(2))).unwrap();
2558 acc.update(Some(SqlValue::Double(3.5))).unwrap();
2559 acc.update(Some(SqlValue::Null)).unwrap();
2560 assert_eq!(acc.finalize().unwrap(), SqlValue::Double(5.5));
2561 }
2562
2563 #[test]
2564 fn total_accumulator_returns_zero_for_empty() {
2565 let acc = TotalAccumulator::new();
2566 assert_eq!(acc.finalize().unwrap(), SqlValue::Double(0.0));
2567 }
2568
2569 #[test]
2570 fn total_accumulator_aggregates_numeric_values() {
2571 let mut acc = TotalAccumulator::new();
2572 acc.update(Some(SqlValue::Integer(2))).unwrap();
2573 acc.update(Some(SqlValue::Null)).unwrap();
2574 acc.update(Some(SqlValue::Double(1.5))).unwrap();
2575 assert_eq!(acc.finalize().unwrap(), SqlValue::Double(3.5));
2576 }
2577
2578 #[test]
2579 fn avg_accumulator_handles_empty_and_nulls() {
2580 let mut acc = AvgAccumulator::new();
2581 assert_eq!(acc.finalize().unwrap(), SqlValue::Null);
2582 acc.update(Some(SqlValue::Null)).unwrap();
2583 acc.update(Some(SqlValue::BigInt(4))).unwrap();
2584 acc.update(Some(SqlValue::Integer(2))).unwrap();
2585 assert_eq!(acc.finalize().unwrap(), SqlValue::Double(3.0));
2586 }
2587
2588 #[test]
2589 fn min_max_accumulator_tracks_extremes() {
2590 let mut min_acc = MinMaxAccumulator::new(true);
2591 let mut max_acc = MinMaxAccumulator::new(false);
2592 for value in [3, 1, 2] {
2593 min_acc.update(Some(SqlValue::Integer(value))).unwrap();
2594 max_acc.update(Some(SqlValue::Integer(value))).unwrap();
2595 }
2596 assert_eq!(min_acc.finalize().unwrap(), SqlValue::Integer(1));
2597 assert_eq!(max_acc.finalize().unwrap(), SqlValue::Integer(3));
2598 }
2599
2600 #[test]
2601 fn min_max_accumulator_rejects_type_mismatch() {
2602 let mut acc = MinMaxAccumulator::new(true);
2603 acc.update(Some(SqlValue::Integer(1))).unwrap();
2604 let err = acc.update(Some(SqlValue::Text("bad".into()))).unwrap_err();
2605 match err {
2606 ExecutorError::Evaluation(crate::executor::EvaluationError::TypeMismatch {
2607 ..
2608 }) => {}
2609 other => panic!("unexpected error {:?}", other),
2610 }
2611 }
2612
2613 #[test]
2614 fn group_concat_accumulator_joins_values() {
2615 let mut acc = GroupConcatAccumulator::new("|".into());
2616 acc.update(Some(SqlValue::Text("a".into()))).unwrap();
2617 acc.update(Some(SqlValue::Null)).unwrap();
2618 acc.update(Some(SqlValue::Text("b".into()))).unwrap();
2619 assert_eq!(acc.finalize().unwrap(), SqlValue::Text("a|b".into()));
2620 }
2621
2622 #[test]
2623 fn group_concat_accumulator_empty_returns_null() {
2624 let acc = GroupConcatAccumulator::new(",".into());
2625 assert_eq!(acc.finalize().unwrap(), SqlValue::Null);
2626 }
2627
2628 #[test]
2629 fn string_agg_accumulator_joins_values() {
2630 let mut acc = StringAggAccumulator::new("::".into());
2631 acc.update(Some(SqlValue::Text("a".into()))).unwrap();
2632 acc.update(Some(SqlValue::Null)).unwrap();
2633 acc.update(Some(SqlValue::Text("b".into()))).unwrap();
2634 assert_eq!(acc.finalize().unwrap(), SqlValue::Text("a::b".into()));
2635 }
2636
2637 #[test]
2638 fn string_agg_accumulator_empty_returns_null() {
2639 let acc = StringAggAccumulator::new(",".into());
2640 assert_eq!(acc.finalize().unwrap(), SqlValue::Null);
2641 }
2642
2643 #[test]
2644 fn encode_group_key_is_deterministic() {
2645 let values = vec![
2646 SqlValue::Integer(1),
2647 SqlValue::Text("a".into()),
2648 SqlValue::Null,
2649 ];
2650 let first = encode_group_key(&values).unwrap();
2651 let second = encode_group_key(&values).unwrap();
2652 assert_eq!(first, second);
2653 }
2654}