1mod bool;
5mod constant;
6mod decimal;
7mod grouped;
8mod primitive;
9pub(crate) use grouped::PrimitiveGroupedSumEncodingKernel;
10use vortex_error::VortexExpect;
11use vortex_error::VortexResult;
12use vortex_error::vortex_bail;
13use vortex_error::vortex_err;
14use vortex_error::vortex_panic;
15use vortex_session::VortexSession;
16use vortex_session::registry::CachedId;
17
18use self::bool::accumulate_bool;
19use self::constant::multiply_constant;
20use self::decimal::accumulate_decimal;
21use self::primitive::accumulate_primitive;
22use crate::ArrayRef;
23use crate::Canonical;
24use crate::Columnar;
25use crate::ExecutionCtx;
26use crate::aggregate_fn::Accumulator;
27use crate::aggregate_fn::AggregateFnId;
28use crate::aggregate_fn::AggregateFnVTable;
29use crate::aggregate_fn::DynAccumulator;
30use crate::aggregate_fn::NumericalAggregateOpts;
31use crate::dtype::DType;
32use crate::dtype::DecimalDType;
33use crate::dtype::MAX_PRECISION;
34use crate::dtype::Nullability;
35use crate::dtype::PType;
36use crate::expr::stats::Precision;
37use crate::expr::stats::Stat;
38use crate::expr::stats::StatsProvider;
39use crate::expr::stats::StatsProviderExt;
40use crate::scalar::DecimalValue;
41use crate::scalar::Scalar;
42
43pub fn sum(array: &ArrayRef, ctx: &mut ExecutionCtx) -> VortexResult<Scalar> {
47 if let Precision::Exact(sum_scalar) = array.statistics().get(Stat::Sum) {
49 return Ok(sum_scalar);
50 }
51
52 let mut acc = Accumulator::try_new(
55 Sum,
56 NumericalAggregateOpts::default(),
57 array.dtype().clone(),
58 )?;
59 acc.accumulate(array, ctx)?;
60 let result = acc.finish()?;
61
62 if let Some(val) = result.value().cloned() {
64 array.statistics().set(Stat::Sum, Precision::Exact(val));
65 }
66
67 Ok(result)
68}
69
70#[derive(Clone, Debug)]
78pub struct Sum;
79
80pub(crate) fn sum_decimal_dtype(input: &DecimalDType) -> DecimalDType {
84 DecimalDType::new(
85 u8::min(MAX_PRECISION, input.precision() + 10),
86 input.scale(),
87 )
88}
89
90impl AggregateFnVTable for Sum {
91 type Options = NumericalAggregateOpts;
92 type Partial = SumPartial;
93
94 fn id(&self) -> AggregateFnId {
95 static ID: CachedId = CachedId::new("vortex.sum");
96 *ID
97 }
98
99 fn serialize(&self, options: &Self::Options) -> VortexResult<Option<Vec<u8>>> {
100 Ok(Some(options.serialize()))
101 }
102
103 fn deserialize(
104 &self,
105 metadata: &[u8],
106 _session: &VortexSession,
107 ) -> VortexResult<Self::Options> {
108 NumericalAggregateOpts::deserialize(metadata)
109 }
110
111 fn return_dtype(&self, _options: &Self::Options, input_dtype: &DType) -> Option<DType> {
112 use Nullability::Nullable;
115
116 Some(match input_dtype {
117 DType::Bool(_) => DType::Primitive(PType::U64, Nullable),
118 DType::Primitive(ptype, _) => match ptype {
119 PType::U8 | PType::U16 | PType::U32 | PType::U64 => {
120 DType::Primitive(PType::U64, Nullable)
121 }
122 PType::I8 | PType::I16 | PType::I32 | PType::I64 => {
123 DType::Primitive(PType::I64, Nullable)
124 }
125 PType::F16 | PType::F32 | PType::F64 => {
126 DType::Primitive(PType::F64, Nullable)
128 }
129 },
130 DType::Decimal(decimal_dtype, _) => {
131 DType::Decimal(sum_decimal_dtype(decimal_dtype), Nullable)
132 }
133 _ => return None,
135 })
136 }
137
138 fn partial_dtype(&self, options: &Self::Options, input_dtype: &DType) -> Option<DType> {
139 self.return_dtype(options, input_dtype)
140 }
141
142 fn empty_partial(
143 &self,
144 options: &Self::Options,
145 input_dtype: &DType,
146 ) -> VortexResult<Self::Partial> {
147 let return_dtype = self
148 .return_dtype(options, input_dtype)
149 .ok_or_else(|| vortex_err!("Unsupported sum dtype: {}", input_dtype))?;
150 let initial = make_zero_state(&return_dtype);
151
152 Ok(SumPartial {
153 return_dtype,
154 current: Some(initial),
155 skip_nans: options.skip_nans,
156 })
157 }
158
159 fn combine_partials(&self, partial: &mut Self::Partial, other: Scalar) -> VortexResult<()> {
160 if other.is_null() {
161 partial.current = None;
163 return Ok(());
164 }
165 let Some(ref mut inner) = partial.current else {
166 return Ok(());
167 };
168 let saturated = match inner {
169 SumState::Unsigned(acc) => {
170 let val = other
171 .as_primitive()
172 .typed_value::<u64>()
173 .vortex_expect("checked non-null");
174 checked_add_u64(acc, val)
175 }
176 SumState::Signed(acc) => {
177 let val = other
178 .as_primitive()
179 .typed_value::<i64>()
180 .vortex_expect("checked non-null");
181 checked_add_i64(acc, val)
182 }
183 SumState::Float(acc) => {
184 let val = other
185 .as_primitive()
186 .typed_value::<f64>()
187 .vortex_expect("checked non-null");
188 *acc += val;
189 false
190 }
191 SumState::Decimal { value, dtype } => {
192 let val = other
193 .as_decimal()
194 .decimal_value()
195 .vortex_expect("checked non-null");
196 match value.checked_add(&val) {
197 Some(r) => {
198 *value = r;
199 !value.fits_in_precision(*dtype)
200 }
201 None => true,
202 }
203 }
204 };
205 if saturated {
206 partial.current = None;
207 }
208 Ok(())
209 }
210
211 fn to_scalar(&self, partial: &Self::Partial) -> VortexResult<Scalar> {
212 Ok(match &partial.current {
213 None => Scalar::null(partial.return_dtype.as_nullable()),
214 Some(SumState::Unsigned(v)) => Scalar::primitive(*v, Nullability::Nullable),
215 Some(SumState::Signed(v)) => Scalar::primitive(*v, Nullability::Nullable),
216 Some(SumState::Float(v)) => Scalar::primitive(*v, Nullability::Nullable),
217 Some(SumState::Decimal { value, .. }) => {
218 let decimal_dtype = *partial
219 .return_dtype
220 .as_decimal_opt()
221 .vortex_expect("return dtype must be decimal");
222 Scalar::decimal(*value, decimal_dtype, Nullability::Nullable)
223 }
224 })
225 }
226
227 fn reset(&self, partial: &mut Self::Partial) {
228 partial.current = Some(make_zero_state(&partial.return_dtype));
229 }
230
231 #[inline]
232 fn is_saturated(&self, partial: &Self::Partial) -> bool {
233 match partial.current.as_ref() {
234 None => true,
235 Some(SumState::Float(v)) => v.is_nan(),
236 Some(_) => false,
237 }
238 }
239
240 fn try_accumulate(
241 &self,
242 partial: &mut Self::Partial,
243 batch: &ArrayRef,
244 _ctx: &mut ExecutionCtx,
245 ) -> VortexResult<bool> {
246 if partial.skip_nans || !matches!(partial.current, Some(SumState::Float(_))) {
249 return Ok(false);
250 }
251 match batch.statistics().get_as::<u64>(Stat::NaNCount) {
252 Precision::Exact(0) => {
253 if let Precision::Exact(sum) = batch.statistics().get(Stat::Sum) {
256 let sum = if sum.dtype() == &partial.return_dtype {
257 sum
258 } else {
259 sum.cast(&partial.return_dtype)?
260 };
261 self.combine_partials(partial, sum)?;
262 return Ok(true);
263 }
264 Ok(false)
265 }
266 Precision::Exact(_) => {
267 if let Some(SumState::Float(acc)) = partial.current.as_mut() {
269 *acc = f64::NAN;
270 }
271 Ok(true)
272 }
273 _ => Ok(false),
274 }
275 }
276
277 fn accumulate(
278 &self,
279 partial: &mut Self::Partial,
280 batch: &Columnar,
281 ctx: &mut ExecutionCtx,
282 ) -> VortexResult<()> {
283 if let Columnar::Constant(c) = batch {
285 if partial.skip_nans && c.scalar().as_primitive_opt().is_some_and(|p| p.is_nan()) {
287 return Ok(());
288 }
289 if let Some(product) = multiply_constant(c.scalar(), c.len(), &partial.return_dtype)? {
290 self.combine_partials(partial, product)?;
291 }
292 return Ok(());
293 }
294
295 let skip_nans = partial.skip_nans;
296 let mut inner = match partial.current.take() {
297 Some(inner) => inner,
298 None => return Ok(()),
299 };
300
301 let result = match batch {
302 Columnar::Canonical(c) => match c {
303 Canonical::Primitive(p) => accumulate_primitive(&mut inner, p, ctx, skip_nans),
304 Canonical::Bool(b) => accumulate_bool(&mut inner, b, ctx),
305 Canonical::Decimal(d) => accumulate_decimal(&mut inner, d, ctx),
306 _ => vortex_bail!("Unsupported canonical type for sum: {}", batch.dtype()),
307 },
308 Columnar::Constant(_) => unreachable!(),
309 };
310
311 match result {
312 Ok(false) => partial.current = Some(inner),
313 Ok(true) => {} Err(e) => {
315 partial.current = Some(inner);
316 return Err(e);
317 }
318 }
319 Ok(())
320 }
321
322 fn finalize(&self, partials: ArrayRef) -> VortexResult<ArrayRef> {
323 Ok(partials)
324 }
325
326 fn finalize_scalar(&self, partial: &Self::Partial) -> VortexResult<Scalar> {
327 self.to_scalar(partial)
328 }
329}
330
331pub struct SumPartial {
334 return_dtype: DType,
335 current: Option<SumState>,
337 skip_nans: bool,
339}
340
341pub enum SumState {
345 Unsigned(u64),
346 Signed(i64),
347 Float(f64),
348 Decimal {
349 value: DecimalValue,
350 dtype: DecimalDType,
351 },
352}
353
354fn make_zero_state(return_dtype: &DType) -> SumState {
355 match return_dtype {
356 DType::Primitive(ptype, _) => match ptype {
357 PType::U8 | PType::U16 | PType::U32 | PType::U64 => SumState::Unsigned(0),
358 PType::I8 | PType::I16 | PType::I32 | PType::I64 => SumState::Signed(0),
359 PType::F16 | PType::F32 | PType::F64 => SumState::Float(0.0),
360 },
361 DType::Decimal(decimal, _) => SumState::Decimal {
362 value: DecimalValue::zero(decimal),
363 dtype: *decimal,
364 },
365 _ => vortex_panic!("Unsupported sum type"),
366 }
367}
368
369#[inline(always)]
371fn checked_add_u64(acc: &mut u64, val: u64) -> bool {
372 match acc.checked_add(val) {
373 Some(r) => {
374 *acc = r;
375 false
376 }
377 None => true,
378 }
379}
380
381#[inline(always)]
383fn checked_add_i64(acc: &mut i64, val: i64) -> bool {
384 match acc.checked_add(val) {
385 Some(r) => {
386 *acc = r;
387 false
388 }
389 None => true,
390 }
391}
392
393#[cfg(test)]
394mod tests {
395 use num_traits::CheckedAdd;
396 use vortex_buffer::buffer;
397 use vortex_error::VortexExpect;
398 use vortex_error::VortexResult;
399
400 use crate::ArrayRef;
401 use crate::IntoArray;
402 use crate::VortexSessionExecute;
403 use crate::aggregate_fn::Accumulator;
404 use crate::aggregate_fn::AggregateFnVTable;
405 use crate::aggregate_fn::DynAccumulator;
406 use crate::aggregate_fn::DynGroupedAccumulator;
407 use crate::aggregate_fn::GroupedAccumulator;
408 use crate::aggregate_fn::NumericalAggregateOpts;
409 use crate::aggregate_fn::fns::sum::Sum;
410 use crate::aggregate_fn::fns::sum::sum;
411 use crate::array_session;
412 use crate::arrays::BoolArray;
413 use crate::arrays::ChunkedArray;
414 use crate::arrays::ConstantArray;
415 use crate::arrays::DecimalArray;
416 use crate::arrays::FixedSizeListArray;
417 use crate::arrays::ListViewArray;
418 use crate::arrays::PrimitiveArray;
419 use crate::assert_arrays_eq;
420 use crate::dtype::DType;
421 use crate::dtype::DecimalDType;
422 use crate::dtype::Nullability;
423 use crate::dtype::Nullability::Nullable;
424 use crate::dtype::PType;
425 use crate::dtype::i256;
426 use crate::expr::stats::Precision;
427 use crate::expr::stats::Stat;
428 use crate::expr::stats::StatsProvider;
429 use crate::scalar::DecimalValue;
430 use crate::scalar::NumericOperator;
431 use crate::scalar::Scalar;
432 use crate::validity::Validity;
433
434 fn sum_with_accumulator(array: &ArrayRef, accumulator: &Scalar) -> VortexResult<Scalar> {
436 let mut ctx = array_session().create_execution_ctx();
437 if accumulator.is_null() {
438 return Ok(accumulator.clone());
439 }
440 if accumulator.is_zero() == Some(true) {
441 return sum(array, &mut ctx);
442 }
443
444 let sum_dtype = Stat::Sum.dtype(array.dtype()).ok_or_else(|| {
445 vortex_error::vortex_err!("Sum not supported for dtype: {}", array.dtype())
446 })?;
447
448 if !matches!(&sum_dtype, DType::Primitive(p, _) if p.is_float())
450 && let Precision::Exact(sum_scalar) = array.statistics().get(Stat::Sum)
451 {
452 return add_scalars(&sum_dtype, &sum_scalar, accumulator);
453 }
454
455 let array_sum = sum(array, &mut ctx)?;
457
458 add_scalars(&sum_dtype, &array_sum, accumulator)
460 }
461
462 fn add_scalars(sum_dtype: &DType, lhs: &Scalar, rhs: &Scalar) -> VortexResult<Scalar> {
464 if lhs.is_null() || rhs.is_null() {
465 return Ok(Scalar::null(sum_dtype.as_nullable()));
466 }
467
468 Ok(match sum_dtype {
469 DType::Primitive(ptype, _) if ptype.is_float() => {
470 let lhs_val = f64::try_from(lhs)?;
471 let rhs_val = f64::try_from(rhs)?;
472 Scalar::primitive(lhs_val + rhs_val, Nullable)
473 }
474 DType::Primitive(..) => lhs
475 .as_primitive()
476 .checked_add(&rhs.as_primitive())
477 .map(Scalar::from)
478 .unwrap_or_else(|| Scalar::null(sum_dtype.as_nullable())),
479 DType::Decimal(decimal_dtype, _) => lhs
482 .as_decimal()
483 .checked_binary_numeric(&rhs.as_decimal(), NumericOperator::Add)?
484 .and_then(|scalar| scalar.as_decimal().decimal_value())
485 .filter(|value| value.fits_in_precision(*decimal_dtype))
486 .map(|value| Scalar::decimal(value, *decimal_dtype, Nullable))
487 .unwrap_or_else(|| Scalar::null(sum_dtype.as_nullable())),
488 _ => unreachable!("Sum will always be a decimal or a primitive dtype"),
489 })
490 }
491
492 #[test]
495 fn sum_multi_batch() -> VortexResult<()> {
496 let mut ctx = array_session().create_execution_ctx();
497 let dtype = DType::Primitive(PType::I32, Nullability::NonNullable);
498 let mut acc = Accumulator::try_new(Sum, NumericalAggregateOpts::default(), dtype)?;
499
500 let batch1 = PrimitiveArray::new(buffer![10i32, 20], Validity::NonNullable).into_array();
501 acc.accumulate(&batch1, &mut ctx)?;
502
503 let batch2 = PrimitiveArray::new(buffer![3i32, 6, 9], Validity::NonNullable).into_array();
504 acc.accumulate(&batch2, &mut ctx)?;
505
506 let result = acc.finish()?;
507 assert_eq!(result.as_primitive().typed_value::<i64>(), Some(48));
508 Ok(())
509 }
510
511 #[test]
512 fn sum_finish_resets_state() -> VortexResult<()> {
513 let mut ctx = array_session().create_execution_ctx();
514 let dtype = DType::Primitive(PType::I32, Nullability::NonNullable);
515 let mut acc = Accumulator::try_new(Sum, NumericalAggregateOpts::default(), dtype)?;
516
517 let batch1 = PrimitiveArray::new(buffer![10i32, 20], Validity::NonNullable).into_array();
518 acc.accumulate(&batch1, &mut ctx)?;
519 let result1 = acc.finish()?;
520 assert_eq!(result1.as_primitive().typed_value::<i64>(), Some(30));
521
522 let batch2 = PrimitiveArray::new(buffer![3i32, 6, 9], Validity::NonNullable).into_array();
523 acc.accumulate(&batch2, &mut ctx)?;
524 let result2 = acc.finish()?;
525 assert_eq!(result2.as_primitive().typed_value::<i64>(), Some(18));
526 Ok(())
527 }
528
529 #[test]
532 fn sum_state_merge() -> VortexResult<()> {
533 let dtype = DType::Primitive(PType::I32, Nullability::NonNullable);
534 let mut state = Sum.empty_partial(&NumericalAggregateOpts::default(), &dtype)?;
535
536 let scalar1 = Scalar::primitive(100i64, Nullable);
537 Sum.combine_partials(&mut state, scalar1)?;
538
539 let scalar2 = Scalar::primitive(50i64, Nullable);
540 Sum.combine_partials(&mut state, scalar2)?;
541
542 let result = Sum.to_scalar(&state)?;
543 Sum.reset(&mut state);
544 assert_eq!(result.as_primitive().typed_value::<i64>(), Some(150));
545 Ok(())
546 }
547
548 #[test]
551 fn sum_stats() -> VortexResult<()> {
552 let array = ChunkedArray::try_new(
553 vec![
554 PrimitiveArray::from_iter([1, 1, 1]).into_array(),
555 PrimitiveArray::from_iter([2, 2, 2]).into_array(),
556 ],
557 DType::Primitive(PType::I32, Nullability::NonNullable),
558 )
559 .vortex_expect("operation should succeed in test");
560 let array = array.into_array();
561 sum_with_accumulator(&array, &Scalar::primitive(2i64, Nullable))?;
563
564 let sum_without_acc = sum(&array, &mut array_session().create_execution_ctx())?;
565 assert_eq!(sum_without_acc, Scalar::primitive(9i64, Nullable));
566 Ok(())
567 }
568
569 #[test]
572 fn sum_constant_float_non_multiply() -> VortexResult<()> {
573 let acc = -2048669276050936500000000000f64;
574 let array = ConstantArray::new(6.1811675e16f64, 25);
575 let result = sum_with_accumulator(&array.into_array(), &Scalar::primitive(acc, Nullable))
576 .vortex_expect("operation should succeed in test");
577 assert_eq!(
578 f64::try_from(&result).vortex_expect("operation should succeed in test"),
579 -2048669274505644600000000000f64
580 );
581 Ok(())
582 }
583
584 fn run_grouped_sum(groups: &ArrayRef, elem_dtype: &DType) -> VortexResult<ArrayRef> {
587 let mut acc = GroupedAccumulator::try_new(
588 Sum,
589 NumericalAggregateOpts::default(),
590 elem_dtype.clone(),
591 )?;
592 acc.accumulate_list(groups, &mut array_session().create_execution_ctx())?;
593 acc.finish()
594 }
595
596 #[test]
597 fn grouped_sum_fixed_size_list() -> VortexResult<()> {
598 let mut ctx = array_session().create_execution_ctx();
599 let elements =
600 PrimitiveArray::new(buffer![1i32, 2, 3, 4, 5, 6], Validity::NonNullable).into_array();
601 let groups = FixedSizeListArray::try_new(elements, 3, Validity::NonNullable, 2)?;
602
603 let elem_dtype = DType::Primitive(PType::I32, Nullability::NonNullable);
604 let result = run_grouped_sum(&groups.into_array(), &elem_dtype)?;
605
606 let expected = PrimitiveArray::from_option_iter([Some(6i64), Some(15i64)]).into_array();
607 assert_arrays_eq!(&result, &expected, &mut ctx);
608 Ok(())
609 }
610
611 #[test]
612 fn grouped_sum_with_null_elements() -> VortexResult<()> {
613 let mut ctx = array_session().create_execution_ctx();
614 let elements =
615 PrimitiveArray::from_option_iter([Some(1i32), None, Some(3), None, Some(5), Some(6)])
616 .into_array();
617 let groups = FixedSizeListArray::try_new(elements, 3, Validity::NonNullable, 2)?;
618
619 let elem_dtype = DType::Primitive(PType::I32, Nullable);
620 let result = run_grouped_sum(&groups.into_array(), &elem_dtype)?;
621
622 let expected = PrimitiveArray::from_option_iter([Some(4i64), Some(11i64)]).into_array();
623 assert_arrays_eq!(&result, &expected, &mut ctx);
624 Ok(())
625 }
626
627 #[test]
628 fn grouped_sum_with_null_group() -> VortexResult<()> {
629 let mut ctx = array_session().create_execution_ctx();
630 let elements =
631 PrimitiveArray::new(buffer![1i32, 2, 3, 4, 5, 6, 7, 8, 9], Validity::NonNullable)
632 .into_array();
633 let validity = Validity::from_iter([true, false, true]);
634 let groups = FixedSizeListArray::try_new(elements, 3, validity, 3)?;
635
636 let elem_dtype = DType::Primitive(PType::I32, Nullability::NonNullable);
637 let result = run_grouped_sum(&groups.into_array(), &elem_dtype)?;
638
639 let expected =
640 PrimitiveArray::from_option_iter([Some(6i64), None, Some(24i64)]).into_array();
641 assert_arrays_eq!(&result, &expected, &mut ctx);
642 Ok(())
643 }
644
645 #[test]
646 fn grouped_sum_all_null_elements_in_group() -> VortexResult<()> {
647 let mut ctx = array_session().create_execution_ctx();
648 let elements =
649 PrimitiveArray::from_option_iter([None::<i32>, None, Some(3), Some(4)]).into_array();
650 let groups = FixedSizeListArray::try_new(elements, 2, Validity::NonNullable, 2)?;
651
652 let elem_dtype = DType::Primitive(PType::I32, Nullable);
653 let result = run_grouped_sum(&groups.into_array(), &elem_dtype)?;
654
655 let expected = PrimitiveArray::from_option_iter([Some(0i64), Some(7i64)]).into_array();
656 assert_arrays_eq!(&result, &expected, &mut ctx);
657 Ok(())
658 }
659
660 #[test]
661 fn grouped_sum_bool() -> VortexResult<()> {
662 let mut ctx = array_session().create_execution_ctx();
663 let elements: BoolArray = [true, false, true, true, true, true].into_iter().collect();
664 let groups =
665 FixedSizeListArray::try_new(elements.into_array(), 3, Validity::NonNullable, 2)?;
666
667 let elem_dtype = DType::Bool(Nullability::NonNullable);
668 let result = run_grouped_sum(&groups.into_array(), &elem_dtype)?;
669
670 let expected = PrimitiveArray::from_option_iter([Some(2u64), Some(3u64)]).into_array();
671 assert_arrays_eq!(&result, &expected, &mut ctx);
672 Ok(())
673 }
674
675 #[test]
676 fn grouped_sum_finish_resets() -> VortexResult<()> {
677 let mut ctx = array_session().create_execution_ctx();
678 let elem_dtype = DType::Primitive(PType::I32, Nullability::NonNullable);
679 let mut acc =
680 GroupedAccumulator::try_new(Sum, NumericalAggregateOpts::default(), elem_dtype)?;
681
682 let elements1 =
683 PrimitiveArray::new(buffer![1i32, 2, 3, 4], Validity::NonNullable).into_array();
684 let groups1 = FixedSizeListArray::try_new(elements1, 2, Validity::NonNullable, 2)?;
685 acc.accumulate_list(&groups1.into_array(), &mut ctx)?;
686 let result1 = acc.finish()?;
687
688 let expected1 = PrimitiveArray::from_option_iter([Some(3i64), Some(7i64)]).into_array();
689 assert_arrays_eq!(&result1, &expected1, &mut ctx);
690
691 let elements2 = PrimitiveArray::new(buffer![10i32, 20], Validity::NonNullable).into_array();
692 let groups2 = FixedSizeListArray::try_new(elements2, 2, Validity::NonNullable, 1)?;
693 acc.accumulate_list(&groups2.into_array(), &mut ctx)?;
694 let result2 = acc.finish()?;
695
696 let expected2 = PrimitiveArray::from_option_iter([Some(30i64)]).into_array();
697 assert_arrays_eq!(&result2, &expected2, &mut ctx);
698 Ok(())
699 }
700
701 #[test]
702 fn grouped_sum_listview_out_of_order_offsets_with_null_group() -> VortexResult<()> {
703 let mut ctx = array_session().create_execution_ctx();
704 let elements =
705 PrimitiveArray::new(buffer![100i32, 200, 300], Validity::NonNullable).into_array();
706 let offsets = PrimitiveArray::new(buffer![2i32, 0, 1], Validity::NonNullable).into_array();
707 let sizes = PrimitiveArray::new(buffer![1i32, 1, 1], Validity::NonNullable).into_array();
708 let validity = Validity::from_iter([true, false, true]);
709 let groups = ListViewArray::try_new(elements, offsets, sizes, validity)?.into_array();
710
711 let elem_dtype = DType::Primitive(PType::I32, Nullability::NonNullable);
712 let result = run_grouped_sum(&groups, &elem_dtype)?;
713
714 let expected =
716 PrimitiveArray::from_option_iter([Some(300i64), None, Some(200i64)]).into_array();
717 assert_arrays_eq!(&result, &expected, &mut ctx);
718 Ok(())
719 }
720
721 #[test]
724 fn sum_chunked_floats_with_nulls() -> VortexResult<()> {
725 let chunk1 =
726 PrimitiveArray::from_option_iter(vec![Some(1.5f64), None, Some(3.2), Some(4.8)]);
727 let chunk2 = PrimitiveArray::from_option_iter(vec![Some(2.1f64), Some(5.7), None]);
728 let chunk3 = PrimitiveArray::from_option_iter(vec![None, Some(1.0f64), Some(2.5), None]);
729 let dtype = chunk1.dtype().clone();
730 let chunked = ChunkedArray::try_new(
731 vec![
732 chunk1.into_array(),
733 chunk2.into_array(),
734 chunk3.into_array(),
735 ],
736 dtype,
737 )?;
738
739 let result = sum(
740 &chunked.into_array(),
741 &mut array_session().create_execution_ctx(),
742 )?;
743 assert_eq!(result.as_primitive().as_::<f64>(), Some(20.8));
744 Ok(())
745 }
746
747 #[test]
748 fn sum_chunked_floats_all_nulls_is_zero() -> VortexResult<()> {
749 let chunk1 = PrimitiveArray::from_option_iter::<f32, _>(vec![None, None, None]);
750 let chunk2 = PrimitiveArray::from_option_iter::<f32, _>(vec![None, None]);
751 let dtype = chunk1.dtype().clone();
752 let chunked = ChunkedArray::try_new(vec![chunk1.into_array(), chunk2.into_array()], dtype)?;
753 let result = sum(
754 &chunked.into_array(),
755 &mut array_session().create_execution_ctx(),
756 )?;
757 assert_eq!(result, Scalar::primitive(0f64, Nullable));
758 Ok(())
759 }
760
761 #[test]
762 fn sum_chunked_floats_empty_chunks() -> VortexResult<()> {
763 let chunk1 = PrimitiveArray::from_option_iter(vec![Some(10.5f64), Some(20.3)]);
764 let chunk2 = ConstantArray::new(Scalar::primitive(0f64, Nullable), 0);
765 let chunk3 = PrimitiveArray::from_option_iter(vec![Some(5.2f64)]);
766 let dtype = chunk1.dtype().clone();
767 let chunked = ChunkedArray::try_new(
768 vec![
769 chunk1.into_array(),
770 chunk2.into_array(),
771 chunk3.into_array(),
772 ],
773 dtype,
774 )?;
775
776 let result = sum(
777 &chunked.into_array(),
778 &mut array_session().create_execution_ctx(),
779 )?;
780 assert_eq!(result.as_primitive().as_::<f64>(), Some(36.0));
781 Ok(())
782 }
783
784 #[test]
785 fn sum_chunked_int_almost_all_null() -> VortexResult<()> {
786 let chunk1 = PrimitiveArray::from_option_iter::<u32, _>(vec![Some(1)]);
787 let chunk2 = PrimitiveArray::from_option_iter::<u32, _>(vec![None]);
788 let dtype = chunk1.dtype().clone();
789 let chunked = ChunkedArray::try_new(vec![chunk1.into_array(), chunk2.into_array()], dtype)?;
790
791 let result = sum(
792 &chunked.into_array(),
793 &mut array_session().create_execution_ctx(),
794 )?;
795 assert_eq!(result.as_primitive().as_::<u64>(), Some(1));
796 Ok(())
797 }
798
799 #[test]
800 fn sum_chunked_decimals() -> VortexResult<()> {
801 let decimal_dtype = DecimalDType::new(10, 2);
802 let chunk1 = DecimalArray::new(
803 buffer![100i32, 100i32, 100i32, 100i32, 100i32],
804 decimal_dtype,
805 Validity::AllValid,
806 );
807 let chunk2 = DecimalArray::new(
808 buffer![200i32, 200i32, 200i32],
809 decimal_dtype,
810 Validity::AllValid,
811 );
812 let chunk3 = DecimalArray::new(buffer![300i32, 300i32], decimal_dtype, Validity::AllValid);
813 let dtype = chunk1.dtype().clone();
814 let chunked = ChunkedArray::try_new(
815 vec![
816 chunk1.into_array(),
817 chunk2.into_array(),
818 chunk3.into_array(),
819 ],
820 dtype,
821 )?;
822
823 let result = sum(
824 &chunked.into_array(),
825 &mut array_session().create_execution_ctx(),
826 )?;
827 let decimal_result = result.as_decimal();
828 assert_eq!(
829 decimal_result.decimal_value(),
830 Some(DecimalValue::I256(i256::from_i128(1700)))
831 );
832 Ok(())
833 }
834
835 #[test]
836 fn sum_chunked_decimals_with_nulls() -> VortexResult<()> {
837 let decimal_dtype = DecimalDType::new(10, 2);
838 let chunk1 = DecimalArray::new(
839 buffer![100i32, 100i32, 100i32],
840 decimal_dtype,
841 Validity::AllValid,
842 );
843 let chunk2 = DecimalArray::new(
844 buffer![0i32, 0i32],
845 decimal_dtype,
846 Validity::from_iter([false, false]),
847 );
848 let chunk3 = DecimalArray::new(buffer![200i32, 200i32], decimal_dtype, Validity::AllValid);
849 let dtype = chunk1.dtype().clone();
850 let chunked = ChunkedArray::try_new(
851 vec![
852 chunk1.into_array(),
853 chunk2.into_array(),
854 chunk3.into_array(),
855 ],
856 dtype,
857 )?;
858
859 let result = sum(
860 &chunked.into_array(),
861 &mut array_session().create_execution_ctx(),
862 )?;
863 let decimal_result = result.as_decimal();
864 assert_eq!(
865 decimal_result.decimal_value(),
866 Some(DecimalValue::I256(i256::from_i128(700)))
867 );
868 Ok(())
869 }
870
871 #[test]
872 fn sum_chunked_decimals_large() -> VortexResult<()> {
873 let decimal_dtype = DecimalDType::new(3, 0);
874 let chunk1 = ConstantArray::new(
875 Scalar::decimal(
876 DecimalValue::I16(500),
877 decimal_dtype,
878 Nullability::NonNullable,
879 ),
880 1,
881 );
882 let chunk2 = ConstantArray::new(
883 Scalar::decimal(
884 DecimalValue::I16(600),
885 decimal_dtype,
886 Nullability::NonNullable,
887 ),
888 1,
889 );
890 let dtype = chunk1.dtype().clone();
891 let chunked = ChunkedArray::try_new(vec![chunk1.into_array(), chunk2.into_array()], dtype)?;
892
893 let result = sum(
894 &chunked.into_array(),
895 &mut array_session().create_execution_ctx(),
896 )?;
897 let decimal_result = result.as_decimal();
898 assert_eq!(
899 decimal_result.decimal_value(),
900 Some(DecimalValue::I256(i256::from_i128(1100)))
901 );
902 assert_eq!(
903 result.dtype(),
904 &DType::Decimal(DecimalDType::new(13, 0), Nullable)
905 );
906 Ok(())
907 }
908}