Skip to main content

datafusion_functions_aggregate/
sum.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18//! Defines `SUM` and `SUM DISTINCT` aggregate accumulators
19
20use arrow::array::{Array, ArrayRef, ArrowNativeTypeOp, ArrowNumericType, AsArray};
21use arrow::datatypes::Field;
22use arrow::datatypes::{
23    ArrowNativeType, DECIMAL32_MAX_PRECISION, DECIMAL64_MAX_PRECISION,
24    DECIMAL128_MAX_PRECISION, DECIMAL256_MAX_PRECISION, DataType, Decimal32Type,
25    Decimal64Type, Decimal128Type, Decimal256Type, DurationMicrosecondType,
26    DurationMillisecondType, DurationNanosecondType, DurationSecondType, FieldRef,
27    Float64Type, Int64Type, IntervalDayTimeType, IntervalMonthDayNanoType, IntervalUnit,
28    IntervalYearMonthType, TimeUnit, UInt64Type,
29};
30use datafusion_common::hash_utils::RandomState;
31use datafusion_common::internal_err;
32use datafusion_common::stats::Precision;
33use datafusion_common::types::{
34    NativeType, logical_float64, logical_int8, logical_int16, logical_int32,
35    logical_int64, logical_uint8, logical_uint16, logical_uint32, logical_uint64,
36};
37use datafusion_common::{HashMap, Result, ScalarValue, exec_err, not_impl_err};
38use datafusion_expr::expr::AggregateFunction;
39use datafusion_expr::expr_fn::cast;
40use datafusion_expr::function::{AccumulatorArgs, StateFieldsArgs};
41use datafusion_expr::utils::{AggregateOrderSensitivity, format_state_name};
42use datafusion_expr::{
43    Accumulator, AggregateUDFImpl, Coercion, Documentation, Expr, GroupsAccumulator,
44    Operator, ReversedUDAF, SetMonotonicity, Signature, StatisticsArgs, TypeSignature,
45    TypeSignatureClass, Volatility,
46};
47use datafusion_functions_aggregate_common::aggregate::groups_accumulator::prim_op::PrimitiveGroupsAccumulator;
48use datafusion_functions_aggregate_common::aggregate::sum_distinct::DistinctSumAccumulator;
49use datafusion_macros::user_doc;
50use datafusion_physical_expr::expressions::{CastExpr, Column};
51use std::mem::{size_of, size_of_val};
52
53make_udaf_expr_and_func!(
54    Sum,
55    sum,
56    expression,
57    "Returns the sum of a group of values.",
58    sum_udaf
59);
60
61pub fn sum_distinct(expr: Expr) -> Expr {
62    Expr::AggregateFunction(AggregateFunction::new_udf(
63        sum_udaf(),
64        vec![expr],
65        true,
66        None,
67        vec![],
68        None,
69    ))
70}
71
72/// Sum only supports a subset of numeric types, instead relying on type coercion
73///
74/// This macro is similar to [downcast_primitive](arrow::array::downcast_primitive)
75///
76/// `args` is [AccumulatorArgs]
77/// `helper` is a macro accepting (ArrowPrimitiveType, DataType)
78macro_rules! downcast_sum {
79    ($args:ident, $helper:ident) => {
80        match $args.return_field.data_type().clone() {
81            DataType::UInt64 => {
82                $helper!(UInt64Type, $args.return_field.data_type().clone())
83            }
84            DataType::Int64 => {
85                $helper!(Int64Type, $args.return_field.data_type().clone())
86            }
87            DataType::Float64 => {
88                $helper!(Float64Type, $args.return_field.data_type().clone())
89            }
90            DataType::Decimal32(_, _) => {
91                $helper!(Decimal32Type, $args.return_field.data_type().clone())
92            }
93            DataType::Decimal64(_, _) => {
94                $helper!(Decimal64Type, $args.return_field.data_type().clone())
95            }
96            DataType::Decimal128(_, _) => {
97                $helper!(Decimal128Type, $args.return_field.data_type().clone())
98            }
99            DataType::Decimal256(_, _) => {
100                $helper!(Decimal256Type, $args.return_field.data_type().clone())
101            }
102            DataType::Duration(TimeUnit::Second) => {
103                $helper!(DurationSecondType, $args.return_field.data_type().clone())
104            }
105            DataType::Duration(TimeUnit::Millisecond) => {
106                $helper!(
107                    DurationMillisecondType,
108                    $args.return_field.data_type().clone()
109                )
110            }
111            DataType::Duration(TimeUnit::Microsecond) => {
112                $helper!(
113                    DurationMicrosecondType,
114                    $args.return_field.data_type().clone()
115                )
116            }
117            DataType::Duration(TimeUnit::Nanosecond) => {
118                $helper!(
119                    DurationNanosecondType,
120                    $args.return_field.data_type().clone()
121                )
122            }
123            DataType::Interval(IntervalUnit::YearMonth) => {
124                $helper!(
125                    IntervalYearMonthType,
126                    $args.return_field.data_type().clone()
127                )
128            }
129            DataType::Interval(IntervalUnit::DayTime) => {
130                $helper!(IntervalDayTimeType, $args.return_field.data_type().clone())
131            }
132            DataType::Interval(IntervalUnit::MonthDayNano) => {
133                $helper!(
134                    IntervalMonthDayNanoType,
135                    $args.return_field.data_type().clone()
136                )
137            }
138            _ => {
139                not_impl_err!(
140                    "Sum not supported for {}: {}",
141                    $args.name,
142                    $args.return_field.data_type()
143                )
144            }
145        }
146    };
147}
148
149#[user_doc(
150    doc_section(label = "General Functions"),
151    description = "Returns the sum of all values in the specified column.",
152    syntax_example = "sum(expression)",
153    sql_example = r#"```sql
154> SELECT sum(column_name) FROM table_name;
155+-----------------------+
156| sum(column_name)       |
157+-----------------------+
158| 12345                 |
159+-----------------------+
160```"#,
161    standard_argument(name = "expression",)
162)]
163#[derive(Debug, PartialEq, Eq, Hash)]
164pub struct Sum {
165    signature: Signature,
166}
167
168impl Sum {
169    pub fn new() -> Self {
170        Self {
171            // Refer to https://www.postgresql.org/docs/8.2/functions-aggregate.html doc
172            // smallint, int, bigint, real, double precision, decimal, or interval.
173            signature: Signature::one_of(
174                vec![
175                    TypeSignature::Coercible(vec![Coercion::new_exact(
176                        TypeSignatureClass::Decimal,
177                    )]),
178                    // Unsigned to u64
179                    TypeSignature::Coercible(vec![Coercion::new_implicit(
180                        TypeSignatureClass::Native(logical_uint64()),
181                        vec![
182                            TypeSignatureClass::Native(logical_uint8()),
183                            TypeSignatureClass::Native(logical_uint16()),
184                            TypeSignatureClass::Native(logical_uint32()),
185                        ],
186                        NativeType::UInt64,
187                    )]),
188                    // Signed to i64
189                    TypeSignature::Coercible(vec![Coercion::new_implicit(
190                        TypeSignatureClass::Native(logical_int64()),
191                        vec![
192                            TypeSignatureClass::Native(logical_int8()),
193                            TypeSignatureClass::Native(logical_int16()),
194                            TypeSignatureClass::Native(logical_int32()),
195                        ],
196                        NativeType::Int64,
197                    )]),
198                    // Floats to f64
199                    TypeSignature::Coercible(vec![Coercion::new_implicit(
200                        TypeSignatureClass::Native(logical_float64()),
201                        vec![TypeSignatureClass::Float],
202                        NativeType::Float64,
203                    )]),
204                    TypeSignature::Coercible(vec![Coercion::new_exact(
205                        TypeSignatureClass::Duration,
206                    )]),
207                    TypeSignature::Coercible(vec![Coercion::new_exact(
208                        TypeSignatureClass::Interval,
209                    )]),
210                ],
211                Volatility::Immutable,
212            ),
213        }
214    }
215}
216
217impl Default for Sum {
218    fn default() -> Self {
219        Self::new()
220    }
221}
222
223impl AggregateUDFImpl for Sum {
224    fn name(&self) -> &str {
225        "sum"
226    }
227
228    fn signature(&self) -> &Signature {
229        &self.signature
230    }
231
232    fn return_type(&self, arg_types: &[DataType]) -> Result<DataType> {
233        match &arg_types[0] {
234            DataType::Int64 => Ok(DataType::Int64),
235            DataType::UInt64 => Ok(DataType::UInt64),
236            DataType::Float64 => Ok(DataType::Float64),
237            // In the spark, the result type is DECIMAL(min(38,precision+10), s)
238            // ref: https://github.com/apache/spark/blob/fcf636d9eb8d645c24be3db2d599aba2d7e2955a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/aggregate/Sum.scala#L66
239            DataType::Decimal32(precision, scale) => {
240                let new_precision = DECIMAL32_MAX_PRECISION.min(*precision + 10);
241                Ok(DataType::Decimal32(new_precision, *scale))
242            }
243            DataType::Decimal64(precision, scale) => {
244                let new_precision = DECIMAL64_MAX_PRECISION.min(*precision + 10);
245                Ok(DataType::Decimal64(new_precision, *scale))
246            }
247            DataType::Decimal128(precision, scale) => {
248                let new_precision = DECIMAL128_MAX_PRECISION.min(*precision + 10);
249                Ok(DataType::Decimal128(new_precision, *scale))
250            }
251            DataType::Decimal256(precision, scale) => {
252                let new_precision = DECIMAL256_MAX_PRECISION.min(*precision + 10);
253                Ok(DataType::Decimal256(new_precision, *scale))
254            }
255            DataType::Duration(time_unit) => Ok(DataType::Duration(*time_unit)),
256            DataType::Interval(interval_unit) => Ok(DataType::Interval(*interval_unit)),
257            other => {
258                exec_err!("[return_type] SUM not supported for {}", other)
259            }
260        }
261    }
262
263    fn accumulator(&self, args: AccumulatorArgs) -> Result<Box<dyn Accumulator>> {
264        if args.is_distinct {
265            macro_rules! helper {
266                ($t:ty, $dt:expr) => {
267                    Ok(Box::new(DistinctSumAccumulator::<$t>::new(&$dt)))
268                };
269            }
270            downcast_sum!(args, helper)
271        } else {
272            macro_rules! helper {
273                ($t:ty, $dt:expr) => {
274                    Ok(Box::new(SumAccumulator::<$t>::new($dt.clone())))
275                };
276            }
277            downcast_sum!(args, helper)
278        }
279    }
280
281    fn state_fields(&self, args: StateFieldsArgs) -> Result<Vec<FieldRef>> {
282        if args.is_distinct {
283            Ok(vec![
284                Field::new_list(
285                    format_state_name(args.name, "sum distinct"),
286                    // See COMMENTS.md to understand why nullable is set to true
287                    Field::new_list_field(args.return_type().clone(), true),
288                    false,
289                )
290                .into(),
291            ])
292        } else {
293            Ok(vec![
294                Field::new(
295                    format_state_name(args.name, "sum"),
296                    args.return_type().clone(),
297                    true,
298                )
299                .into(),
300            ])
301        }
302    }
303
304    fn groups_accumulator_supported(&self, args: AccumulatorArgs) -> bool {
305        !args.is_distinct
306    }
307
308    fn create_groups_accumulator(
309        &self,
310        args: AccumulatorArgs,
311    ) -> Result<Box<dyn GroupsAccumulator>> {
312        macro_rules! helper {
313            ($t:ty, $dt:expr) => {
314                Ok(Box::new(PrimitiveGroupsAccumulator::<$t, _>::new(
315                    &$dt,
316                    |x, y| *x = x.add_wrapping(y),
317                )))
318            };
319        }
320        downcast_sum!(args, helper)
321    }
322
323    fn create_sliding_accumulator(
324        &self,
325        args: AccumulatorArgs,
326    ) -> Result<Box<dyn Accumulator>> {
327        if args.is_distinct {
328            // distinct path: [`SlidingDistinctSumAccumulator`] only implements
329            // Int64, so gate the supported type here rather than dispatching
330            // through `downcast_sum!`, which accepts every SUM type
331            match args.return_field.data_type() {
332                DataType::Int64 => Ok(Box::new(SlidingDistinctSumAccumulator::try_new(
333                    &DataType::Int64,
334                )?)),
335                _ => not_impl_err!(
336                    "SUM(DISTINCT) over sliding window frames is only supported for Int64, got {}",
337                    args.expr_fields[0].data_type()
338                ),
339            }
340        } else {
341            // non‐distinct path: existing sliding sum
342            macro_rules! helper {
343                ($t:ty, $dt:expr) => {
344                    Ok(Box::new(SlidingSumAccumulator::<$t>::new($dt.clone())))
345                };
346            }
347            downcast_sum!(args, helper)
348        }
349    }
350
351    fn reverse_expr(&self) -> ReversedUDAF {
352        ReversedUDAF::Identical
353    }
354
355    fn order_sensitivity(&self) -> AggregateOrderSensitivity {
356        AggregateOrderSensitivity::Insensitive
357    }
358
359    fn documentation(&self) -> Option<&Documentation> {
360        self.doc()
361    }
362
363    fn set_monotonicity(&self, data_type: &DataType) -> SetMonotonicity {
364        // `SUM` is only monotonically increasing when its input is unsigned.
365        // TODO: Expand these utilizing statistics.
366        match data_type {
367            DataType::UInt8 => SetMonotonicity::Increasing,
368            DataType::UInt16 => SetMonotonicity::Increasing,
369            DataType::UInt32 => SetMonotonicity::Increasing,
370            DataType::UInt64 => SetMonotonicity::Increasing,
371            _ => SetMonotonicity::NotMonotonic,
372        }
373    }
374
375    /// Implement ClickBench Q29 specific optimization:
376    /// `SUM(arg + constant)` --> `SUM(arg) + constant * COUNT(arg)`
377    ///
378    /// See background on [`AggregateUDFImpl::simplify_expr_op_literal`]
379    fn simplify_expr_op_literal(
380        &self,
381        agg_function: &AggregateFunction,
382        arg: &Expr,
383        op: Operator,
384        lit: &Expr,
385        // Only support '+' so the order of the args doesn't matter
386        _arg_is_left: bool,
387    ) -> Result<Option<Expr>> {
388        if op != Operator::Plus {
389            return Ok(None);
390        }
391
392        let lit_type = match &lit {
393            Expr::Literal(value, _) => value.data_type(),
394            _ => {
395                return internal_err!(
396                    "Sum::simplify_expr_op_literal got a non literal argument"
397                );
398            }
399        };
400        if lit_type == DataType::Null {
401            return Ok(None);
402        }
403
404        // Build up SUM(arg)
405        let mut sum_agg = agg_function.clone();
406        sum_agg.params.args = vec![arg.clone()];
407        let sum_agg = Expr::AggregateFunction(sum_agg);
408
409        // COUNT(arg) - cast to the correct type
410        let count_agg = cast(crate::count::count(arg.clone()), lit_type);
411
412        // SUM(arg) + lit * COUNT(arg)
413        Ok(Some(sum_agg + (lit.clone() * count_agg)))
414    }
415
416    fn value_from_stats(&self, statistics_args: &StatisticsArgs) -> Option<ScalarValue> {
417        if statistics_args.is_distinct {
418            return None;
419        }
420
421        let [expr] = statistics_args.exprs else {
422            return None;
423        };
424
425        let (col_expr, cast_type) = match expr.downcast_ref::<Column>() {
426            Some(col_expr) => (col_expr, None),
427            None => {
428                let cast_expr = expr.downcast_ref::<CastExpr>()?;
429                let col_expr = cast_expr.expr().downcast_ref::<Column>()?;
430                (col_expr, Some(cast_expr.cast_type()))
431            }
432        };
433
434        let col_stats = statistics_args
435            .statistics
436            .column_statistics
437            .get(col_expr.index())?;
438
439        // Replacing SUM with a literal is only valid for exact statistics.
440        // `cast_to_sum_type` also widens small integer stats to the SQL SUM
441        // return type, e.g. Int32 statistics become an Int64 SUM value.
442        let Precision::Exact(val) = col_stats.sum_value.cast_to_sum_type() else {
443            return None;
444        };
445        if val.is_null() {
446            return None;
447        }
448
449        // SUM coercion can introduce a physical CAST around the input column
450        // (`SUM(Int32)` becomes `SUM(CAST(Int32 AS Int64))`). Only use the
451        // column's raw sum stats when the widened stats value matches that
452        // cast target and the aggregate return type.
453        if let Some(cast_type) = cast_type {
454            let value_type = val.data_type();
455            if cast_type != statistics_args.return_type || &value_type != cast_type {
456                return None;
457            }
458            return Some(val);
459        }
460
461        if &val.data_type() == statistics_args.return_type {
462            Some(val)
463        } else {
464            val.cast_to(statistics_args.return_type).ok()
465        }
466    }
467}
468
469/// This accumulator computes SUM incrementally
470struct SumAccumulator<T: ArrowNumericType> {
471    sum: Option<T::Native>,
472    data_type: DataType,
473}
474
475impl<T: ArrowNumericType> std::fmt::Debug for SumAccumulator<T> {
476    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
477        write!(f, "SumAccumulator({})", self.data_type)
478    }
479}
480
481impl<T: ArrowNumericType> SumAccumulator<T> {
482    fn new(data_type: DataType) -> Self {
483        Self {
484            sum: None,
485            data_type,
486        }
487    }
488}
489
490impl<T: ArrowNumericType> Accumulator for SumAccumulator<T> {
491    fn state(&mut self) -> Result<Vec<ScalarValue>> {
492        Ok(vec![self.evaluate()?])
493    }
494
495    fn update_batch(&mut self, values: &[ArrayRef]) -> Result<()> {
496        let values = values[0].as_primitive::<T>();
497        if let Some(x) = arrow::compute::sum(values) {
498            let v = self.sum.get_or_insert_with(|| T::Native::usize_as(0));
499            *v = v.add_wrapping(x);
500        }
501        Ok(())
502    }
503
504    fn merge_batch(&mut self, states: &[ArrayRef]) -> Result<()> {
505        self.update_batch(states)
506    }
507
508    fn evaluate(&mut self) -> Result<ScalarValue> {
509        ScalarValue::new_primitive::<T>(self.sum, &self.data_type)
510    }
511
512    fn size(&self) -> usize {
513        size_of_val(self)
514    }
515}
516
517/// This accumulator incrementally computes sums over a sliding window
518///
519/// This is separate from [`SumAccumulator`] as requires additional state
520struct SlidingSumAccumulator<T: ArrowNumericType> {
521    sum: T::Native,
522    count: u64,
523    data_type: DataType,
524}
525
526impl<T: ArrowNumericType> std::fmt::Debug for SlidingSumAccumulator<T> {
527    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
528        write!(f, "SlidingSumAccumulator({})", self.data_type)
529    }
530}
531
532impl<T: ArrowNumericType> SlidingSumAccumulator<T> {
533    fn new(data_type: DataType) -> Self {
534        Self {
535            sum: T::Native::usize_as(0),
536            count: 0,
537            data_type,
538        }
539    }
540}
541
542impl<T: ArrowNumericType> Accumulator for SlidingSumAccumulator<T> {
543    fn state(&mut self) -> Result<Vec<ScalarValue>> {
544        Ok(vec![self.evaluate()?, self.count.into()])
545    }
546
547    fn update_batch(&mut self, values: &[ArrayRef]) -> Result<()> {
548        let values = values[0].as_primitive::<T>();
549        self.count += (values.len() - values.null_count()) as u64;
550        if let Some(x) = arrow::compute::sum(values) {
551            self.sum = self.sum.add_wrapping(x)
552        }
553        Ok(())
554    }
555
556    fn merge_batch(&mut self, states: &[ArrayRef]) -> Result<()> {
557        let values = states[0].as_primitive::<T>();
558        if let Some(x) = arrow::compute::sum(values) {
559            self.sum = self.sum.add_wrapping(x)
560        }
561        if let Some(x) = arrow::compute::sum(states[1].as_primitive::<UInt64Type>()) {
562            self.count += x;
563        }
564        Ok(())
565    }
566
567    fn evaluate(&mut self) -> Result<ScalarValue> {
568        let v = (self.count != 0).then_some(self.sum);
569        ScalarValue::new_primitive::<T>(v, &self.data_type)
570    }
571
572    fn size(&self) -> usize {
573        size_of_val(self)
574    }
575
576    fn retract_batch(&mut self, values: &[ArrayRef]) -> Result<()> {
577        let values = values[0].as_primitive::<T>();
578        if let Some(x) = arrow::compute::sum(values) {
579            self.sum = self.sum.sub_wrapping(x)
580        }
581        self.count -= (values.len() - values.null_count()) as u64;
582        Ok(())
583    }
584
585    fn supports_retract_batch(&self) -> bool {
586        true
587    }
588}
589
590/// A sliding‐window accumulator for `SUM(DISTINCT)` over Int64 columns.
591/// Maintains a running sum so that `evaluate()` is O(1).
592#[derive(Debug)]
593pub struct SlidingDistinctSumAccumulator {
594    /// Map each distinct value → its current count in the window
595    counts: HashMap<i64, usize, RandomState>,
596    /// Running sum of all distinct keys currently in the window
597    sum: i64,
598    /// Data type (must be Int64)
599    data_type: DataType,
600}
601
602impl SlidingDistinctSumAccumulator {
603    /// Create a new accumulator; only `DataType::Int64` is supported.
604    pub fn try_new(data_type: &DataType) -> Result<Self> {
605        // TODO support other numeric types
606        if *data_type != DataType::Int64 {
607            return exec_err!(
608                "SlidingDistinctSumAccumulator only supports Int64, got {data_type}"
609            );
610        }
611        Ok(Self {
612            counts: HashMap::default(),
613            sum: 0,
614            data_type: data_type.clone(),
615        })
616    }
617
618    fn update_value(&mut self, value: i64) {
619        let cnt = self.counts.entry(value).or_insert(0);
620        if *cnt == 0 {
621            // first occurrence in window
622            self.sum = self.sum.wrapping_add(value);
623        }
624        *cnt += 1;
625    }
626
627    fn retract_value(&mut self, value: i64) {
628        if let Some(cnt) = self.counts.get_mut(&value) {
629            *cnt -= 1;
630            if *cnt == 0 {
631                // last copy leaving window
632                self.sum = self.sum.wrapping_sub(value);
633                self.counts.remove(&value);
634            }
635        }
636    }
637
638    fn apply_valid_values<F>(
639        &mut self,
640        arr: &arrow::array::PrimitiveArray<Int64Type>,
641        mut op: F,
642    ) where
643        F: FnMut(&mut Self, i64),
644    {
645        if arr.null_count() == 0 {
646            for &value in arr.values() {
647                op(self, value);
648            }
649        } else {
650            for (idx, &value) in arr.values().iter().enumerate() {
651                if arr.is_valid(idx) {
652                    op(self, value);
653                }
654            }
655        }
656    }
657}
658
659impl Accumulator for SlidingDistinctSumAccumulator {
660    fn update_batch(&mut self, values: &[ArrayRef]) -> Result<()> {
661        let arr = values[0].as_primitive::<Int64Type>();
662        self.apply_valid_values(arr, Self::update_value);
663        Ok(())
664    }
665
666    fn evaluate(&mut self) -> Result<ScalarValue> {
667        // O(1) wrap of running sum
668        Ok(ScalarValue::Int64(
669            (!self.counts.is_empty()).then_some(self.sum),
670        ))
671    }
672
673    fn size(&self) -> usize {
674        // Estimate the owned map buckets; implementation-specific control bytes are excluded.
675        size_of_val(self) + self.counts.capacity() * size_of::<(i64, usize)>()
676    }
677
678    fn state(&mut self) -> Result<Vec<ScalarValue>> {
679        // Serialize distinct keys for cross-partition merge if needed
680        let keys = self
681            .counts
682            .keys()
683            .cloned()
684            .map(Some)
685            .map(ScalarValue::Int64)
686            .collect::<Vec<_>>();
687        Ok(vec![ScalarValue::List(ScalarValue::new_list_nullable(
688            &keys,
689            &self.data_type,
690        ))])
691    }
692
693    fn merge_batch(&mut self, states: &[ArrayRef]) -> Result<()> {
694        // Merge distinct keys from other partitions
695        let list_arr = states[0].as_list::<i32>();
696        for maybe_inner in list_arr.iter().flatten() {
697            for idx in 0..maybe_inner.len() {
698                if let ScalarValue::Int64(Some(v)) =
699                    ScalarValue::try_from_array(&*maybe_inner, idx)?
700                {
701                    self.update_value(v);
702                }
703            }
704        }
705        Ok(())
706    }
707
708    fn retract_batch(&mut self, values: &[ArrayRef]) -> Result<()> {
709        let arr = values[0].as_primitive::<Int64Type>();
710        self.apply_valid_values(arr, Self::retract_value);
711        Ok(())
712    }
713
714    fn supports_retract_batch(&self) -> bool {
715        true
716    }
717}
718
719#[cfg(test)]
720mod tests {
721    use super::*;
722    use arrow::{
723        array::{Decimal128Array, Int64Array},
724        buffer::{NullBuffer, ScalarBuffer},
725    };
726    use std::{
727        mem::{size_of, size_of_val},
728        sync::Arc,
729    };
730
731    #[test]
732    fn sliding_distinct_sum_ignores_null_slots() -> Result<()> {
733        let mut acc = SlidingDistinctSumAccumulator::try_new(&DataType::Int64)?;
734
735        let values: ArrayRef = Arc::new(Int64Array::new(
736            ScalarBuffer::from(vec![42, 5, 5]),
737            Some(NullBuffer::from(vec![false, true, true])),
738        ));
739        acc.update_batch(&[values])?;
740        assert_eq!(acc.evaluate()?, ScalarValue::Int64(Some(5)));
741
742        let retract: ArrayRef = Arc::new(Int64Array::new(
743            ScalarBuffer::from(vec![42, 5]),
744            Some(NullBuffer::from(vec![false, true])),
745        ));
746        acc.retract_batch(&[retract])?;
747        assert_eq!(acc.evaluate()?, ScalarValue::Int64(Some(5)));
748
749        let retract_last: ArrayRef =
750            Arc::new(Int64Array::new(ScalarBuffer::from(vec![5]), None));
751        acc.retract_batch(&[retract_last])?;
752        assert_eq!(acc.evaluate()?, ScalarValue::Int64(None));
753
754        Ok(())
755    }
756
757    fn expected_sliding_distinct_sum_size(acc: &SlidingDistinctSumAccumulator) -> usize {
758        size_of_val(acc) + acc.counts.capacity() * size_of::<(i64, usize)>()
759    }
760
761    #[test]
762    fn sliding_distinct_sum_size_includes_hash_map_capacity() -> Result<()> {
763        let mut acc = SlidingDistinctSumAccumulator::try_new(&DataType::Int64)?;
764        let empty_size = acc.size();
765        let values: ArrayRef = Arc::new(Int64Array::from(vec![1, 2, 3]));
766        acc.update_batch(&[Arc::clone(&values)])?;
767
768        let expected = expected_sliding_distinct_sum_size(&acc);
769        assert!(acc.counts.capacity() > 0);
770        assert_eq!(acc.size(), expected);
771        assert!(acc.size() > empty_size);
772
773        let initial_capacity = acc.counts.capacity();
774        let additional_values: ArrayRef =
775            Arc::new(Int64Array::from_iter(4..4 + initial_capacity as i64 + 1));
776        acc.update_batch(&[Arc::clone(&additional_values)])?;
777
778        let grown_size = expected_sliding_distinct_sum_size(&acc);
779        assert!(acc.counts.capacity() > initial_capacity);
780        assert_eq!(acc.size(), grown_size);
781        assert!(acc.size() > expected);
782
783        acc.retract_batch(&[values])?;
784        acc.retract_batch(&[additional_values])?;
785        assert!(acc.counts.is_empty());
786        assert_eq!(acc.size(), grown_size);
787
788        Ok(())
789    }
790
791    #[test]
792    fn sliding_distinct_sum_returns_null_for_all_null_frame() -> Result<()> {
793        let mut acc = SlidingDistinctSumAccumulator::try_new(&DataType::Int64)?;
794
795        let values: ArrayRef = Arc::new(Int64Array::new(
796            ScalarBuffer::from(vec![99]),
797            Some(NullBuffer::from(vec![false])),
798        ));
799        acc.update_batch(&[values])?;
800        assert_eq!(acc.evaluate()?, ScalarValue::Int64(None));
801
802        Ok(())
803    }
804
805    #[test]
806    fn decimal_sum_accumulator_uses_widened_return_type() -> Result<()> {
807        let values: ArrayRef = Arc::new(
808            Decimal128Array::from(vec![Some(99_999), Some(99_999)])
809                .with_precision_and_scale(5, 2)?,
810        );
811        let mut acc = SumAccumulator::<Decimal128Type>::new(DataType::Decimal128(15, 2));
812
813        acc.update_batch(&[values])?;
814
815        assert_eq!(
816            acc.evaluate()?,
817            ScalarValue::Decimal128(Some(199_998), 15, 2)
818        );
819        Ok(())
820    }
821
822    #[test]
823    fn sum_value_from_stats_widens_small_integer_sum() {
824        let statistics = datafusion_common::Statistics {
825            num_rows: Precision::Absent,
826            total_byte_size: Precision::Absent,
827            column_statistics: vec![datafusion_common::ColumnStatistics {
828                sum_value: Precision::Exact(ScalarValue::Int32(Some(10))),
829                ..Default::default()
830            }],
831        };
832        let return_type = DataType::Int64;
833        let expr: Arc<dyn datafusion_physical_expr::PhysicalExpr> =
834            Arc::new(Column::new("a", 0));
835        let exprs = vec![expr];
836        let statistics_args = StatisticsArgs {
837            statistics: &statistics,
838            return_type: &return_type,
839            is_distinct: false,
840            exprs: &exprs,
841        };
842
843        assert_eq!(
844            Sum::new().value_from_stats(&statistics_args),
845            Some(ScalarValue::Int64(Some(10)))
846        );
847    }
848
849    #[test]
850    fn sum_value_from_stats_casts_decimal_sum_to_return_type() {
851        let statistics = datafusion_common::Statistics {
852            num_rows: Precision::Absent,
853            total_byte_size: Precision::Absent,
854            column_statistics: vec![datafusion_common::ColumnStatistics {
855                sum_value: Precision::Exact(ScalarValue::Decimal128(Some(12345), 5, 2)),
856                ..Default::default()
857            }],
858        };
859        let return_type = DataType::Decimal128(15, 2);
860        let expr: Arc<dyn datafusion_physical_expr::PhysicalExpr> =
861            Arc::new(Column::new("a", 0));
862        let exprs = vec![expr];
863        let statistics_args = StatisticsArgs {
864            statistics: &statistics,
865            return_type: &return_type,
866            is_distinct: false,
867            exprs: &exprs,
868        };
869
870        assert_eq!(
871            Sum::new().value_from_stats(&statistics_args),
872            Some(ScalarValue::Decimal128(Some(12345), 15, 2))
873        );
874    }
875}