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