Skip to main content

alopex_sql/executor/query/
aggregate.rs

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
18/// Byte-encoded group key for hash-based aggregation.
19pub 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
81/// Encode group key values into a deterministic byte sequence.
82pub 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
90/// Accumulator interface for aggregate function execution.
91pub trait Accumulator: Send {
92    /// Update the accumulator with a new value (None for COUNT(*) rows).
93    fn update(&mut self, value: Option<SqlValue>) -> Result<()>;
94    /// Return the serializable partial aggregate state.
95    fn state(&self) -> Result<Vec<SqlValue>>;
96    /// Merge a partial state produced by an accumulator of the same function.
97    fn merge(&mut self, state: &[SqlValue]) -> Result<()>;
98    /// Finalize the accumulator and return the resulting SqlValue.
99    fn finalize(&self) -> Result<SqlValue>;
100    /// Clone the accumulator as a trait object.
101    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/// Accumulator for COUNT / COUNT(DISTINCT).
182#[derive(Debug, Clone)]
183pub struct CountAccumulator {
184    count: usize,
185    distinct_values: Option<HashSet<Vec<u8>>>,
186}
187
188impl CountAccumulator {
189    /// Create a new count accumulator.
190    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/// Accumulator for SUM.
252#[derive(Debug, Clone)]
253pub struct SumAccumulator {
254    sum: Option<f64>,
255    distinct_values: Option<HashSet<Vec<u8>>>,
256}
257
258impl SumAccumulator {
259    /// Create a new sum accumulator.
260    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/// Accumulator for TOTAL (SUM that returns 0.0 on empty/all-NULL input).
318#[derive(Debug, Clone)]
319pub struct TotalAccumulator {
320    sum: Option<f64>,
321}
322
323impl TotalAccumulator {
324    /// Create a new total accumulator.
325    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/// Accumulator for AVG.
370#[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    /// Create a new average accumulator.
379    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/// Accumulator for MIN / MAX.
466#[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    /// Create a new min/max accumulator.
475    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/// Accumulator for GROUP_CONCAT.
553#[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    /// Create a new GROUP_CONCAT accumulator with the given separator.
562    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/// Accumulator for STRING_AGG.
648#[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    /// Create a new string_agg accumulator.
657    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
742/// Create a new accumulator instance for the aggregate function.
743pub 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
762/// Returns whether an aggregate's partial state can be merged without
763/// changing the current local SQL result. Floating-point, DISTINCT, and
764/// order-sensitive aggregates must instead be replayed from ordered inputs.
765pub 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
773/// Merge only aggregate states whose merge rule is proven to preserve the
774/// current local SQL result. This is the coordinator-side kernel used by the
775/// distributed result assembler after every worker has acknowledged cleanup.
776pub 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/// Aggregate execution mode.
822#[derive(Debug, Clone, Copy, PartialEq, Eq)]
823pub enum AggregateMode {
824    /// Consume raw input rows and output aggregate partial state rows.
825    Partial,
826    /// Consume partial state rows and output final aggregate values.
827    Final,
828    /// Consume raw input rows and output final aggregate values in one pass.
829    Single,
830}
831
832struct AggregateGroup {
833    key_values: Vec<SqlValue>,
834    accumulators: Vec<Box<dyn Accumulator>>,
835}
836
837/// Iterator that performs hash-based aggregation over input rows.
838pub 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    /// Create a new aggregate iterator with the default group limit.
855    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    /// Override the maximum number of groups allowed during aggregation.
879    pub fn with_group_limit(mut self, limit: usize) -> Self {
880        self.group_limit = limit;
881        self
882    }
883
884    /// Set the aggregate execution mode.
885    pub fn with_mode(mut self, mode: AggregateMode) -> Self {
886        self.mode = mode;
887        self
888    }
889
890    /// Attach a memory policy for enforcing in-flight aggregation limits.
891    pub fn with_memory_policy(mut self, policy: Option<MemoryPolicy>) -> Self {
892        self.memory_tracker = policy.map(MemoryTracker::new);
893        self
894    }
895
896    /// Attach a shared group counter used by parallel Partial aggregation.
897    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
1128/// Iterator that performs streaming aggregation over sorted input.
1129pub 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(&current_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(&current_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
1307/// Build output schema for aggregate results.
1308pub 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
1336/// Build internal partial aggregate schema.
1337pub 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
1360/// Return true when aggregate execution must remain Single for correctness.
1361pub 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
1610/// Execute a deterministic single-process parallel partial-to-final aggregate.
1611pub 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/// Execute a deterministic parallel aggregate with memory fallback controls.
1632#[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}