1mod bool;
5mod decimal;
6mod extension;
7mod fixed_size_list;
8mod list;
9pub mod primitive;
10mod struct_;
11mod varbin;
12
13use vortex_error::VortexExpect;
14use vortex_error::VortexResult;
15use vortex_error::vortex_bail;
16use vortex_session::registry::CachedId;
17
18use self::bool::check_bool_constant;
19use self::decimal::check_decimal_constant;
20use self::extension::check_extension_constant;
21use self::fixed_size_list::check_fixed_size_list_constant;
22use self::list::check_listview_constant;
23use self::primitive::check_primitive_constant;
24use self::struct_::check_struct_constant;
25use self::varbin::check_varbinview_constant;
26use crate::ArrayRef;
27use crate::Canonical;
28use crate::Columnar;
29use crate::ExecutionCtx;
30use crate::IntoArray;
31use crate::aggregate_fn::Accumulator;
32use crate::aggregate_fn::AggregateFnId;
33use crate::aggregate_fn::AggregateFnVTable;
34use crate::aggregate_fn::DynAccumulator;
35use crate::aggregate_fn::EmptyOptions;
36use crate::arrays::Constant;
37use crate::arrays::Null;
38use crate::builtins::ArrayBuiltins;
39use crate::dtype::DType;
40use crate::dtype::FieldNames;
41use crate::dtype::Nullability;
42use crate::dtype::StructFields;
43use crate::expr::stats::Precision;
44use crate::expr::stats::Stat;
45use crate::expr::stats::StatsProvider;
46use crate::expr::stats::StatsProviderExt;
47use crate::scalar::Scalar;
48use crate::scalar_fn::fns::operators::Operator;
49
50fn arrays_value_equal(a: &ArrayRef, b: &ArrayRef, ctx: &mut ExecutionCtx) -> VortexResult<bool> {
55 debug_assert_eq!(a.len(), b.len());
56 if a.is_empty() {
57 return Ok(true);
58 }
59
60 let a_mask = a.validity()?.execute_mask(a.len(), ctx)?;
62 let b_mask = b.validity()?.execute_mask(b.len(), ctx)?;
63 if a_mask != b_mask {
64 return Ok(false);
65 }
66
67 let valid_count = a_mask.true_count();
68 if valid_count == 0 {
69 return Ok(true);
71 }
72
73 let eq_result = a.binary(b.clone(), Operator::Eq)?;
76 let eq_result = eq_result.null_as_false().execute(ctx)?;
77
78 Ok(eq_result.true_count() == valid_count)
79}
80
81pub fn is_constant(array: &ArrayRef, ctx: &mut ExecutionCtx) -> VortexResult<bool> {
90 if let Precision::Exact(value) = array.statistics().get_as::<bool>(Stat::IsConstant) {
92 return Ok(value);
93 }
94
95 if array.is_empty() {
97 return Ok(false);
98 }
99
100 if array.len() == 1 {
102 array
103 .statistics()
104 .set(Stat::IsConstant, Precision::Exact(true.into()));
105 return Ok(true);
106 }
107
108 if array.is::<Constant>() || array.is::<Null>() {
110 array
111 .statistics()
112 .set(Stat::IsConstant, Precision::Exact(true.into()));
113 return Ok(true);
114 }
115
116 let all_invalid = array.all_invalid(ctx)?;
117 if all_invalid {
118 array
119 .statistics()
120 .set(Stat::IsConstant, Precision::Exact(true.into()));
121 return Ok(true);
122 }
123
124 let all_valid = array.all_valid(ctx)?;
125
126 if !all_valid && !all_invalid {
128 array
129 .statistics()
130 .set(Stat::IsConstant, Precision::Exact(false.into()));
131 return Ok(false);
132 }
133
134 let min_stat = array.statistics().get(Stat::Min);
136 let max_stat = array.statistics().get(Stat::Max);
137
138 if let Precision::Exact(min) = min_stat.as_ref()
139 && let Precision::Exact(max) = max_stat.as_ref()
140 && min == max
141 && (Stat::NaNCount.dtype(array.dtype()).is_none()
142 || array.statistics().get_as::<u64>(Stat::NaNCount) == Precision::exact(0u64))
143 {
144 array
145 .statistics()
146 .set(Stat::IsConstant, Precision::Exact(true.into()));
147 return Ok(true);
148 }
149
150 if IsConstant
152 .return_dtype(&EmptyOptions, array.dtype())
153 .is_none()
154 {
155 return Ok(false);
157 }
158
159 let mut acc = Accumulator::try_new(IsConstant, EmptyOptions, array.dtype().clone())?;
161 acc.accumulate(array, ctx)?;
162 let result_scalar = acc.finish()?;
163
164 let result = result_scalar.as_bool().value().unwrap_or(false);
165
166 array
168 .statistics()
169 .set(Stat::IsConstant, Precision::Exact(result.into()));
170
171 Ok(result)
172}
173
174#[derive(Clone, Debug)]
180pub struct IsConstant;
181
182impl IsConstant {
183 pub fn make_partial(
189 batch: &ArrayRef,
190 is_constant: bool,
191 ctx: &mut ExecutionCtx,
192 ) -> VortexResult<Scalar> {
193 let partial_dtype = make_is_constant_partial_dtype(batch.dtype());
194 if is_constant {
195 if batch.is_empty() {
196 return Ok(Scalar::null(partial_dtype));
197 }
198 let first_value = batch.execute_scalar(0, ctx)?.into_nullable();
199 Ok(Scalar::struct_(
200 partial_dtype,
201 vec![Scalar::bool(true, Nullability::NonNullable), first_value],
202 ))
203 } else {
204 Ok(Scalar::struct_(
205 partial_dtype,
206 vec![
207 Scalar::bool(false, Nullability::NonNullable),
208 Scalar::null(batch.dtype().as_nullable()),
209 ],
210 ))
211 }
212 }
213}
214
215pub struct IsConstantPartial {
217 is_constant: bool,
218 first_value: Option<Scalar>,
220 element_dtype: DType,
221}
222
223impl IsConstantPartial {
224 fn check_value(&mut self, value: Scalar) {
225 if !self.is_constant {
226 return;
227 }
228 match &self.first_value {
229 None => {
230 self.first_value = Some(value);
231 }
232 Some(first) => {
233 if *first != value {
234 self.is_constant = false;
235 }
236 }
237 }
238 }
239}
240
241static NAMES: std::sync::LazyLock<FieldNames> =
242 std::sync::LazyLock::new(|| FieldNames::from(["is_constant", "value"]));
243
244pub fn make_is_constant_partial_dtype(element_dtype: &DType) -> DType {
245 DType::Struct(
246 StructFields::new(
247 NAMES.clone(),
248 vec![
249 DType::Bool(Nullability::NonNullable),
250 element_dtype.as_nullable(),
251 ],
252 ),
253 Nullability::Nullable,
254 )
255}
256
257impl AggregateFnVTable for IsConstant {
258 type Options = EmptyOptions;
259 type Partial = IsConstantPartial;
260
261 fn id(&self) -> AggregateFnId {
262 static ID: CachedId = CachedId::new("vortex.is_constant");
263 *ID
264 }
265
266 fn serialize(&self, _options: &Self::Options) -> VortexResult<Option<Vec<u8>>> {
267 unimplemented!("IsConstant is not yet serializable");
268 }
269
270 fn return_dtype(&self, _options: &Self::Options, input_dtype: &DType) -> Option<DType> {
271 match input_dtype {
272 DType::Null | DType::Variant(..) => None,
273 _ => Some(DType::Bool(Nullability::NonNullable)),
274 }
275 }
276
277 fn partial_dtype(&self, _options: &Self::Options, input_dtype: &DType) -> Option<DType> {
278 match input_dtype {
279 DType::Null | DType::Variant(..) => None,
280 _ => Some(make_is_constant_partial_dtype(input_dtype)),
281 }
282 }
283
284 fn empty_partial(
285 &self,
286 _options: &Self::Options,
287 input_dtype: &DType,
288 ) -> VortexResult<Self::Partial> {
289 Ok(IsConstantPartial {
290 is_constant: true,
291 first_value: None,
292 element_dtype: input_dtype.clone(),
293 })
294 }
295
296 fn combine_partials(&self, partial: &mut Self::Partial, other: Scalar) -> VortexResult<()> {
297 if !partial.is_constant {
298 return Ok(());
299 }
300
301 if other.is_null() {
303 return Ok(());
304 }
305
306 let other_is_constant = other
307 .as_struct()
308 .field_by_idx(0)
309 .map(|s| s.as_bool().value().unwrap_or(false))
310 .unwrap_or(false);
311
312 if !other_is_constant {
313 partial.is_constant = false;
314 return Ok(());
315 }
316
317 let other_value = other.as_struct().field_by_idx(1);
318
319 if let Some(other_val) = other_value {
320 partial.check_value(other_val);
321 }
322
323 Ok(())
324 }
325
326 fn to_scalar(&self, partial: &Self::Partial) -> VortexResult<Scalar> {
327 let dtype = make_is_constant_partial_dtype(&partial.element_dtype);
328 Ok(match &partial.first_value {
329 None => {
330 Scalar::null(dtype)
332 }
333 Some(first_value) => Scalar::struct_(
334 dtype,
335 vec![
336 Scalar::bool(partial.is_constant, Nullability::NonNullable),
337 first_value
338 .clone()
339 .cast(&partial.element_dtype.as_nullable())?,
340 ],
341 ),
342 })
343 }
344
345 fn reset(&self, partial: &mut Self::Partial) {
346 partial.is_constant = true;
347 partial.first_value = None;
348 }
349
350 #[inline]
351 fn is_saturated(&self, partial: &Self::Partial) -> bool {
352 !partial.is_constant
353 }
354
355 fn accumulate(
356 &self,
357 partial: &mut Self::Partial,
358 batch: &Columnar,
359 ctx: &mut ExecutionCtx,
360 ) -> VortexResult<()> {
361 if !partial.is_constant {
362 return Ok(());
363 }
364
365 match batch {
366 Columnar::Constant(c) => {
367 partial.check_value(c.scalar().clone().into_nullable());
368 Ok(())
369 }
370 Columnar::Canonical(c) => {
371 if c.is_empty() {
372 return Ok(());
373 }
374
375 let array_ref = c.clone().into_array();
377
378 let all_invalid = array_ref.all_invalid(ctx)?;
379 if all_invalid {
380 partial.check_value(Scalar::null(partial.element_dtype.as_nullable()));
381 return Ok(());
382 }
383
384 let all_valid = array_ref.all_valid(ctx)?;
385 if !all_valid && !all_invalid {
387 partial.is_constant = false;
388 return Ok(());
389 }
390
391 if c.len() == 1 {
393 partial.check_value(array_ref.execute_scalar(0, ctx)?.into_nullable());
394 return Ok(());
395 }
396
397 let batch_is_constant = match c {
398 Canonical::Primitive(p) => check_primitive_constant(p),
399 Canonical::Bool(b) => check_bool_constant(b),
400 Canonical::VarBinView(v) => check_varbinview_constant(v),
401 Canonical::Decimal(d) => check_decimal_constant(d),
402 Canonical::Struct(s) => check_struct_constant(s, ctx)?,
403 Canonical::Extension(e) => check_extension_constant(e, ctx)?,
404 Canonical::List(l) => check_listview_constant(l, ctx)?,
405 Canonical::FixedSizeList(f) => check_fixed_size_list_constant(f, ctx)?,
406 Canonical::Null(_) => true,
407 Canonical::Union(_) => {
408 todo!("TODO(connor)[Union]: implement IsConstant for Union arrays")
409 }
410 Canonical::Variant(_) => {
411 vortex_bail!("Variant arrays don't support IsConstant")
412 }
413 };
414
415 if !batch_is_constant {
416 partial.is_constant = false;
417 return Ok(());
418 }
419
420 partial.check_value(array_ref.execute_scalar(0, ctx)?.into_nullable());
421 Ok(())
422 }
423 }
424 }
425
426 fn finalize(&self, partials: ArrayRef) -> VortexResult<ArrayRef> {
427 partials.get_item(NAMES.get(0).vortex_expect("out of bounds").clone())
428 }
429
430 fn finalize_scalar(&self, partial: &Self::Partial) -> VortexResult<Scalar> {
431 if partial.first_value.is_none() {
432 return Ok(Scalar::bool(false, Nullability::NonNullable));
434 }
435 Ok(Scalar::bool(partial.is_constant, Nullability::NonNullable))
436 }
437}
438
439#[cfg(test)]
440mod tests {
441 use rstest::rstest;
442 use vortex_buffer::Buffer;
443 use vortex_buffer::buffer;
444 use vortex_error::VortexResult;
445
446 use crate::IntoArray as _;
447 use crate::VortexSessionExecute;
448 use crate::aggregate_fn::fns::is_constant::is_constant;
449 use crate::array_session;
450 use crate::arrays::BoolArray;
451 use crate::arrays::ChunkedArray;
452 use crate::arrays::DecimalArray;
453 use crate::arrays::ListArray;
454 use crate::arrays::PrimitiveArray;
455 use crate::arrays::StructArray;
456 use crate::dtype::DType;
457 use crate::dtype::DecimalDType;
458 use crate::dtype::FieldNames;
459 use crate::dtype::Nullability;
460 use crate::dtype::PType;
461 use crate::expr::stats::Stat;
462 use crate::validity::Validity;
463
464 #[test]
466 fn is_constant_min_max_no_nan() -> VortexResult<()> {
467 let mut ctx = array_session().create_execution_ctx();
468
469 let arr = buffer![0, 1].into_array();
470 arr.statistics()
471 .compute_all(&[Stat::Min, Stat::Max], &mut ctx)?;
472 assert!(!is_constant(&arr, &mut ctx)?);
473
474 let arr = buffer![0, 0].into_array();
475 arr.statistics()
476 .compute_all(&[Stat::Min, Stat::Max], &mut ctx)?;
477 assert!(is_constant(&arr, &mut ctx)?);
478
479 let arr = PrimitiveArray::from_option_iter([Some(0), Some(0)]).into_array();
480 assert!(is_constant(&arr, &mut ctx)?);
481 Ok(())
482 }
483
484 #[test]
485 fn is_constant_min_max_with_nan() -> VortexResult<()> {
486 let mut ctx = array_session().create_execution_ctx();
487
488 let arr = PrimitiveArray::from_iter([0.0, 0.0, f32::NAN]).into_array();
489 arr.statistics()
490 .compute_all(&[Stat::Min, Stat::Max], &mut ctx)?;
491 assert!(!is_constant(&arr, &mut ctx)?);
492
493 let arr =
494 PrimitiveArray::from_option_iter([Some(f32::NEG_INFINITY), Some(f32::NEG_INFINITY)])
495 .into_array();
496 arr.statistics()
497 .compute_all(&[Stat::Min, Stat::Max], &mut ctx)?;
498 assert!(is_constant(&arr, &mut ctx)?);
499 Ok(())
500 }
501
502 #[rstest]
504 #[case(vec![true], true)]
505 #[case(vec![false; 65], true)]
506 #[case({
507 let mut v = vec![true; 64];
508 v.push(false);
509 v
510 }, false)]
511 fn test_bool_is_constant(#[case] input: Vec<bool>, #[case] expected: bool) -> VortexResult<()> {
512 let array = BoolArray::from_iter(input);
513 let mut ctx = array_session().create_execution_ctx();
514 assert_eq!(is_constant(&array.into_array(), &mut ctx)?, expected);
515 Ok(())
516 }
517
518 #[test]
520 fn empty_chunk_is_constant() -> VortexResult<()> {
521 let chunked = ChunkedArray::try_new(
522 vec![
523 Buffer::<u8>::empty().into_array(),
524 Buffer::<u8>::empty().into_array(),
525 buffer![255u8, 255].into_array(),
526 Buffer::<u8>::empty().into_array(),
527 buffer![255u8, 255].into_array(),
528 ],
529 DType::Primitive(PType::U8, Nullability::NonNullable),
530 )?
531 .into_array();
532
533 let mut ctx = array_session().create_execution_ctx();
534 assert!(is_constant(&chunked, &mut ctx)?);
535 Ok(())
536 }
537
538 #[test]
540 fn test_decimal_is_constant() -> VortexResult<()> {
541 let mut ctx = array_session().create_execution_ctx();
542
543 let array = DecimalArray::new(
544 buffer![0i128, 1i128, 2i128],
545 DecimalDType::new(19, 0),
546 Validity::NonNullable,
547 );
548 assert!(!is_constant(&array.into_array(), &mut ctx)?);
549
550 let array = DecimalArray::new(
551 buffer![100i128, 100i128, 100i128],
552 DecimalDType::new(19, 0),
553 Validity::NonNullable,
554 );
555 assert!(is_constant(&array.into_array(), &mut ctx)?);
556 Ok(())
557 }
558
559 #[test]
561 fn test_is_constant_nested_list() -> VortexResult<()> {
562 let mut ctx = array_session().create_execution_ctx();
563
564 let xs = ListArray::try_new(
565 buffer![0i32, 1, 0, 1].into_array(),
566 buffer![0u32, 2, 4].into_array(),
567 Validity::NonNullable,
568 )?;
569
570 let struct_of_lists = StructArray::try_new(
571 FieldNames::from(["xs"]),
572 vec![xs.into_array()],
573 2,
574 Validity::NonNullable,
575 )?;
576 assert!(is_constant(
577 &struct_of_lists.clone().into_array(),
578 &mut ctx
579 )?);
580 assert!(is_constant(&struct_of_lists.into_array(), &mut ctx)?);
581 Ok(())
582 }
583
584 #[rstest]
585 #[case(
586 vec![1i32, 2, 1, 2, 1, 2],
588 vec![0u32, 2, 4, 6],
589 true
590 )]
591 #[case(
592 vec![1i32, 2, 3, 4, 5],
594 vec![0u32, 2, 3, 5],
595 false
596 )]
597 #[case(
598 vec![1i32, 2, 3, 4],
600 vec![0u32, 2, 4],
601 false
602 )]
603 #[case(
604 vec![],
606 vec![0u32, 0, 0, 0],
607 true
608 )]
609 fn test_list_is_constant(
610 #[case] elements: Vec<i32>,
611 #[case] offsets: Vec<u32>,
612 #[case] expected: bool,
613 ) -> VortexResult<()> {
614 let list_array = ListArray::try_new(
615 PrimitiveArray::from_iter(elements).into_array(),
616 PrimitiveArray::from_iter(offsets).into_array(),
617 Validity::NonNullable,
618 )?;
619
620 let mut ctx = array_session().create_execution_ctx();
621 assert_eq!(is_constant(&list_array.into_array(), &mut ctx)?, expected);
622 Ok(())
623 }
624
625 #[test]
626 fn test_list_is_constant_nested_lists() -> VortexResult<()> {
627 let inner_elements = buffer![1i32, 2, 1, 2].into_array();
628 let inner_offsets = buffer![0u32, 1, 2, 3, 4].into_array();
629 let inner_lists = ListArray::try_new(inner_elements, inner_offsets, Validity::NonNullable)?;
630
631 let outer_offsets = buffer![0u32, 2, 4].into_array();
632 let outer_list = ListArray::try_new(
633 inner_lists.into_array(),
634 outer_offsets,
635 Validity::NonNullable,
636 )?;
637
638 let mut ctx = array_session().create_execution_ctx();
639 assert!(is_constant(&outer_list.into_array(), &mut ctx)?);
641 Ok(())
642 }
643
644 #[rstest]
645 #[case(
646 [1i32, 2].repeat(100),
648 (0..101).map(|i| (i * 2) as u32).collect(),
649 true
650 )]
651 #[case(
652 {
654 let mut elements = [1i32, 2].repeat(64);
655 elements.extend_from_slice(&[3, 4]);
656 elements
657 },
658 (0..66).map(|i| (i * 2) as u32).collect(),
659 false
660 )]
661 #[case(
662 {
664 let mut elements = [1i32, 2].repeat(63);
665 elements.extend_from_slice(&[3, 4]);
666 elements.extend([1i32, 2].repeat(37));
667 elements
668 },
669 (0..101).map(|i| (i * 2) as u32).collect(),
670 false
671 )]
672 fn test_list_is_constant_with_threshold(
673 #[case] elements: Vec<i32>,
674 #[case] offsets: Vec<u32>,
675 #[case] expected: bool,
676 ) -> VortexResult<()> {
677 let list_array = ListArray::try_new(
678 PrimitiveArray::from_iter(elements).into_array(),
679 PrimitiveArray::from_iter(offsets).into_array(),
680 Validity::NonNullable,
681 )?;
682
683 let mut ctx = array_session().create_execution_ctx();
684 assert_eq!(is_constant(&list_array.into_array(), &mut ctx)?, expected);
685 Ok(())
686 }
687}