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(..) => lhs
480 .as_decimal()
481 .checked_binary_numeric(&rhs.as_decimal(), NumericOperator::Add)
482 .map(Scalar::from)
483 .unwrap_or_else(|| Scalar::null(sum_dtype.as_nullable())),
484 _ => unreachable!("Sum will always be a decimal or a primitive dtype"),
485 })
486 }
487
488 #[test]
491 fn sum_multi_batch() -> VortexResult<()> {
492 let mut ctx = array_session().create_execution_ctx();
493 let dtype = DType::Primitive(PType::I32, Nullability::NonNullable);
494 let mut acc = Accumulator::try_new(Sum, NumericalAggregateOpts::default(), dtype)?;
495
496 let batch1 = PrimitiveArray::new(buffer![10i32, 20], Validity::NonNullable).into_array();
497 acc.accumulate(&batch1, &mut ctx)?;
498
499 let batch2 = PrimitiveArray::new(buffer![3i32, 6, 9], Validity::NonNullable).into_array();
500 acc.accumulate(&batch2, &mut ctx)?;
501
502 let result = acc.finish()?;
503 assert_eq!(result.as_primitive().typed_value::<i64>(), Some(48));
504 Ok(())
505 }
506
507 #[test]
508 fn sum_finish_resets_state() -> VortexResult<()> {
509 let mut ctx = array_session().create_execution_ctx();
510 let dtype = DType::Primitive(PType::I32, Nullability::NonNullable);
511 let mut acc = Accumulator::try_new(Sum, NumericalAggregateOpts::default(), dtype)?;
512
513 let batch1 = PrimitiveArray::new(buffer![10i32, 20], Validity::NonNullable).into_array();
514 acc.accumulate(&batch1, &mut ctx)?;
515 let result1 = acc.finish()?;
516 assert_eq!(result1.as_primitive().typed_value::<i64>(), Some(30));
517
518 let batch2 = PrimitiveArray::new(buffer![3i32, 6, 9], Validity::NonNullable).into_array();
519 acc.accumulate(&batch2, &mut ctx)?;
520 let result2 = acc.finish()?;
521 assert_eq!(result2.as_primitive().typed_value::<i64>(), Some(18));
522 Ok(())
523 }
524
525 #[test]
528 fn sum_state_merge() -> VortexResult<()> {
529 let dtype = DType::Primitive(PType::I32, Nullability::NonNullable);
530 let mut state = Sum.empty_partial(&NumericalAggregateOpts::default(), &dtype)?;
531
532 let scalar1 = Scalar::primitive(100i64, Nullable);
533 Sum.combine_partials(&mut state, scalar1)?;
534
535 let scalar2 = Scalar::primitive(50i64, Nullable);
536 Sum.combine_partials(&mut state, scalar2)?;
537
538 let result = Sum.to_scalar(&state)?;
539 Sum.reset(&mut state);
540 assert_eq!(result.as_primitive().typed_value::<i64>(), Some(150));
541 Ok(())
542 }
543
544 #[test]
547 fn sum_stats() -> VortexResult<()> {
548 let array = ChunkedArray::try_new(
549 vec![
550 PrimitiveArray::from_iter([1, 1, 1]).into_array(),
551 PrimitiveArray::from_iter([2, 2, 2]).into_array(),
552 ],
553 DType::Primitive(PType::I32, Nullability::NonNullable),
554 )
555 .vortex_expect("operation should succeed in test");
556 let array = array.into_array();
557 sum_with_accumulator(&array, &Scalar::primitive(2i64, Nullable))?;
559
560 let sum_without_acc = sum(&array, &mut array_session().create_execution_ctx())?;
561 assert_eq!(sum_without_acc, Scalar::primitive(9i64, Nullable));
562 Ok(())
563 }
564
565 #[test]
568 fn sum_constant_float_non_multiply() -> VortexResult<()> {
569 let acc = -2048669276050936500000000000f64;
570 let array = ConstantArray::new(6.1811675e16f64, 25);
571 let result = sum_with_accumulator(&array.into_array(), &Scalar::primitive(acc, Nullable))
572 .vortex_expect("operation should succeed in test");
573 assert_eq!(
574 f64::try_from(&result).vortex_expect("operation should succeed in test"),
575 -2048669274505644600000000000f64
576 );
577 Ok(())
578 }
579
580 fn run_grouped_sum(groups: &ArrayRef, elem_dtype: &DType) -> VortexResult<ArrayRef> {
583 let mut acc = GroupedAccumulator::try_new(
584 Sum,
585 NumericalAggregateOpts::default(),
586 elem_dtype.clone(),
587 )?;
588 acc.accumulate_list(groups, &mut array_session().create_execution_ctx())?;
589 acc.finish()
590 }
591
592 #[test]
593 fn grouped_sum_fixed_size_list() -> VortexResult<()> {
594 let mut ctx = array_session().create_execution_ctx();
595 let elements =
596 PrimitiveArray::new(buffer![1i32, 2, 3, 4, 5, 6], Validity::NonNullable).into_array();
597 let groups = FixedSizeListArray::try_new(elements, 3, Validity::NonNullable, 2)?;
598
599 let elem_dtype = DType::Primitive(PType::I32, Nullability::NonNullable);
600 let result = run_grouped_sum(&groups.into_array(), &elem_dtype)?;
601
602 let expected = PrimitiveArray::from_option_iter([Some(6i64), Some(15i64)]).into_array();
603 assert_arrays_eq!(&result, &expected, &mut ctx);
604 Ok(())
605 }
606
607 #[test]
608 fn grouped_sum_with_null_elements() -> VortexResult<()> {
609 let mut ctx = array_session().create_execution_ctx();
610 let elements =
611 PrimitiveArray::from_option_iter([Some(1i32), None, Some(3), None, Some(5), Some(6)])
612 .into_array();
613 let groups = FixedSizeListArray::try_new(elements, 3, Validity::NonNullable, 2)?;
614
615 let elem_dtype = DType::Primitive(PType::I32, Nullable);
616 let result = run_grouped_sum(&groups.into_array(), &elem_dtype)?;
617
618 let expected = PrimitiveArray::from_option_iter([Some(4i64), Some(11i64)]).into_array();
619 assert_arrays_eq!(&result, &expected, &mut ctx);
620 Ok(())
621 }
622
623 #[test]
624 fn grouped_sum_with_null_group() -> VortexResult<()> {
625 let mut ctx = array_session().create_execution_ctx();
626 let elements =
627 PrimitiveArray::new(buffer![1i32, 2, 3, 4, 5, 6, 7, 8, 9], Validity::NonNullable)
628 .into_array();
629 let validity = Validity::from_iter([true, false, true]);
630 let groups = FixedSizeListArray::try_new(elements, 3, validity, 3)?;
631
632 let elem_dtype = DType::Primitive(PType::I32, Nullability::NonNullable);
633 let result = run_grouped_sum(&groups.into_array(), &elem_dtype)?;
634
635 let expected =
636 PrimitiveArray::from_option_iter([Some(6i64), None, Some(24i64)]).into_array();
637 assert_arrays_eq!(&result, &expected, &mut ctx);
638 Ok(())
639 }
640
641 #[test]
642 fn grouped_sum_all_null_elements_in_group() -> VortexResult<()> {
643 let mut ctx = array_session().create_execution_ctx();
644 let elements =
645 PrimitiveArray::from_option_iter([None::<i32>, None, Some(3), Some(4)]).into_array();
646 let groups = FixedSizeListArray::try_new(elements, 2, Validity::NonNullable, 2)?;
647
648 let elem_dtype = DType::Primitive(PType::I32, Nullable);
649 let result = run_grouped_sum(&groups.into_array(), &elem_dtype)?;
650
651 let expected = PrimitiveArray::from_option_iter([Some(0i64), Some(7i64)]).into_array();
652 assert_arrays_eq!(&result, &expected, &mut ctx);
653 Ok(())
654 }
655
656 #[test]
657 fn grouped_sum_bool() -> VortexResult<()> {
658 let mut ctx = array_session().create_execution_ctx();
659 let elements: BoolArray = [true, false, true, true, true, true].into_iter().collect();
660 let groups =
661 FixedSizeListArray::try_new(elements.into_array(), 3, Validity::NonNullable, 2)?;
662
663 let elem_dtype = DType::Bool(Nullability::NonNullable);
664 let result = run_grouped_sum(&groups.into_array(), &elem_dtype)?;
665
666 let expected = PrimitiveArray::from_option_iter([Some(2u64), Some(3u64)]).into_array();
667 assert_arrays_eq!(&result, &expected, &mut ctx);
668 Ok(())
669 }
670
671 #[test]
672 fn grouped_sum_finish_resets() -> VortexResult<()> {
673 let mut ctx = array_session().create_execution_ctx();
674 let elem_dtype = DType::Primitive(PType::I32, Nullability::NonNullable);
675 let mut acc =
676 GroupedAccumulator::try_new(Sum, NumericalAggregateOpts::default(), elem_dtype)?;
677
678 let elements1 =
679 PrimitiveArray::new(buffer![1i32, 2, 3, 4], Validity::NonNullable).into_array();
680 let groups1 = FixedSizeListArray::try_new(elements1, 2, Validity::NonNullable, 2)?;
681 acc.accumulate_list(&groups1.into_array(), &mut ctx)?;
682 let result1 = acc.finish()?;
683
684 let expected1 = PrimitiveArray::from_option_iter([Some(3i64), Some(7i64)]).into_array();
685 assert_arrays_eq!(&result1, &expected1, &mut ctx);
686
687 let elements2 = PrimitiveArray::new(buffer![10i32, 20], Validity::NonNullable).into_array();
688 let groups2 = FixedSizeListArray::try_new(elements2, 2, Validity::NonNullable, 1)?;
689 acc.accumulate_list(&groups2.into_array(), &mut ctx)?;
690 let result2 = acc.finish()?;
691
692 let expected2 = PrimitiveArray::from_option_iter([Some(30i64)]).into_array();
693 assert_arrays_eq!(&result2, &expected2, &mut ctx);
694 Ok(())
695 }
696
697 #[test]
698 fn grouped_sum_listview_out_of_order_offsets_with_null_group() -> VortexResult<()> {
699 let mut ctx = array_session().create_execution_ctx();
700 let elements =
701 PrimitiveArray::new(buffer![100i32, 200, 300], Validity::NonNullable).into_array();
702 let offsets = PrimitiveArray::new(buffer![2i32, 0, 1], Validity::NonNullable).into_array();
703 let sizes = PrimitiveArray::new(buffer![1i32, 1, 1], Validity::NonNullable).into_array();
704 let validity = Validity::from_iter([true, false, true]);
705 let groups = ListViewArray::try_new(elements, offsets, sizes, validity)?.into_array();
706
707 let elem_dtype = DType::Primitive(PType::I32, Nullability::NonNullable);
708 let result = run_grouped_sum(&groups, &elem_dtype)?;
709
710 let expected =
712 PrimitiveArray::from_option_iter([Some(300i64), None, Some(200i64)]).into_array();
713 assert_arrays_eq!(&result, &expected, &mut ctx);
714 Ok(())
715 }
716
717 #[test]
720 fn sum_chunked_floats_with_nulls() -> VortexResult<()> {
721 let chunk1 =
722 PrimitiveArray::from_option_iter(vec![Some(1.5f64), None, Some(3.2), Some(4.8)]);
723 let chunk2 = PrimitiveArray::from_option_iter(vec![Some(2.1f64), Some(5.7), None]);
724 let chunk3 = PrimitiveArray::from_option_iter(vec![None, Some(1.0f64), Some(2.5), None]);
725 let dtype = chunk1.dtype().clone();
726 let chunked = ChunkedArray::try_new(
727 vec![
728 chunk1.into_array(),
729 chunk2.into_array(),
730 chunk3.into_array(),
731 ],
732 dtype,
733 )?;
734
735 let result = sum(
736 &chunked.into_array(),
737 &mut array_session().create_execution_ctx(),
738 )?;
739 assert_eq!(result.as_primitive().as_::<f64>(), Some(20.8));
740 Ok(())
741 }
742
743 #[test]
744 fn sum_chunked_floats_all_nulls_is_zero() -> VortexResult<()> {
745 let chunk1 = PrimitiveArray::from_option_iter::<f32, _>(vec![None, None, None]);
746 let chunk2 = PrimitiveArray::from_option_iter::<f32, _>(vec![None, None]);
747 let dtype = chunk1.dtype().clone();
748 let chunked = ChunkedArray::try_new(vec![chunk1.into_array(), chunk2.into_array()], dtype)?;
749 let result = sum(
750 &chunked.into_array(),
751 &mut array_session().create_execution_ctx(),
752 )?;
753 assert_eq!(result, Scalar::primitive(0f64, Nullable));
754 Ok(())
755 }
756
757 #[test]
758 fn sum_chunked_floats_empty_chunks() -> VortexResult<()> {
759 let chunk1 = PrimitiveArray::from_option_iter(vec![Some(10.5f64), Some(20.3)]);
760 let chunk2 = ConstantArray::new(Scalar::primitive(0f64, Nullable), 0);
761 let chunk3 = PrimitiveArray::from_option_iter(vec![Some(5.2f64)]);
762 let dtype = chunk1.dtype().clone();
763 let chunked = ChunkedArray::try_new(
764 vec![
765 chunk1.into_array(),
766 chunk2.into_array(),
767 chunk3.into_array(),
768 ],
769 dtype,
770 )?;
771
772 let result = sum(
773 &chunked.into_array(),
774 &mut array_session().create_execution_ctx(),
775 )?;
776 assert_eq!(result.as_primitive().as_::<f64>(), Some(36.0));
777 Ok(())
778 }
779
780 #[test]
781 fn sum_chunked_int_almost_all_null() -> VortexResult<()> {
782 let chunk1 = PrimitiveArray::from_option_iter::<u32, _>(vec![Some(1)]);
783 let chunk2 = PrimitiveArray::from_option_iter::<u32, _>(vec![None]);
784 let dtype = chunk1.dtype().clone();
785 let chunked = ChunkedArray::try_new(vec![chunk1.into_array(), chunk2.into_array()], dtype)?;
786
787 let result = sum(
788 &chunked.into_array(),
789 &mut array_session().create_execution_ctx(),
790 )?;
791 assert_eq!(result.as_primitive().as_::<u64>(), Some(1));
792 Ok(())
793 }
794
795 #[test]
796 fn sum_chunked_decimals() -> VortexResult<()> {
797 let decimal_dtype = DecimalDType::new(10, 2);
798 let chunk1 = DecimalArray::new(
799 buffer![100i32, 100i32, 100i32, 100i32, 100i32],
800 decimal_dtype,
801 Validity::AllValid,
802 );
803 let chunk2 = DecimalArray::new(
804 buffer![200i32, 200i32, 200i32],
805 decimal_dtype,
806 Validity::AllValid,
807 );
808 let chunk3 = DecimalArray::new(buffer![300i32, 300i32], decimal_dtype, Validity::AllValid);
809 let dtype = chunk1.dtype().clone();
810 let chunked = ChunkedArray::try_new(
811 vec![
812 chunk1.into_array(),
813 chunk2.into_array(),
814 chunk3.into_array(),
815 ],
816 dtype,
817 )?;
818
819 let result = sum(
820 &chunked.into_array(),
821 &mut array_session().create_execution_ctx(),
822 )?;
823 let decimal_result = result.as_decimal();
824 assert_eq!(
825 decimal_result.decimal_value(),
826 Some(DecimalValue::I256(i256::from_i128(1700)))
827 );
828 Ok(())
829 }
830
831 #[test]
832 fn sum_chunked_decimals_with_nulls() -> VortexResult<()> {
833 let decimal_dtype = DecimalDType::new(10, 2);
834 let chunk1 = DecimalArray::new(
835 buffer![100i32, 100i32, 100i32],
836 decimal_dtype,
837 Validity::AllValid,
838 );
839 let chunk2 = DecimalArray::new(
840 buffer![0i32, 0i32],
841 decimal_dtype,
842 Validity::from_iter([false, false]),
843 );
844 let chunk3 = DecimalArray::new(buffer![200i32, 200i32], decimal_dtype, Validity::AllValid);
845 let dtype = chunk1.dtype().clone();
846 let chunked = ChunkedArray::try_new(
847 vec![
848 chunk1.into_array(),
849 chunk2.into_array(),
850 chunk3.into_array(),
851 ],
852 dtype,
853 )?;
854
855 let result = sum(
856 &chunked.into_array(),
857 &mut array_session().create_execution_ctx(),
858 )?;
859 let decimal_result = result.as_decimal();
860 assert_eq!(
861 decimal_result.decimal_value(),
862 Some(DecimalValue::I256(i256::from_i128(700)))
863 );
864 Ok(())
865 }
866
867 #[test]
868 fn sum_chunked_decimals_large() -> VortexResult<()> {
869 let decimal_dtype = DecimalDType::new(3, 0);
870 let chunk1 = ConstantArray::new(
871 Scalar::decimal(
872 DecimalValue::I16(500),
873 decimal_dtype,
874 Nullability::NonNullable,
875 ),
876 1,
877 );
878 let chunk2 = ConstantArray::new(
879 Scalar::decimal(
880 DecimalValue::I16(600),
881 decimal_dtype,
882 Nullability::NonNullable,
883 ),
884 1,
885 );
886 let dtype = chunk1.dtype().clone();
887 let chunked = ChunkedArray::try_new(vec![chunk1.into_array(), chunk2.into_array()], dtype)?;
888
889 let result = sum(
890 &chunked.into_array(),
891 &mut array_session().create_execution_ctx(),
892 )?;
893 let decimal_result = result.as_decimal();
894 assert_eq!(
895 decimal_result.decimal_value(),
896 Some(DecimalValue::I256(i256::from_i128(1100)))
897 );
898 assert_eq!(
899 result.dtype(),
900 &DType::Decimal(DecimalDType::new(13, 0), Nullable)
901 );
902 Ok(())
903 }
904}