1mod bool;
5mod decimal;
6mod extension;
7mod fixed_size_list;
8mod list;
9mod map;
10pub mod primitive;
11mod struct_;
12mod varbin;
13
14use vortex_error::VortexExpect;
15use vortex_error::VortexResult;
16use vortex_error::vortex_bail;
17use vortex_session::registry::CachedId;
18
19use self::bool::check_bool_constant;
20use self::decimal::check_decimal_constant;
21use self::extension::check_extension_constant;
22use self::fixed_size_list::check_fixed_size_list_constant;
23use self::list::check_listview_constant;
24use self::map::check_map_constant;
25use self::primitive::check_primitive_constant;
26use self::struct_::check_struct_constant;
27use self::varbin::check_varbinview_constant;
28use crate::ArrayRef;
29use crate::Canonical;
30use crate::Columnar;
31use crate::ExecutionCtx;
32use crate::IntoArray;
33use crate::aggregate_fn::Accumulator;
34use crate::aggregate_fn::AggregateArgs;
35use crate::aggregate_fn::AggregateFnId;
36use crate::aggregate_fn::AggregateFnVTable;
37use crate::aggregate_fn::DynAccumulator;
38use crate::aggregate_fn::EmptyOptions;
39use crate::arrays::Constant;
40use crate::arrays::Null;
41use crate::builtins::ArrayBuiltins;
42use crate::dtype::DType;
43use crate::dtype::FieldNames;
44use crate::dtype::Nullability;
45use crate::dtype::StructFields;
46use crate::expr::stats::Precision;
47use crate::expr::stats::Stat;
48use crate::expr::stats::StatsProvider;
49use crate::expr::stats::StatsProviderExt;
50use crate::scalar::Scalar;
51use crate::scalar_fn::fns::operators::Operator;
52
53fn arrays_value_equal(a: &ArrayRef, b: &ArrayRef, ctx: &mut ExecutionCtx) -> VortexResult<bool> {
58 debug_assert_eq!(a.len(), b.len());
59 if a.is_empty() {
60 return Ok(true);
61 }
62
63 let a_mask = a.validity()?.execute_mask(a.len(), ctx)?;
65 let b_mask = b.validity()?.execute_mask(b.len(), ctx)?;
66 if a_mask != b_mask {
67 return Ok(false);
68 }
69
70 let valid_count = a_mask.true_count();
71 if valid_count == 0 {
72 return Ok(true);
74 }
75
76 let eq_result = a.binary(b.clone(), Operator::Eq)?;
79 let eq_result = eq_result.null_as_false().execute(ctx)?;
80
81 Ok(eq_result.true_count() == valid_count)
82}
83
84pub fn is_constant(array: &ArrayRef, ctx: &mut ExecutionCtx) -> VortexResult<bool> {
93 if let Precision::Exact(value) = array.statistics().get_as::<bool>(Stat::IsConstant) {
95 return Ok(value);
96 }
97
98 if array.is_empty() {
100 return Ok(false);
101 }
102
103 if array.len() == 1 {
105 array
106 .statistics()
107 .set(Stat::IsConstant, Precision::Exact(true.into()));
108 return Ok(true);
109 }
110
111 if array.is::<Constant>() || array.is::<Null>() {
113 array
114 .statistics()
115 .set(Stat::IsConstant, Precision::Exact(true.into()));
116 return Ok(true);
117 }
118
119 let all_invalid = array.all_invalid(ctx)?;
120 if all_invalid {
121 array
122 .statistics()
123 .set(Stat::IsConstant, Precision::Exact(true.into()));
124 return Ok(true);
125 }
126
127 let all_valid = array.all_valid(ctx)?;
128
129 if !all_valid && !all_invalid {
131 array
132 .statistics()
133 .set(Stat::IsConstant, Precision::Exact(false.into()));
134 return Ok(false);
135 }
136
137 let min_stat = array.statistics().get(Stat::Min);
139 let max_stat = array.statistics().get(Stat::Max);
140
141 if let Precision::Exact(min) = min_stat.as_ref()
142 && let Precision::Exact(max) = max_stat.as_ref()
143 && min == max
144 && (Stat::NaNCount.dtype(array.dtype()).is_none()
145 || array.statistics().get_as::<u64>(Stat::NaNCount) == Precision::exact(0u64))
146 {
147 array
148 .statistics()
149 .set(Stat::IsConstant, Precision::Exact(true.into()));
150 return Ok(true);
151 }
152
153 if IsConstant
155 .return_dtype(&EmptyOptions, array.dtype())
156 .is_none()
157 {
158 return Ok(false);
160 }
161
162 let mut acc = Accumulator::try_new(IsConstant, EmptyOptions, array.dtype().clone())?;
164 acc.accumulate(array, ctx)?;
165 let result_scalar = acc.finish()?;
166
167 let result = result_scalar.as_bool().value().unwrap_or(false);
168
169 array
171 .statistics()
172 .set(Stat::IsConstant, Precision::Exact(result.into()));
173
174 Ok(result)
175}
176
177#[derive(Clone, Debug)]
183pub struct IsConstant;
184
185impl IsConstant {
186 pub fn make_partial(
192 batch: &ArrayRef,
193 is_constant: bool,
194 ctx: &mut ExecutionCtx,
195 ) -> VortexResult<Scalar> {
196 let partial_dtype = make_is_constant_partial_dtype(batch.dtype());
197 if is_constant {
198 if batch.is_empty() {
199 return Ok(Scalar::null(partial_dtype));
200 }
201 let first_value = batch.execute_scalar(0, ctx)?.into_nullable();
202 Ok(Scalar::struct_(
203 partial_dtype,
204 vec![Scalar::bool(true, Nullability::NonNullable), first_value],
205 ))
206 } else {
207 Ok(Scalar::struct_(
208 partial_dtype,
209 vec![
210 Scalar::bool(false, Nullability::NonNullable),
211 Scalar::null(batch.dtype().as_nullable()),
212 ],
213 ))
214 }
215 }
216}
217
218pub struct IsConstantPartial {
220 is_constant: bool,
221 first_value: Option<Scalar>,
223}
224
225impl IsConstantPartial {
226 fn empty() -> Self {
228 Self {
229 is_constant: true,
230 first_value: None,
231 }
232 }
233
234 fn check_value(&mut self, value: Scalar) {
235 if !self.is_constant {
236 return;
237 }
238 match &self.first_value {
239 None => {
240 self.first_value = Some(value);
241 }
242 Some(first) => {
243 if *first != value {
244 self.is_constant = false;
245 }
246 }
247 }
248 }
249}
250
251static NAMES: std::sync::LazyLock<FieldNames> =
252 std::sync::LazyLock::new(|| FieldNames::from(["is_constant", "value"]));
253
254pub fn make_is_constant_partial_dtype(element_dtype: &DType) -> DType {
255 DType::Struct(
256 StructFields::new(
257 NAMES.clone(),
258 vec![
259 DType::Bool(Nullability::NonNullable),
260 element_dtype.as_nullable(),
261 ],
262 ),
263 Nullability::Nullable,
264 )
265}
266
267impl AggregateFnVTable for IsConstant {
268 type Options = EmptyOptions;
269 type Partial = IsConstantPartial;
270
271 fn id(&self) -> AggregateFnId {
272 static ID: CachedId = CachedId::new("vortex.is_constant");
273 *ID
274 }
275
276 fn serialize(&self, _options: &Self::Options) -> VortexResult<Option<Vec<u8>>> {
277 vortex_bail!("IsConstant is not yet serializable");
278 }
279
280 fn return_dtype(&self, _options: &Self::Options, input_dtype: &DType) -> Option<DType> {
281 match input_dtype {
282 DType::Null | DType::Variant(..) => None,
283 _ => Some(DType::Bool(Nullability::NonNullable)),
284 }
285 }
286
287 fn partial_dtype(&self, _options: &Self::Options, input_dtype: &DType) -> Option<DType> {
288 match input_dtype {
289 DType::Null | DType::Variant(..) => None,
290 _ => Some(make_is_constant_partial_dtype(input_dtype)),
291 }
292 }
293
294 fn empty_partial(
295 &self,
296 _args: AggregateArgs<'_, Self::Options>,
297 ) -> VortexResult<Self::Partial> {
298 Ok(IsConstantPartial::empty())
299 }
300
301 fn partial_from_scalar(
302 &self,
303 _args: AggregateArgs<'_, Self::Options>,
304 scalar: Scalar,
305 ) -> VortexResult<Self::Partial> {
306 if scalar.is_null() {
308 return Ok(IsConstantPartial::empty());
309 }
310
311 let is_constant = scalar
312 .as_struct()
313 .field_by_idx(0)
314 .map(|s| s.as_bool().value().unwrap_or(false))
315 .unwrap_or(false);
316
317 Ok(IsConstantPartial {
318 is_constant,
319 first_value: scalar.as_struct().field_by_idx(1),
320 })
321 }
322
323 fn merge_partials(
324 &self,
325 _args: AggregateArgs<'_, Self::Options>,
326 mut acc: Self::Partial,
327 partial: Self::Partial,
328 ) -> VortexResult<Self::Partial> {
329 if !partial.is_constant {
330 acc.is_constant = false;
331 } else if let Some(value) = partial.first_value {
332 acc.check_value(value);
333 }
334 Ok(acc)
335 }
336
337 fn to_scalar(
338 &self,
339 args: AggregateArgs<'_, Self::Options>,
340 partial: &Self::Partial,
341 ) -> VortexResult<Scalar> {
342 let dtype = args.partial_dtype.clone();
343 let element_dtype = args.dtype.as_nullable();
344 let first_value = match &partial.first_value {
347 Some(first_value) => first_value.clone().cast(&element_dtype)?,
348 None if partial.is_constant => return Ok(Scalar::null(dtype)),
349 None => Scalar::null(element_dtype),
350 };
351 Ok(Scalar::struct_(
352 dtype,
353 vec![
354 Scalar::bool(partial.is_constant, Nullability::NonNullable),
355 first_value,
356 ],
357 ))
358 }
359
360 #[inline]
361 fn is_saturated(
362 &self,
363 _args: AggregateArgs<'_, Self::Options>,
364 partial: &Self::Partial,
365 ) -> bool {
366 !partial.is_constant
367 }
368
369 fn accumulate(
370 &self,
371 args: AggregateArgs<'_, Self::Options>,
372 partial: &mut Self::Partial,
373 batch: &Columnar,
374 ctx: &mut ExecutionCtx,
375 ) -> VortexResult<()> {
376 if !partial.is_constant {
377 return Ok(());
378 }
379
380 match batch {
381 Columnar::Constant(c) => {
382 partial.check_value(c.scalar().clone().into_nullable());
383 Ok(())
384 }
385 Columnar::Canonical(c) => {
386 if c.is_empty() {
387 return Ok(());
388 }
389
390 let array_ref = c.clone().into_array();
392
393 let all_invalid = array_ref.all_invalid(ctx)?;
394 if all_invalid {
395 partial.check_value(Scalar::null(args.dtype.as_nullable()));
396 return Ok(());
397 }
398
399 let all_valid = array_ref.all_valid(ctx)?;
400 if !all_valid && !all_invalid {
402 partial.is_constant = false;
403 return Ok(());
404 }
405
406 if c.len() == 1 {
408 partial.check_value(array_ref.execute_scalar(0, ctx)?.into_nullable());
409 return Ok(());
410 }
411
412 let batch_is_constant = match c {
413 Canonical::Primitive(p) => check_primitive_constant(p),
414 Canonical::Bool(b) => check_bool_constant(b),
415 Canonical::VarBinView(v) => check_varbinview_constant(v),
416 Canonical::Decimal(d) => check_decimal_constant(d),
417 Canonical::Struct(s) => check_struct_constant(s, ctx)?,
418 Canonical::Extension(e) => check_extension_constant(e, ctx)?,
419 Canonical::List(l) => check_listview_constant(l, ctx)?,
420 Canonical::Map(m) => check_map_constant(m, ctx)?,
421 Canonical::FixedSizeList(f) => check_fixed_size_list_constant(f, ctx)?,
422 Canonical::Null(_) => true,
423 Canonical::Union(_) => {
424 vortex_bail!("TODO(connor)[Union]: implement IsConstant for Union arrays")
425 }
426 Canonical::Variant(_) => {
427 vortex_bail!("Variant arrays don't support IsConstant")
428 }
429 };
430
431 if !batch_is_constant {
432 partial.is_constant = false;
433 return Ok(());
434 }
435
436 partial.check_value(array_ref.execute_scalar(0, ctx)?.into_nullable());
437 Ok(())
438 }
439 }
440 }
441
442 fn finalize(
443 &self,
444 _args: AggregateArgs<'_, Self::Options>,
445 partials: ArrayRef,
446 ) -> VortexResult<ArrayRef> {
447 partials.get_item(NAMES.get(0).vortex_expect("out of bounds").clone())
448 }
449
450 fn finalize_scalar(
451 &self,
452 _args: AggregateArgs<'_, Self::Options>,
453 partial: &Self::Partial,
454 ) -> VortexResult<Scalar> {
455 if partial.first_value.is_none() {
456 return Ok(Scalar::bool(false, Nullability::NonNullable));
458 }
459 Ok(Scalar::bool(partial.is_constant, Nullability::NonNullable))
460 }
461}
462
463#[cfg(test)]
464mod tests {
465 use rstest::rstest;
466 use vortex_buffer::Buffer;
467 use vortex_buffer::buffer;
468 use vortex_error::VortexResult;
469
470 use crate::IntoArray as _;
471 use crate::VortexSessionExecute;
472 use crate::aggregate_fn::Accumulator;
473 use crate::aggregate_fn::AggregateDTypes;
474 use crate::aggregate_fn::AggregateFnVTable;
475 use crate::aggregate_fn::DynAccumulator;
476 use crate::aggregate_fn::EmptyOptions;
477 use crate::aggregate_fn::fns::is_constant::IsConstant;
478 use crate::aggregate_fn::fns::is_constant::IsConstantPartial;
479 use crate::aggregate_fn::fns::is_constant::is_constant;
480 use crate::array_session;
481 use crate::arrays::BoolArray;
482 use crate::arrays::ChunkedArray;
483 use crate::arrays::DecimalArray;
484 use crate::arrays::ListArray;
485 use crate::arrays::PrimitiveArray;
486 use crate::arrays::StructArray;
487 use crate::builders::MapBuilder;
488 use crate::dtype::DType;
489 use crate::dtype::DecimalDType;
490 use crate::dtype::FieldNames;
491 use crate::dtype::MapDType;
492 use crate::dtype::Nullability;
493 use crate::dtype::PType;
494 use crate::expr::stats::Stat;
495 use crate::scalar::Scalar;
496 use crate::validity::Validity;
497
498 type MapEntryFixture<'a> = (i32, Option<&'a str>);
499 type MapRowFixture<'a> = Option<Vec<MapEntryFixture<'a>>>;
500
501 fn map_array_from_rows(rows: &[MapRowFixture<'_>]) -> VortexResult<crate::ArrayRef> {
502 let map_dtype = MapDType::try_new(
503 DType::Primitive(PType::I32, Nullability::NonNullable),
504 DType::Utf8(Nullability::Nullable),
505 false,
506 )?;
507 let dtype = DType::Map(map_dtype.clone(), Nullability::Nullable);
508 let mut builder = MapBuilder::<u64, u64>::with_capacity_in(
509 map_dtype,
510 Nullability::Nullable,
511 rows.len(),
512 vortex_buffer::BufferAllocatorRef::static_ref(),
513 );
514
515 for row in rows {
516 let scalar = match row {
517 Some(entries) => {
518 let entries = entries
519 .iter()
520 .map(|(key, value)| {
521 let key = Scalar::primitive(*key, Nullability::NonNullable);
522 let value = value.map_or_else(
523 || Scalar::null(DType::Utf8(Nullability::Nullable)),
524 |value| Scalar::utf8(value, Nullability::Nullable),
525 );
526 (key, value)
527 })
528 .collect::<Vec<_>>();
529 Scalar::try_map(dtype.clone(), entries)?
530 }
531 None => Scalar::null(dtype.clone()),
532 };
533 builder.append_value(scalar.as_map())?;
534 }
535
536 Ok(builder.finish_into_map().into_array())
537 }
538
539 #[test]
541 fn is_constant_min_max_no_nan() -> VortexResult<()> {
542 let mut ctx = array_session().create_execution_ctx();
543
544 let arr = buffer![0, 1].into_array();
545 arr.statistics()
546 .compute_all(&[Stat::Min, Stat::Max], &mut ctx)?;
547 assert!(!is_constant(&arr, &mut ctx)?);
548
549 let arr = buffer![0, 0].into_array();
550 arr.statistics()
551 .compute_all(&[Stat::Min, Stat::Max], &mut ctx)?;
552 assert!(is_constant(&arr, &mut ctx)?);
553
554 let arr = PrimitiveArray::from_option_iter([Some(0), Some(0)]).into_array();
555 assert!(is_constant(&arr, &mut ctx)?);
556 Ok(())
557 }
558
559 #[test]
560 fn is_constant_min_max_with_nan() -> VortexResult<()> {
561 let mut ctx = array_session().create_execution_ctx();
562
563 let arr = PrimitiveArray::from_iter([0.0, 0.0, f32::NAN]).into_array();
564 arr.statistics()
565 .compute_all(&[Stat::Min, Stat::Max], &mut ctx)?;
566 assert!(!is_constant(&arr, &mut ctx)?);
567
568 let arr =
569 PrimitiveArray::from_option_iter([Some(f32::NEG_INFINITY), Some(f32::NEG_INFINITY)])
570 .into_array();
571 arr.statistics()
572 .compute_all(&[Stat::Min, Stat::Max], &mut ctx)?;
573 assert!(is_constant(&arr, &mut ctx)?);
574 Ok(())
575 }
576
577 #[rstest]
579 #[case(vec![true], true)]
580 #[case(vec![false; 65], true)]
581 #[case({
582 let mut v = vec![true; 64];
583 v.push(false);
584 v
585 }, false)]
586 fn test_bool_is_constant(#[case] input: Vec<bool>, #[case] expected: bool) -> VortexResult<()> {
587 let array = BoolArray::from_iter(input);
588 let mut ctx = array_session().create_execution_ctx();
589 assert_eq!(is_constant(&array.into_array(), &mut ctx)?, expected);
590 Ok(())
591 }
592
593 #[test]
595 fn empty_chunk_is_constant() -> VortexResult<()> {
596 let chunked = ChunkedArray::try_new(
597 vec![
598 Buffer::<u8>::empty().into_array(),
599 Buffer::<u8>::empty().into_array(),
600 buffer![255u8, 255].into_array(),
601 Buffer::<u8>::empty().into_array(),
602 buffer![255u8, 255].into_array(),
603 ],
604 DType::Primitive(PType::U8, Nullability::NonNullable),
605 )?
606 .into_array();
607
608 let mut ctx = array_session().create_execution_ctx();
609 assert!(is_constant(&chunked, &mut ctx)?);
610 Ok(())
611 }
612
613 #[test]
615 fn test_decimal_is_constant() -> VortexResult<()> {
616 let mut ctx = array_session().create_execution_ctx();
617
618 let array = DecimalArray::new(
619 buffer![0i128, 1i128, 2i128],
620 DecimalDType::new(19, 0),
621 Validity::NonNullable,
622 );
623 assert!(!is_constant(&array.into_array(), &mut ctx)?);
624
625 let array = DecimalArray::new(
626 buffer![100i128, 100i128, 100i128],
627 DecimalDType::new(19, 0),
628 Validity::NonNullable,
629 );
630 assert!(is_constant(&array.into_array(), &mut ctx)?);
631 Ok(())
632 }
633
634 #[test]
636 fn test_is_constant_nested_list() -> VortexResult<()> {
637 let mut ctx = array_session().create_execution_ctx();
638
639 let xs = ListArray::try_new(
640 buffer![0i32, 1, 0, 1].into_array(),
641 buffer![0u32, 2, 4].into_array(),
642 Validity::NonNullable,
643 )?;
644
645 let struct_of_lists = StructArray::try_new(
646 FieldNames::from(["xs"]),
647 vec![xs.into_array()],
648 2,
649 Validity::NonNullable,
650 )?;
651 assert!(is_constant(
652 &struct_of_lists.clone().into_array(),
653 &mut ctx
654 )?);
655 assert!(is_constant(&struct_of_lists.into_array(), &mut ctx)?);
656 Ok(())
657 }
658
659 #[rstest]
660 #[case(
661 vec![1i32, 2, 1, 2, 1, 2],
663 vec![0u32, 2, 4, 6],
664 true
665 )]
666 #[case(
667 vec![1i32, 2, 3, 4, 5],
669 vec![0u32, 2, 3, 5],
670 false
671 )]
672 #[case(
673 vec![1i32, 2, 3, 4],
675 vec![0u32, 2, 4],
676 false
677 )]
678 #[case(
679 vec![],
681 vec![0u32, 0, 0, 0],
682 true
683 )]
684 fn test_list_is_constant(
685 #[case] elements: Vec<i32>,
686 #[case] offsets: Vec<u32>,
687 #[case] expected: bool,
688 ) -> VortexResult<()> {
689 let list_array = ListArray::try_new(
690 PrimitiveArray::from_iter(elements).into_array(),
691 PrimitiveArray::from_iter(offsets).into_array(),
692 Validity::NonNullable,
693 )?;
694
695 let mut ctx = array_session().create_execution_ctx();
696 assert_eq!(is_constant(&list_array.into_array(), &mut ctx)?, expected);
697 Ok(())
698 }
699
700 #[test]
701 fn test_list_is_constant_nested_lists() -> VortexResult<()> {
702 let inner_elements = buffer![1i32, 2, 1, 2].into_array();
703 let inner_offsets = buffer![0u32, 1, 2, 3, 4].into_array();
704 let inner_lists = ListArray::try_new(inner_elements, inner_offsets, Validity::NonNullable)?;
705
706 let outer_offsets = buffer![0u32, 2, 4].into_array();
707 let outer_list = ListArray::try_new(
708 inner_lists.into_array(),
709 outer_offsets,
710 Validity::NonNullable,
711 )?;
712
713 let mut ctx = array_session().create_execution_ctx();
714 assert!(is_constant(&outer_list.into_array(), &mut ctx)?);
716 Ok(())
717 }
718
719 #[rstest]
720 #[case(
721 [1i32, 2].repeat(100),
723 (0..101).map(|i| (i * 2) as u32).collect(),
724 true
725 )]
726 #[case(
727 {
729 let mut elements = [1i32, 2].repeat(64);
730 elements.extend_from_slice(&[3, 4]);
731 elements
732 },
733 (0..66).map(|i| (i * 2) as u32).collect(),
734 false
735 )]
736 #[case(
737 {
739 let mut elements = [1i32, 2].repeat(63);
740 elements.extend_from_slice(&[3, 4]);
741 elements.extend([1i32, 2].repeat(37));
742 elements
743 },
744 (0..101).map(|i| (i * 2) as u32).collect(),
745 false
746 )]
747 fn test_list_is_constant_with_threshold(
748 #[case] elements: Vec<i32>,
749 #[case] offsets: Vec<u32>,
750 #[case] expected: bool,
751 ) -> VortexResult<()> {
752 let list_array = ListArray::try_new(
753 PrimitiveArray::from_iter(elements).into_array(),
754 PrimitiveArray::from_iter(offsets).into_array(),
755 Validity::NonNullable,
756 )?;
757
758 let mut ctx = array_session().create_execution_ctx();
759 assert_eq!(is_constant(&list_array.into_array(), &mut ctx)?, expected);
760 Ok(())
761 }
762
763 #[test]
764 fn test_map_is_constant() -> VortexResult<()> {
765 let mut ctx = array_session().create_execution_ctx();
766
767 let identical = map_array_from_rows(&[
768 Some(vec![(1, Some("one")), (2, None)]),
769 Some(vec![(1, Some("one")), (2, None)]),
770 ])?;
771 assert!(is_constant(&identical, &mut ctx)?);
772
773 let different = map_array_from_rows(&[
774 Some(vec![(1, Some("one")), (2, None)]),
775 Some(vec![(1, Some("one")), (3, None)]),
776 ])?;
777 assert!(!is_constant(&different, &mut ctx)?);
778
779 let all_null = map_array_from_rows(&[None, None])?;
780 assert!(is_constant(&all_null, &mut ctx)?);
781
782 Ok(())
783 }
784
785 #[test]
788 fn non_constant_merged_into_empty_keeps_verdict() -> VortexResult<()> {
789 let mut ctx = array_session().create_execution_ctx();
790 let dtype = DType::Primitive(PType::I32, Nullability::NonNullable);
791 let mut empty = Accumulator::try_new(IsConstant, EmptyOptions, dtype.clone())?;
792 let mut varying = Accumulator::try_new(IsConstant, EmptyOptions, dtype)?;
793
794 empty.accumulate(
796 &PrimitiveArray::new(Buffer::<i32>::empty(), Validity::NonNullable).into_array(),
797 &mut ctx,
798 )?;
799 varying.accumulate(&buffer![1i32, 2].into_array(), &mut ctx)?;
800 empty.merge_from(&mut varying)?;
801
802 assert!(!empty.partial_scalar()?.is_null());
803 assert_eq!(
804 empty.finish()?,
805 Scalar::bool(false, Nullability::NonNullable)
806 );
807 Ok(())
808 }
809
810 #[test]
812 fn non_constant_partial_without_value_is_not_empty() -> VortexResult<()> {
813 let dtype = DType::Primitive(PType::I32, Nullability::NonNullable);
814 let owned = AggregateDTypes::try_new(&IsConstant, &EmptyOptions, dtype)?;
815 let args = owned.args(&EmptyOptions);
816 let partial = IsConstantPartial {
817 is_constant: false,
818 first_value: None,
819 };
820
821 let scalar = IsConstant.to_scalar(args, &partial)?;
822 assert!(!scalar.is_null());
823 let parsed = IsConstant.partial_from_scalar(args, scalar)?;
824 assert_eq!(
825 IsConstant.finalize_scalar(args, &parsed)?,
826 Scalar::bool(false, Nullability::NonNullable)
827 );
828 Ok(())
829 }
830}