1use std::any::type_name;
5use std::fmt::Debug;
6
7use enum_iterator::all;
8use num_traits::CheckedAdd;
9use smallvec::SmallVec;
10use smallvec::smallvec;
11use vortex_error::VortexError;
12use vortex_error::VortexExpect;
13use vortex_error::VortexResult;
14use vortex_error::vortex_err;
15use vortex_error::vortex_panic;
16
17use crate::dtype::DType;
18use crate::expr::stats::IsConstant;
19use crate::expr::stats::IsSorted;
20use crate::expr::stats::IsStrictSorted;
21use crate::expr::stats::Max;
22use crate::expr::stats::Min;
23use crate::expr::stats::NaNCount;
24use crate::expr::stats::NullCount;
25use crate::expr::stats::Precision;
26use crate::expr::stats::Stat;
27use crate::expr::stats::StatBound;
28use crate::expr::stats::StatType;
29use crate::expr::stats::StatsProvider;
30use crate::expr::stats::StatsProviderExt;
31use crate::expr::stats::Sum;
32use crate::expr::stats::UncompressedSizeInBytes;
33use crate::scalar::Scalar;
34use crate::scalar::ScalarValue;
35
36pub type StatsArray = [(Stat, Precision<ScalarValue>); 4];
38
39#[derive(Default, Debug, Clone)]
40pub struct StatsSet {
41 values: SmallVec<StatsArray>,
42}
43
44impl StatsSet {
45 pub unsafe fn new_unchecked(values: SmallVec<StatsArray>) -> Self {
51 Self { values }
52 }
53
54 pub fn of(stat: Stat, value: Precision<ScalarValue>) -> Self {
56 Self {
57 values: smallvec![(stat, value)],
58 }
59 }
60
61 pub fn as_mut_typed_ref<'a, 'b>(&'a mut self, dtype: &'b DType) -> MutTypedStatsSetRef<'a, 'b> {
63 MutTypedStatsSetRef {
64 values: self,
65 dtype,
66 }
67 }
68
69 pub fn as_typed_ref<'a, 'b>(&'a self, dtype: &'b DType) -> TypedStatsSetRef<'a, 'b> {
71 TypedStatsSetRef {
72 values: self,
73 dtype,
74 }
75 }
76}
77
78impl StatsSet {
80 pub fn set(&mut self, stat: Stat, value: Precision<ScalarValue>) {
82 if let Some(existing) = self.values.iter_mut().find(|(s, _)| *s == stat) {
83 *existing = (stat, value);
84 } else {
85 self.values.push((stat, value));
86 }
87 }
88
89 pub fn clear(&mut self, stat: Stat) {
91 self.values.retain(|(s, _)| *s != stat);
92 }
93
94 pub fn retain_only(&mut self, stats: &[Stat]) {
96 self.values.retain(|(s, _)| stats.contains(s));
97 }
98
99 pub fn iter(&self) -> impl Iterator<Item = &(Stat, Precision<ScalarValue>)> {
103 self.values.iter()
104 }
105
106 pub fn get(&self, stat: Stat) -> Precision<ScalarValue> {
108 self.get_ref(stat).map(ScalarValue::clone)
109 }
110
111 fn get_ref(&self, stat: Stat) -> Precision<&ScalarValue> {
113 self.values
114 .iter()
115 .find(|(s, _)| *s == stat)
116 .map(|(_, v)| v.as_ref())
117 .unwrap_or(Precision::Absent)
118 }
119
120 pub(super) fn get_usize(&self, stat: Stat) -> Precision<usize> {
122 self.get_ref(stat).map(|v| {
123 usize::try_from(v).unwrap_or_else(|err| {
124 vortex_panic!(
125 err,
126 "Failed to get stat {} as {}",
127 stat,
128 type_name::<usize>()
129 )
130 })
131 })
132 }
133
134 pub(super) fn get_bool(&self, stat: Stat) -> Precision<bool> {
137 self.get_ref(stat).map(|v| match v {
138 ScalarValue::Bool(b) => *b,
139 _ => vortex_panic!("Failed to get stat {} as bool, found {}", stat, v),
140 })
141 }
142
143 pub fn len(&self) -> usize {
145 self.values.len()
146 }
147
148 pub fn is_empty(&self) -> bool {
150 self.values.is_empty()
151 }
152
153 pub fn get_as<T: for<'a> TryFrom<&'a Scalar, Error = VortexError>>(
155 &self,
156 stat: Stat,
157 dtype: &DType,
158 ) -> Precision<T> {
159 self.get(stat).map(|v| {
160 T::try_from(
161 &Scalar::try_new(dtype.clone(), Some(v))
162 .vortex_expect("failed to construct a scalar statistic"),
163 )
164 .unwrap_or_else(|err| {
165 vortex_panic!(err, "Failed to get stat {} as {}", stat, type_name::<T>())
166 })
167 })
168 }
169}
170
171pub struct StatsSetIntoIter(smallvec::IntoIter<StatsArray>);
177
178impl Iterator for StatsSetIntoIter {
179 type Item = (Stat, Precision<ScalarValue>);
180
181 fn next(&mut self) -> Option<Self::Item> {
182 self.0.next()
183 }
184}
185
186impl IntoIterator for StatsSet {
187 type Item = (Stat, Precision<ScalarValue>);
188 type IntoIter = StatsSetIntoIter;
189
190 fn into_iter(self) -> Self::IntoIter {
191 StatsSetIntoIter(self.values.into_iter())
192 }
193}
194
195impl FromIterator<(Stat, Precision<ScalarValue>)> for StatsSet {
196 fn from_iter<T: IntoIterator<Item = (Stat, Precision<ScalarValue>)>>(iter: T) -> Self {
197 let iter = iter.into_iter();
198
199 let mut this = Self {
200 values: SmallVec::new(),
201 };
202 this.extend(iter);
203 this
204 }
205}
206
207impl Extend<(Stat, Precision<ScalarValue>)> for StatsSet {
208 #[inline]
209 fn extend<T: IntoIterator<Item = (Stat, Precision<ScalarValue>)>>(&mut self, iter: T) {
210 iter.into_iter()
211 .for_each(|(stat, value)| self.set(stat, value));
212 }
213}
214
215impl StatsSet {
217 pub fn merge_ordered(mut self, other: &Self, dtype: &DType) -> Self {
220 self.as_mut_typed_ref(dtype)
221 .merge_ordered(&other.as_typed_ref(dtype));
222 self
223 }
224
225 pub fn merge_unordered(mut self, other: &Self, dtype: &DType) -> Self {
228 self.as_mut_typed_ref(dtype)
229 .merge_unordered(&other.as_typed_ref(dtype));
230 self
231 }
232
233 pub fn combine_sets(&mut self, other: &Self, dtype: &DType) -> VortexResult<()> {
235 self.as_mut_typed_ref(dtype)
236 .combine_sets(&other.as_typed_ref(dtype))
237 }
238}
239
240fn stat_scalar(values: &StatsSet, stat: Stat, dtype: &DType) -> Precision<Scalar> {
242 values.get_ref(stat).map(|value| {
243 let stat_dtype = stat
244 .dtype(dtype)
245 .vortex_expect("Must have valid dtype if value is present");
246 Scalar::validate(&stat_dtype, Some(value))
247 .vortex_expect("failed to construct a scalar statistic");
248
249 unsafe { Scalar::new_unchecked(stat_dtype, Some(value.clone())) }
251 })
252}
253
254pub struct TypedStatsSetRef<'a, 'b> {
255 pub values: &'a StatsSet,
256 pub dtype: &'b DType,
257}
258
259impl StatsProvider for TypedStatsSetRef<'_, '_> {
260 fn get(&self, stat: Stat) -> Precision<Scalar> {
261 stat_scalar(self.values, stat, self.dtype)
262 }
263
264 fn len(&self) -> usize {
265 self.values.len()
266 }
267}
268
269pub struct MutTypedStatsSetRef<'a, 'b> {
270 pub values: &'a mut StatsSet,
271 pub dtype: &'b DType,
272}
273
274impl MutTypedStatsSetRef<'_, '_> {
275 pub fn set(&mut self, stat: Stat, value: Precision<ScalarValue>) {
277 self.values.set(stat, value);
278 }
279
280 pub fn clear(&mut self, stat: Stat) {
282 self.values.clear(stat);
283 }
284}
285
286impl StatsProvider for MutTypedStatsSetRef<'_, '_> {
287 fn get(&self, stat: Stat) -> Precision<Scalar> {
288 stat_scalar(self.values, stat, self.dtype)
289 }
290
291 fn len(&self) -> usize {
292 self.values.len()
293 }
294}
295
296impl MutTypedStatsSetRef<'_, '_> {
298 pub fn merge_ordered(mut self, other: &TypedStatsSetRef) -> Self {
301 for s in all::<Stat>() {
302 match s {
303 Stat::IsConstant => self.merge_is_constant(other),
304 Stat::IsSorted => self.merge_is_sorted(other),
305 Stat::IsStrictSorted => self.merge_is_strict_sorted(other),
306 Stat::Max => self.merge_max(other),
307 Stat::Min => self.merge_min(other),
308 Stat::Sum => self.merge_sum(other),
309 Stat::NullCount => self.merge_null_count(other),
310 Stat::UncompressedSizeInBytes => self.merge_uncompressed_size_in_bytes(other),
311 Stat::NaNCount => self.merge_nan_count(other),
312 }
313 }
314
315 self
316 }
317
318 pub fn merge_unordered(mut self, other: &TypedStatsSetRef) -> Self {
321 for s in all::<Stat>() {
322 if !s.is_commutative() {
323 self.clear(s);
324 continue;
325 }
326
327 match s {
328 Stat::IsConstant => self.merge_is_constant(other),
329 Stat::Max => self.merge_max(other),
330 Stat::Min => self.merge_min(other),
331 Stat::Sum => self.merge_sum(other),
332 Stat::NullCount => self.merge_null_count(other),
333 Stat::UncompressedSizeInBytes => self.merge_uncompressed_size_in_bytes(other),
334 Stat::IsSorted | Stat::IsStrictSorted => {
335 unreachable!("not commutative")
336 }
337 Stat::NaNCount => self.merge_nan_count(other),
338 }
339 }
340
341 self
342 }
343
344 pub fn combine_sets(&mut self, other: &TypedStatsSetRef) -> VortexResult<()> {
346 let other_stats: Vec<_> = other.values.iter().map(|(stat, _)| *stat).collect();
347 for s in other_stats {
348 match s {
349 Stat::Max => self.combine_bound::<Max>(other)?,
350 Stat::Min => self.combine_bound::<Min>(other)?,
351 Stat::UncompressedSizeInBytes => {
352 self.combine_bound::<UncompressedSizeInBytes>(other)?
353 }
354 Stat::IsConstant => self.combine_bool_stat::<IsConstant>(other)?,
355 Stat::IsSorted => self.combine_bool_stat::<IsSorted>(other)?,
356 Stat::IsStrictSorted => self.combine_bool_stat::<IsStrictSorted>(other)?,
357 Stat::NullCount => self.combine_bound::<NullCount>(other)?,
358 Stat::Sum => self.combine_bound::<Sum>(other)?,
359 Stat::NaNCount => self.combine_bound::<NaNCount>(other)?,
360 }
361 }
362 Ok(())
363 }
364
365 fn combine_bound<S: StatType<Scalar>>(&mut self, other: &TypedStatsSetRef) -> VortexResult<()>
366 where
367 S::Bound: StatBound<Scalar> + Debug + Eq + PartialEq,
368 {
369 match (self.get_scalar_bound::<S>(), other.get_scalar_bound::<S>()) {
370 (Some(m1), Some(m2)) => {
371 let meet = m1
372 .intersection(&m2)
373 .vortex_expect("can always compare scalar")
374 .ok_or_else(|| {
375 vortex_err!("{:?} bounds ({m1:?}, {m2:?}) do not overlap", S::STAT)
376 })?;
377 if meet != m1 {
378 self.set(
379 S::STAT,
380 meet.into_value().map(|s| {
381 s.into_value()
382 .vortex_expect("stat scalar value cannot be null")
383 }),
384 );
385 }
386 }
387 (None, Some(m)) => self.set(
388 S::STAT,
389 m.into_value().map(|s| {
390 s.into_value()
391 .vortex_expect("stat scalar value cannot be null")
392 }),
393 ),
394 (Some(_), _) => (),
395 (None, None) => self.clear(S::STAT),
396 }
397 Ok(())
398 }
399
400 fn combine_bool_stat<S: StatType<bool>>(&mut self, other: &TypedStatsSetRef) -> VortexResult<()>
401 where
402 S::Bound: StatBound<bool> + Debug + Eq + PartialEq,
403 {
404 match (
405 self.get_as_bound::<S, bool>(),
406 other.get_as_bound::<S, bool>(),
407 ) {
408 (Some(m1), Some(m2)) => {
409 let intersection = m1
410 .intersection(&m2)
411 .vortex_expect("can always compare boolean")
412 .ok_or_else(|| {
413 vortex_err!("{:?} bounds ({m1:?}, {m2:?}) do not overlap", S::STAT)
414 })?;
415 if intersection != m1 {
416 self.set(S::STAT, intersection.into_value().map(ScalarValue::from));
417 }
418 }
419 (None, Some(m)) => self.set(S::STAT, m.into_value().map(ScalarValue::from)),
420 (Some(_), None) => (),
421 (None, None) => self.clear(S::STAT),
422 }
423 Ok(())
424 }
425
426 fn merge_min(&mut self, other: &TypedStatsSetRef) {
427 match (
428 self.get_scalar_bound::<Min>(),
429 other.get_scalar_bound::<Min>(),
430 ) {
431 (Some(m1), Some(m2)) => {
432 let meet = m1.union(&m2).vortex_expect("can compare scalar");
433 if meet != m1 {
434 self.set(
435 Stat::Min,
436 meet.into_value().map(|s| {
437 s.into_value()
438 .vortex_expect("stat scalar value cannot be null")
439 }),
440 );
441 }
442 }
443 _ => self.clear(Stat::Min),
444 }
445 }
446
447 fn merge_max(&mut self, other: &TypedStatsSetRef) {
448 match (
449 self.get_scalar_bound::<Max>(),
450 other.get_scalar_bound::<Max>(),
451 ) {
452 (Some(m1), Some(m2)) => {
453 let meet = m1.union(&m2).vortex_expect("can compare scalar");
454 if meet != m1 {
455 self.set(
456 Stat::Max,
457 meet.into_value().map(|s| {
458 s.into_value()
459 .vortex_expect("stat scalar value cannot be null")
460 }),
461 );
462 }
463 }
464 _ => self.clear(Stat::Max),
465 }
466 }
467
468 fn merge_sum(&mut self, other: &TypedStatsSetRef) {
469 match (
470 self.get_scalar_bound::<Sum>(),
471 other.get_scalar_bound::<Sum>(),
472 ) {
473 (Some(m1), Some(m2)) => {
474 let merged = m1.zip(m2).as_exact().and_then(|(s1, s2)| match s1.dtype() {
476 DType::Primitive(..) => s1
477 .as_primitive()
478 .checked_add(&s2.as_primitive())
479 .and_then(|pscalar| pscalar.pvalue().map(ScalarValue::Primitive)),
480 DType::Decimal(decimal_dtype, _) => s1
483 .as_decimal()
484 .checked_binary_numeric(
485 &s2.as_decimal(),
486 crate::scalar::NumericOperator::Add,
487 )
488 .ok()
489 .flatten()
490 .and_then(|scalar| scalar.as_decimal().decimal_value())
491 .filter(|value| value.fits_in_precision(*decimal_dtype))
492 .map(ScalarValue::Decimal),
493 _ => None,
494 });
495
496 match merged {
497 Some(scalar_value) => self.set(Stat::Sum, Precision::Exact(scalar_value)),
498 None => self.clear(Stat::Sum),
501 }
502 }
503 _ => self.clear(Stat::Sum),
504 }
505 }
506
507 fn merge_is_constant(&mut self, other: &TypedStatsSetRef) {
508 let self_const = self.values.get_bool(Stat::IsConstant);
509 let other_const = other.values.get_bool(Stat::IsConstant);
510
511 match (self_const, other_const) {
512 (Precision::Exact(false), _) | (_, Precision::Exact(false)) => {
514 self.set(Stat::IsConstant, Precision::exact(false));
515 }
516 (Precision::Exact(true), Precision::Exact(true)) => {
517 let self_min = self.values.get_ref(Stat::Min).as_exact();
520 let other_min = other.values.get_ref(Stat::Min).as_exact();
521
522 match (self_min, other_min) {
523 (Some(self_min), Some(other_min)) => {
524 let is_constant = self_min == other_min;
525 self.set(Stat::IsConstant, Precision::exact(is_constant));
526 }
527 _ => self.clear(Stat::IsConstant),
530 }
531 }
532 _ => self.clear(Stat::IsConstant),
533 }
534 }
535
536 fn merge_is_sorted(&mut self, other: &TypedStatsSetRef) {
537 self.merge_sortedness_stat(other, Stat::IsSorted, PartialOrd::le)
538 }
539
540 fn merge_is_strict_sorted(&mut self, other: &TypedStatsSetRef) {
541 self.merge_sortedness_stat(other, Stat::IsStrictSorted, PartialOrd::lt)
542 }
543
544 fn merge_sortedness_stat<F: Fn(&Scalar, &Scalar) -> bool>(
545 &mut self,
546 other: &TypedStatsSetRef,
547 stat: Stat,
548 cmp: F,
549 ) {
550 if (Precision::Exact(true), Precision::Exact(true))
551 == (self.values.get_bool(stat), other.values.get_bool(stat))
552 {
553 if let (Some(self_max), Some(other_min)) = (
558 self.get_scalar_bound::<Max>().and_then(|v| v.max_value()),
559 other.get_scalar_bound::<Min>().and_then(|v| v.min_value()),
560 ) {
561 return if cmp(&self_max, &other_min) {
562 } else {
564 self.set(stat, Precision::inexact(false));
565 };
566 }
567 }
568 self.clear(stat);
569 }
570
571 fn merge_null_count(&mut self, other: &TypedStatsSetRef) {
572 self.merge_sum_stat(Stat::NullCount, other)
573 }
574
575 fn merge_nan_count(&mut self, other: &TypedStatsSetRef) {
576 self.merge_sum_stat(Stat::NaNCount, other)
577 }
578
579 fn merge_uncompressed_size_in_bytes(&mut self, other: &TypedStatsSetRef) {
580 self.merge_sum_stat(Stat::UncompressedSizeInBytes, other)
581 }
582
583 fn merge_sum_stat(&mut self, stat: Stat, other: &TypedStatsSetRef) {
584 let merged = self
585 .values
586 .get_usize(stat)
587 .zip(other.values.get_usize(stat))
588 .map(|(l, r)| ScalarValue::from(l + r));
589
590 if merged.is_absent() {
591 self.clear(stat);
592 } else {
593 self.set(stat, merged);
594 }
595 }
596}
597
598#[cfg(test)]
599mod test {
600 use enum_iterator::all;
601 use itertools::Itertools;
602 use rstest::rstest;
603 use smallvec::smallvec;
604
605 use crate::VortexSessionExecute;
606 use crate::array_session;
607 use crate::arrays::PrimitiveArray;
608 use crate::dtype::DType;
609 use crate::dtype::DecimalDType;
610 use crate::dtype::MAX_PRECISION;
611 use crate::dtype::NativeDecimalType;
612 use crate::dtype::Nullability;
613 use crate::dtype::PType;
614 use crate::dtype::i256;
615 use crate::expr::stats::IsConstant;
616 use crate::expr::stats::Precision;
617 use crate::expr::stats::Stat;
618 use crate::expr::stats::StatsProvider;
619 use crate::expr::stats::StatsProviderExt;
620 use crate::scalar::DecimalValue;
621 use crate::scalar::ScalarValue;
622 use crate::stats::StatsSet;
623 use crate::stats::stats_set::Scalar;
624
625 #[test]
626 fn test_iter() {
627 let set = unsafe {
629 StatsSet::new_unchecked(smallvec![
630 (Stat::Max, Precision::exact(100)),
631 (Stat::Min, Precision::exact(42)),
632 ])
633 };
634 let mut iter = set.iter();
635 let first = iter.next().unwrap().clone();
636 assert_eq!(first.0, Stat::Max);
637 assert_eq!(
638 first.1.map(
639 |f| i32::try_from(&Scalar::try_new(PType::I32.into(), Some(f)).unwrap()).unwrap()
640 ),
641 Precision::exact(100)
642 );
643 let snd = iter.next().unwrap().clone();
644 assert_eq!(snd.0, Stat::Min);
645 assert_eq!(
646 snd.1.map(
647 |s| i32::try_from(&Scalar::try_new(PType::I32.into(), Some(s)).unwrap()).unwrap()
648 ),
649 Precision::exact(42)
650 );
651 }
652
653 #[test]
654 fn into_iter() {
655 let mut set = unsafe {
657 StatsSet::new_unchecked(smallvec![
658 (Stat::Max, Precision::exact(100)),
659 (Stat::Min, Precision::exact(42)),
660 ])
661 }
662 .into_iter();
663 let (stat, first) = set.next().unwrap();
664 assert_eq!(stat, Stat::Max);
665 assert_eq!(
666 first.map(
667 |f| i32::try_from(&Scalar::try_new(PType::I32.into(), Some(f)).unwrap()).unwrap()
668 ),
669 Precision::exact(100)
670 );
671 let snd = set.next().unwrap();
672 assert_eq!(snd.0, Stat::Min);
673 assert_eq!(
674 snd.1.map(
675 |s| i32::try_from(&Scalar::try_new(PType::I32.into(), Some(s)).unwrap()).unwrap()
676 ),
677 Precision::exact(42)
678 );
679 }
680
681 #[test]
682 fn merge_sums_overflow_clears() {
683 let dtype = DType::Primitive(PType::I64, Nullability::NonNullable);
686 let merged = StatsSet::of(Stat::Sum, Precision::exact(i64::MAX))
687 .merge_ordered(&StatsSet::of(Stat::Sum, Precision::exact(i64::MAX)), &dtype);
688
689 assert!(merged.get(Stat::Sum).is_absent());
690 }
691
692 #[test]
693 fn merge_decimal_sums_out_of_precision_clears() {
694 let dtype = DType::Decimal(DecimalDType::new(70, 0), Nullability::NonNullable);
698 let max = ScalarValue::Decimal(DecimalValue::I256(
699 <i256 as NativeDecimalType>::MAX_BY_PRECISION[usize::from(MAX_PRECISION)],
700 ));
701 let merged = StatsSet::of(Stat::Sum, Precision::exact(max.clone()))
702 .merge_ordered(&StatsSet::of(Stat::Sum, Precision::exact(max)), &dtype);
703
704 assert!(merged.get(Stat::Sum).is_absent());
705 }
706
707 #[test]
708 fn merge_constant() {
709 let first = StatsSet::from_iter([
710 (Stat::Min, Precision::exact(42)),
711 (Stat::IsConstant, Precision::exact(true)),
712 ])
713 .merge_ordered(
714 &StatsSet::from_iter([
715 (Stat::Min, Precision::inexact(42)),
716 (Stat::IsConstant, Precision::exact(true)),
717 ]),
718 &DType::Primitive(PType::I32, Nullability::NonNullable),
719 );
720
721 let first_ref = first.as_typed_ref(&DType::Primitive(PType::I32, Nullability::NonNullable));
723 assert_eq!(
724 first_ref.get_as::<bool>(Stat::IsConstant),
725 Precision::Absent
726 );
727 assert_eq!(first_ref.get_as::<i32>(Stat::Min), Precision::exact(42));
728 }
729
730 #[rstest]
731 #[case::equal_mins(
732 Precision::exact(true),
733 42,
734 Precision::exact(true),
735 42,
736 Precision::exact(true)
737 )]
738 #[case::different_mins(
739 Precision::exact(true),
740 42,
741 Precision::exact(true),
742 7,
743 Precision::exact(false)
744 )]
745 #[case::first_not_constant(
746 Precision::exact(false),
747 42,
748 Precision::exact(true),
749 42,
750 Precision::exact(false)
751 )]
752 #[case::second_not_constant(
753 Precision::exact(true),
754 42,
755 Precision::exact(false),
756 42,
757 Precision::exact(false)
758 )]
759 #[case::inexact_constant(
760 Precision::inexact(true),
761 42,
762 Precision::exact(true),
763 42,
764 Precision::Absent
765 )]
766 fn merge_constant_parts(
767 #[case] first_const: Precision<bool>,
768 #[case] first_min: i32,
769 #[case] second_const: Precision<bool>,
770 #[case] second_min: i32,
771 #[case] expected: Precision<bool>,
772 ) {
773 let dtype = DType::Primitive(PType::I32, Nullability::NonNullable);
774 let stats = |is_constant: Precision<bool>, min: i32| {
775 StatsSet::from_iter([
776 (Stat::Min, Precision::exact(min)),
777 (Stat::IsConstant, is_constant.map(ScalarValue::from)),
778 ])
779 };
780
781 let merged =
782 stats(first_const, first_min).merge_ordered(&stats(second_const, second_min), &dtype);
783
784 assert_eq!(
785 merged.as_typed_ref(&dtype).get_as::<bool>(Stat::IsConstant),
786 expected
787 );
788 }
789
790 #[test]
791 fn merge_into_min() {
792 let first = StatsSet::of(Stat::Min, Precision::exact(42)).merge_ordered(
793 &StatsSet::default(),
794 &DType::Primitive(PType::I32, Nullability::NonNullable),
795 );
796
797 let first_ref = first.as_typed_ref(&DType::Primitive(PType::I32, Nullability::NonNullable));
798 assert!(first_ref.get(Stat::Min).is_absent());
799 }
800
801 #[test]
802 fn merge_from_min() {
803 let first = StatsSet::default().merge_ordered(
804 &StatsSet::of(Stat::Min, Precision::exact(42)),
805 &DType::Primitive(PType::I32, Nullability::NonNullable),
806 );
807
808 let first_ref = first.as_typed_ref(&DType::Primitive(PType::I32, Nullability::NonNullable));
809 assert!(first_ref.get(Stat::Min).is_absent());
810 }
811
812 #[test]
813 fn merge_mins() {
814 let first = StatsSet::of(Stat::Min, Precision::exact(37)).merge_ordered(
815 &StatsSet::of(Stat::Min, Precision::exact(42)),
816 &DType::Primitive(PType::I32, Nullability::NonNullable),
817 );
818
819 let first_ref = first.as_typed_ref(&DType::Primitive(PType::I32, Nullability::NonNullable));
820 assert_eq!(first_ref.get_as::<i32>(Stat::Min), Precision::exact(37));
821 }
822
823 #[test]
824 fn merge_into_bound_max() {
825 let first = StatsSet::of(Stat::Max, Precision::exact(42)).merge_ordered(
826 &StatsSet::default(),
827 &DType::Primitive(PType::I32, Nullability::NonNullable),
828 );
829 assert!(first.get(Stat::Max).is_absent());
830 }
831
832 #[test]
833 fn merge_from_max() {
834 let first = StatsSet::default().merge_ordered(
835 &StatsSet::of(Stat::Max, Precision::exact(42)),
836 &DType::Primitive(PType::I32, Nullability::NonNullable),
837 );
838 assert!(first.get(Stat::Max).is_absent());
839 }
840
841 #[test]
842 fn merge_maxes() {
843 let first = StatsSet::of(Stat::Max, Precision::exact(37)).merge_ordered(
844 &StatsSet::of(Stat::Max, Precision::exact(42)),
845 &DType::Primitive(PType::I32, Nullability::NonNullable),
846 );
847 let first_ref = first.as_typed_ref(&DType::Primitive(PType::I32, Nullability::NonNullable));
848 assert_eq!(first_ref.get_as::<i32>(Stat::Max), Precision::exact(42));
849 }
850
851 #[test]
852 fn merge_maxes_bound() {
853 let dtype = DType::Primitive(PType::I32, Nullability::NonNullable);
854 let first = StatsSet::of(Stat::Max, Precision::exact(42i32))
855 .merge_ordered(&StatsSet::of(Stat::Max, Precision::inexact(43i32)), &dtype);
856 let first_ref = first.as_typed_ref(&dtype);
857 assert_eq!(first_ref.get_as::<i32>(Stat::Max), Precision::inexact(43));
858 }
859
860 #[test]
861 fn merge_into_scalar() {
862 let first = StatsSet::of(Stat::Sum, Precision::exact(42i64)).merge_ordered(
865 &StatsSet::default(),
866 &DType::Primitive(PType::I32, Nullability::NonNullable),
867 );
868 let first_ref = first.as_typed_ref(&DType::Primitive(PType::I32, Nullability::NonNullable));
869 assert!(first_ref.get(Stat::Sum).is_absent());
870 }
871
872 #[test]
873 fn merge_from_scalar() {
874 let first = StatsSet::default().merge_ordered(
877 &StatsSet::of(Stat::Sum, Precision::exact(42i64)),
878 &DType::Primitive(PType::I32, Nullability::NonNullable),
879 );
880 let first_ref = first.as_typed_ref(&DType::Primitive(PType::I32, Nullability::NonNullable));
881 assert!(first_ref.get(Stat::Sum).is_absent());
882 }
883
884 #[test]
885 fn merge_scalars() {
886 let first = StatsSet::of(Stat::Sum, Precision::exact(37i64)).merge_ordered(
889 &StatsSet::of(Stat::Sum, Precision::exact(42i64)),
890 &DType::Primitive(PType::I32, Nullability::NonNullable),
891 );
892 let first_ref = first.as_typed_ref(&DType::Primitive(PType::I32, Nullability::NonNullable));
893 assert_eq!(first_ref.get_as::<i64>(Stat::Sum), Precision::exact(79i64));
894 }
895
896 #[test]
897 fn merge_into_sortedness() {
898 let first = StatsSet::of(Stat::IsStrictSorted, Precision::exact(true)).merge_ordered(
899 &StatsSet::default(),
900 &DType::Primitive(PType::I32, Nullability::NonNullable),
901 );
902 assert!(first.get(Stat::IsStrictSorted).is_absent());
903 }
904
905 #[test]
906 fn merge_from_sortedness() {
907 let first = StatsSet::default().merge_ordered(
908 &StatsSet::of(Stat::IsStrictSorted, Precision::exact(true)),
909 &DType::Primitive(PType::I32, Nullability::NonNullable),
910 );
911 assert!(first.get(Stat::IsStrictSorted).is_absent());
912 }
913
914 #[test]
915 fn merge_sortedness() {
916 let mut first = StatsSet::of(Stat::IsStrictSorted, Precision::exact(true));
917 first.set(Stat::Max, Precision::exact(1));
918 let mut second = StatsSet::of(Stat::IsStrictSorted, Precision::exact(true));
919 second.set(Stat::Min, Precision::exact(2));
920 first = first.merge_ordered(
921 &second,
922 &DType::Primitive(PType::I32, Nullability::NonNullable),
923 );
924
925 let first_ref = first.as_typed_ref(&DType::Primitive(PType::I32, Nullability::NonNullable));
926 assert_eq!(
927 first_ref.get_as::<bool>(Stat::IsStrictSorted),
928 Precision::exact(true)
929 );
930 }
931
932 #[test]
933 fn merge_sortedness_out_of_order() {
934 let mut first = StatsSet::of(Stat::IsStrictSorted, Precision::exact(true));
935 first.set(Stat::Min, Precision::exact(1));
936 let mut second = StatsSet::of(Stat::IsStrictSorted, Precision::exact(true));
937 second.set(Stat::Max, Precision::exact(2));
938 second = second.merge_ordered(
939 &first,
940 &DType::Primitive(PType::I32, Nullability::NonNullable),
941 );
942
943 let second_ref =
944 second.as_typed_ref(&DType::Primitive(PType::I32, Nullability::NonNullable));
945 assert_eq!(
946 second_ref.get_as::<bool>(Stat::IsStrictSorted),
947 Precision::inexact(false)
948 );
949 }
950
951 #[test]
952 fn merge_sortedness_only_one_sorted() {
953 let mut first = StatsSet::of(Stat::IsStrictSorted, Precision::exact(true));
954 first.set(Stat::Max, Precision::exact(1));
955 let mut second = StatsSet::of(Stat::IsStrictSorted, Precision::exact(false));
956 second.set(Stat::Min, Precision::exact(2));
957 first.merge_ordered(
958 &second,
959 &DType::Primitive(PType::I32, Nullability::NonNullable),
960 );
961
962 let second_ref =
963 second.as_typed_ref(&DType::Primitive(PType::I32, Nullability::NonNullable));
964 assert_eq!(
965 second_ref.get_as::<bool>(Stat::IsStrictSorted),
966 Precision::exact(false)
967 );
968 }
969
970 #[test]
971 fn merge_sortedness_missing_min() {
972 let mut first = StatsSet::of(Stat::IsStrictSorted, Precision::exact(true));
973 first.set(Stat::Max, Precision::exact(1));
974 let second = StatsSet::of(Stat::IsStrictSorted, Precision::exact(true));
975 first = first.merge_ordered(
976 &second,
977 &DType::Primitive(PType::I32, Nullability::NonNullable),
978 );
979 assert!(first.get(Stat::IsStrictSorted).is_absent());
980 }
981
982 #[test]
983 fn merge_sortedness_bound_min() {
984 let mut first = StatsSet::of(Stat::IsStrictSorted, Precision::exact(true));
985 first.set(Stat::Max, Precision::exact(1));
986 let mut second = StatsSet::of(Stat::IsStrictSorted, Precision::exact(true));
987 second.set(Stat::Min, Precision::inexact(2));
988 first = first.merge_ordered(
989 &second,
990 &DType::Primitive(PType::I32, Nullability::NonNullable),
991 );
992
993 let first_ref = first.as_typed_ref(&DType::Primitive(PType::I32, Nullability::NonNullable));
994 assert_eq!(
995 first_ref.get_as::<bool>(Stat::IsStrictSorted),
996 Precision::exact(true)
997 );
998 }
999
1000 #[test]
1001 fn merge_unordered() {
1002 let array =
1003 PrimitiveArray::from_option_iter([Some(1), None, Some(2), Some(42), Some(10000), None]);
1004 let all_stats = all::<Stat>()
1005 .filter(|s| !matches!(s, Stat::Sum))
1006 .filter(|s| !matches!(s, Stat::NaNCount))
1007 .collect_vec();
1008 array
1009 .statistics()
1010 .compute_all(&all_stats, &mut array_session().create_execution_ctx())
1011 .unwrap();
1012
1013 let stats = array.statistics().to_owned();
1014 for stat in &all_stats {
1015 assert!(!stats.get(*stat).is_absent(), "Stat {stat} is missing");
1016 }
1017
1018 let merged = stats.clone().merge_unordered(
1019 &stats,
1020 &DType::Primitive(PType::I32, Nullability::NonNullable),
1021 );
1022 for stat in &all_stats {
1023 assert_eq!(
1024 !merged.get(*stat).is_absent(),
1025 stat.is_commutative(),
1026 "Stat {stat} remains after merge_unordered despite not being commutative, or was removed despite being commutative"
1027 )
1028 }
1029
1030 let merged_ref = merged.as_typed_ref(&DType::Primitive(PType::I32, Nullability::Nullable));
1031 let stats_ref = stats.as_typed_ref(&DType::Primitive(PType::I32, Nullability::Nullable));
1032
1033 assert_eq!(
1034 merged_ref.get_as::<i32>(Stat::Min),
1035 stats_ref.get_as::<i32>(Stat::Min)
1036 );
1037 assert_eq!(
1038 merged_ref.get_as::<i32>(Stat::Max),
1039 stats_ref.get_as::<i32>(Stat::Max)
1040 );
1041 assert_eq!(
1042 merged_ref.get_as::<u64>(Stat::NullCount),
1043 stats_ref.get_as::<u64>(Stat::NullCount).map(|s| s * 2)
1044 );
1045 }
1046
1047 #[test]
1048 fn merge_min_bound_same() {
1049 let merged = StatsSet::of(Stat::Min, Precision::inexact(5)).merge_ordered(
1052 &StatsSet::of(Stat::Min, Precision::exact(5)),
1053 &DType::Primitive(PType::I32, Nullability::NonNullable),
1054 );
1055 let merged_ref =
1056 merged.as_typed_ref(&DType::Primitive(PType::I32, Nullability::NonNullable));
1057 assert_eq!(merged_ref.get_as::<i32>(Stat::Min), Precision::exact(5));
1058 }
1059
1060 #[test]
1061 fn merge_min_bound_bound_lower() {
1062 let merged = StatsSet::of(Stat::Min, Precision::inexact(4)).merge_ordered(
1063 &StatsSet::of(Stat::Min, Precision::exact(5)),
1064 &DType::Primitive(PType::I32, Nullability::NonNullable),
1065 );
1066 let merged_ref =
1067 merged.as_typed_ref(&DType::Primitive(PType::I32, Nullability::NonNullable));
1068 assert_eq!(merged_ref.get_as::<i32>(Stat::Min), Precision::inexact(4));
1069 }
1070
1071 #[test]
1072 fn test_combine_is_constant() {
1073 {
1074 let mut stats = StatsSet::of(Stat::IsConstant, Precision::exact(true));
1075 let stats2 = StatsSet::of(Stat::IsConstant, Precision::exact(true));
1076 let mut stats_ref =
1077 stats.as_mut_typed_ref(&DType::Primitive(PType::I32, Nullability::NonNullable));
1078 stats_ref
1079 .combine_bool_stat::<IsConstant>(
1080 &stats2.as_typed_ref(&DType::Primitive(PType::I32, Nullability::NonNullable)),
1081 )
1082 .unwrap();
1083 assert_eq!(
1084 stats_ref.get_as::<bool>(Stat::IsConstant),
1085 Precision::exact(true)
1086 );
1087 }
1088
1089 {
1090 let mut stats = StatsSet::of(Stat::IsConstant, Precision::exact(true));
1091 let stats2 = StatsSet::of(Stat::IsConstant, Precision::inexact(false));
1092 let mut stats_ref =
1093 stats.as_mut_typed_ref(&DType::Primitive(PType::I32, Nullability::NonNullable));
1094 stats_ref
1095 .combine_bool_stat::<IsConstant>(
1096 &stats2.as_typed_ref(&DType::Primitive(PType::I32, Nullability::NonNullable)),
1097 )
1098 .unwrap();
1099 assert_eq!(
1100 stats_ref.get_as::<bool>(Stat::IsConstant),
1101 Precision::exact(true)
1102 );
1103 }
1104
1105 {
1106 let mut stats = StatsSet::of(Stat::IsConstant, Precision::exact(false));
1107 let stats2 = StatsSet::of(Stat::IsConstant, Precision::inexact(false));
1108 let mut stats_ref =
1109 stats.as_mut_typed_ref(&DType::Primitive(PType::I32, Nullability::NonNullable));
1110 stats_ref
1111 .combine_bool_stat::<IsConstant>(
1112 &stats2.as_typed_ref(&DType::Primitive(PType::I32, Nullability::NonNullable)),
1113 )
1114 .unwrap();
1115 assert_eq!(
1116 stats_ref.get_as::<bool>(Stat::IsConstant),
1117 Precision::exact(false)
1118 );
1119 }
1120 }
1121
1122 #[test]
1123 fn test_combine_sets_boolean_conflict() {
1124 let mut stats1 = StatsSet::from_iter([
1125 (Stat::IsConstant, Precision::exact(true)),
1126 (Stat::IsSorted, Precision::exact(true)),
1127 ]);
1128
1129 let stats2 = StatsSet::from_iter([
1130 (Stat::IsConstant, Precision::exact(false)),
1131 (Stat::IsSorted, Precision::exact(true)),
1132 ]);
1133
1134 let result = stats1.combine_sets(
1135 &stats2,
1136 &DType::Primitive(PType::I32, Nullability::NonNullable),
1137 );
1138 assert!(result.is_err());
1139 }
1140
1141 #[test]
1142 fn test_combine_sets_with_missing_stats() {
1143 let mut stats1 = StatsSet::from_iter([
1144 (Stat::Min, Precision::exact(42)),
1145 (Stat::UncompressedSizeInBytes, Precision::exact(1000)),
1146 ]);
1147
1148 let stats2 = StatsSet::from_iter([
1149 (Stat::Max, Precision::exact(100)),
1150 (Stat::IsStrictSorted, Precision::exact(true)),
1151 ]);
1152
1153 stats1
1154 .combine_sets(
1155 &stats2,
1156 &DType::Primitive(PType::I32, Nullability::NonNullable),
1157 )
1158 .unwrap();
1159
1160 let stats_ref =
1161 stats1.as_typed_ref(&DType::Primitive(PType::I32, Nullability::NonNullable));
1162
1163 assert_eq!(stats_ref.get_as::<i32>(Stat::Min), Precision::exact(42));
1165 assert_eq!(stats_ref.get_as::<i32>(Stat::Max), Precision::exact(100));
1167 assert_eq!(
1169 stats_ref.get_as::<bool>(Stat::IsStrictSorted),
1170 Precision::exact(true)
1171 );
1172 }
1173
1174 #[test]
1175 fn test_combine_sets_with_inexact() {
1176 let mut stats1 = StatsSet::from_iter([
1177 (Stat::Min, Precision::exact(42)),
1178 (Stat::Max, Precision::inexact(100)),
1179 (Stat::IsConstant, Precision::exact(false)),
1180 ]);
1181
1182 let stats2 = StatsSet::from_iter([
1183 (Stat::Min, Precision::inexact(40)),
1185 (Stat::Max, Precision::exact(90)),
1186 (Stat::IsSorted, Precision::exact(true)),
1187 ]);
1188
1189 stats1
1190 .combine_sets(
1191 &stats2,
1192 &DType::Primitive(PType::I32, Nullability::NonNullable),
1193 )
1194 .unwrap();
1195
1196 let stats_ref =
1197 stats1.as_typed_ref(&DType::Primitive(PType::I32, Nullability::NonNullable));
1198
1199 assert_eq!(stats_ref.get_as::<i32>(Stat::Min), Precision::exact(42));
1201 assert_eq!(stats_ref.get_as::<i32>(Stat::Max), Precision::exact(90));
1203 assert_eq!(
1205 stats_ref.get_as::<bool>(Stat::IsSorted),
1206 Precision::exact(true)
1207 );
1208 }
1209}