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 assert!(iter.next().is_none());
652 }
653
654 #[test]
655 fn into_iter() {
656 let mut set = unsafe {
658 StatsSet::new_unchecked(smallvec![
659 (Stat::Max, Precision::exact(100)),
660 (Stat::Min, Precision::exact(42)),
661 ])
662 }
663 .into_iter();
664 let (stat, first) = set.next().unwrap();
665 assert_eq!(stat, Stat::Max);
666 assert_eq!(
667 first.map(
668 |f| i32::try_from(&Scalar::try_new(PType::I32.into(), Some(f)).unwrap()).unwrap()
669 ),
670 Precision::exact(100)
671 );
672 let snd = set.next().unwrap();
673 assert_eq!(snd.0, Stat::Min);
674 assert_eq!(
675 snd.1.map(
676 |s| i32::try_from(&Scalar::try_new(PType::I32.into(), Some(s)).unwrap()).unwrap()
677 ),
678 Precision::exact(42)
679 );
680 assert!(set.next().is_none());
681 }
682
683 #[test]
684 fn merge_sums_overflow_clears() {
685 let dtype = DType::Primitive(PType::I64, Nullability::NonNullable);
688 let merged = StatsSet::of(Stat::Sum, Precision::exact(i64::MAX))
689 .merge_ordered(&StatsSet::of(Stat::Sum, Precision::exact(i64::MAX)), &dtype);
690
691 assert!(merged.get(Stat::Sum).is_absent());
692 }
693
694 #[test]
695 fn merge_decimal_sums_out_of_precision_clears() {
696 let dtype = DType::Decimal(DecimalDType::new(70, 0), Nullability::NonNullable);
700 let max = ScalarValue::Decimal(DecimalValue::I256(
701 <i256 as NativeDecimalType>::MAX_BY_PRECISION[usize::from(MAX_PRECISION)],
702 ));
703 let merged = StatsSet::of(Stat::Sum, Precision::exact(max.clone()))
704 .merge_ordered(&StatsSet::of(Stat::Sum, Precision::exact(max)), &dtype);
705
706 assert!(merged.get(Stat::Sum).is_absent());
707 }
708
709 #[test]
710 fn merge_constant() {
711 let first = StatsSet::from_iter([
712 (Stat::Min, Precision::exact(42)),
713 (Stat::IsConstant, Precision::exact(true)),
714 ])
715 .merge_ordered(
716 &StatsSet::from_iter([
717 (Stat::Min, Precision::inexact(42)),
718 (Stat::IsConstant, Precision::exact(true)),
719 ]),
720 &DType::Primitive(PType::I32, Nullability::NonNullable),
721 );
722
723 let first_ref = first.as_typed_ref(&DType::Primitive(PType::I32, Nullability::NonNullable));
725 assert_eq!(
726 first_ref.get_as::<bool>(Stat::IsConstant),
727 Precision::Absent
728 );
729 assert_eq!(first_ref.get_as::<i32>(Stat::Min), Precision::exact(42));
730 }
731
732 #[rstest]
733 #[case::equal_mins(
734 Precision::exact(true),
735 42,
736 Precision::exact(true),
737 42,
738 Precision::exact(true)
739 )]
740 #[case::different_mins(
741 Precision::exact(true),
742 42,
743 Precision::exact(true),
744 7,
745 Precision::exact(false)
746 )]
747 #[case::first_not_constant(
748 Precision::exact(false),
749 42,
750 Precision::exact(true),
751 42,
752 Precision::exact(false)
753 )]
754 #[case::second_not_constant(
755 Precision::exact(true),
756 42,
757 Precision::exact(false),
758 42,
759 Precision::exact(false)
760 )]
761 #[case::inexact_constant(
762 Precision::inexact(true),
763 42,
764 Precision::exact(true),
765 42,
766 Precision::Absent
767 )]
768 fn merge_constant_parts(
769 #[case] first_const: Precision<bool>,
770 #[case] first_min: i32,
771 #[case] second_const: Precision<bool>,
772 #[case] second_min: i32,
773 #[case] expected: Precision<bool>,
774 ) {
775 let dtype = DType::Primitive(PType::I32, Nullability::NonNullable);
776 let stats = |is_constant: Precision<bool>, min: i32| {
777 StatsSet::from_iter([
778 (Stat::Min, Precision::exact(min)),
779 (Stat::IsConstant, is_constant.map(ScalarValue::from)),
780 ])
781 };
782
783 let merged =
784 stats(first_const, first_min).merge_ordered(&stats(second_const, second_min), &dtype);
785
786 assert_eq!(
787 merged.as_typed_ref(&dtype).get_as::<bool>(Stat::IsConstant),
788 expected
789 );
790 }
791
792 #[test]
793 fn merge_into_min() {
794 let first = StatsSet::of(Stat::Min, Precision::exact(42)).merge_ordered(
795 &StatsSet::default(),
796 &DType::Primitive(PType::I32, Nullability::NonNullable),
797 );
798
799 let first_ref = first.as_typed_ref(&DType::Primitive(PType::I32, Nullability::NonNullable));
800 assert!(first_ref.get(Stat::Min).is_absent());
801 }
802
803 #[test]
804 fn merge_from_min() {
805 let first = StatsSet::default().merge_ordered(
806 &StatsSet::of(Stat::Min, Precision::exact(42)),
807 &DType::Primitive(PType::I32, Nullability::NonNullable),
808 );
809
810 let first_ref = first.as_typed_ref(&DType::Primitive(PType::I32, Nullability::NonNullable));
811 assert!(first_ref.get(Stat::Min).is_absent());
812 }
813
814 #[test]
815 fn merge_mins() {
816 let first = StatsSet::of(Stat::Min, Precision::exact(37)).merge_ordered(
817 &StatsSet::of(Stat::Min, Precision::exact(42)),
818 &DType::Primitive(PType::I32, Nullability::NonNullable),
819 );
820
821 let first_ref = first.as_typed_ref(&DType::Primitive(PType::I32, Nullability::NonNullable));
822 assert_eq!(first_ref.get_as::<i32>(Stat::Min), Precision::exact(37));
823 }
824
825 #[test]
826 fn merge_into_bound_max() {
827 let first = StatsSet::of(Stat::Max, Precision::exact(42)).merge_ordered(
828 &StatsSet::default(),
829 &DType::Primitive(PType::I32, Nullability::NonNullable),
830 );
831 assert!(first.get(Stat::Max).is_absent());
832 }
833
834 #[test]
835 fn merge_from_max() {
836 let first = StatsSet::default().merge_ordered(
837 &StatsSet::of(Stat::Max, Precision::exact(42)),
838 &DType::Primitive(PType::I32, Nullability::NonNullable),
839 );
840 assert!(first.get(Stat::Max).is_absent());
841 }
842
843 #[test]
844 fn merge_maxes() {
845 let first = StatsSet::of(Stat::Max, Precision::exact(37)).merge_ordered(
846 &StatsSet::of(Stat::Max, Precision::exact(42)),
847 &DType::Primitive(PType::I32, Nullability::NonNullable),
848 );
849 let first_ref = first.as_typed_ref(&DType::Primitive(PType::I32, Nullability::NonNullable));
850 assert_eq!(first_ref.get_as::<i32>(Stat::Max), Precision::exact(42));
851 }
852
853 #[test]
854 fn merge_maxes_bound() {
855 let dtype = DType::Primitive(PType::I32, Nullability::NonNullable);
856 let first = StatsSet::of(Stat::Max, Precision::exact(42i32))
857 .merge_ordered(&StatsSet::of(Stat::Max, Precision::inexact(43i32)), &dtype);
858 let first_ref = first.as_typed_ref(&dtype);
859 assert_eq!(first_ref.get_as::<i32>(Stat::Max), Precision::inexact(43));
860 }
861
862 #[test]
863 fn merge_into_scalar() {
864 let first = StatsSet::of(Stat::Sum, Precision::exact(42i64)).merge_ordered(
867 &StatsSet::default(),
868 &DType::Primitive(PType::I32, Nullability::NonNullable),
869 );
870 let first_ref = first.as_typed_ref(&DType::Primitive(PType::I32, Nullability::NonNullable));
871 assert!(first_ref.get(Stat::Sum).is_absent());
872 }
873
874 #[test]
875 fn merge_from_scalar() {
876 let first = StatsSet::default().merge_ordered(
879 &StatsSet::of(Stat::Sum, Precision::exact(42i64)),
880 &DType::Primitive(PType::I32, Nullability::NonNullable),
881 );
882 let first_ref = first.as_typed_ref(&DType::Primitive(PType::I32, Nullability::NonNullable));
883 assert!(first_ref.get(Stat::Sum).is_absent());
884 }
885
886 #[test]
887 fn merge_scalars() {
888 let first = StatsSet::of(Stat::Sum, Precision::exact(37i64)).merge_ordered(
891 &StatsSet::of(Stat::Sum, Precision::exact(42i64)),
892 &DType::Primitive(PType::I32, Nullability::NonNullable),
893 );
894 let first_ref = first.as_typed_ref(&DType::Primitive(PType::I32, Nullability::NonNullable));
895 assert_eq!(first_ref.get_as::<i64>(Stat::Sum), Precision::exact(79i64));
896 }
897
898 #[test]
899 fn merge_into_sortedness() {
900 let first = StatsSet::of(Stat::IsStrictSorted, Precision::exact(true)).merge_ordered(
901 &StatsSet::default(),
902 &DType::Primitive(PType::I32, Nullability::NonNullable),
903 );
904 assert!(first.get(Stat::IsStrictSorted).is_absent());
905 }
906
907 #[test]
908 fn merge_from_sortedness() {
909 let first = StatsSet::default().merge_ordered(
910 &StatsSet::of(Stat::IsStrictSorted, Precision::exact(true)),
911 &DType::Primitive(PType::I32, Nullability::NonNullable),
912 );
913 assert!(first.get(Stat::IsStrictSorted).is_absent());
914 }
915
916 #[test]
917 fn merge_sortedness() {
918 let mut first = StatsSet::of(Stat::IsStrictSorted, Precision::exact(true));
919 first.set(Stat::Max, Precision::exact(1));
920 let mut second = StatsSet::of(Stat::IsStrictSorted, Precision::exact(true));
921 second.set(Stat::Min, Precision::exact(2));
922 first = first.merge_ordered(
923 &second,
924 &DType::Primitive(PType::I32, Nullability::NonNullable),
925 );
926
927 let first_ref = first.as_typed_ref(&DType::Primitive(PType::I32, Nullability::NonNullable));
928 assert_eq!(
929 first_ref.get_as::<bool>(Stat::IsStrictSorted),
930 Precision::exact(true)
931 );
932 }
933
934 #[test]
935 fn merge_sortedness_out_of_order() {
936 let mut first = StatsSet::of(Stat::IsStrictSorted, Precision::exact(true));
937 first.set(Stat::Min, Precision::exact(1));
938 let mut second = StatsSet::of(Stat::IsStrictSorted, Precision::exact(true));
939 second.set(Stat::Max, Precision::exact(2));
940 second = second.merge_ordered(
941 &first,
942 &DType::Primitive(PType::I32, Nullability::NonNullable),
943 );
944
945 let second_ref =
946 second.as_typed_ref(&DType::Primitive(PType::I32, Nullability::NonNullable));
947 assert_eq!(
948 second_ref.get_as::<bool>(Stat::IsStrictSorted),
949 Precision::inexact(false)
950 );
951 }
952
953 #[test]
954 fn merge_sortedness_only_one_sorted() {
955 let mut first = StatsSet::of(Stat::IsStrictSorted, Precision::exact(true));
956 first.set(Stat::Max, Precision::exact(1));
957 let mut second = StatsSet::of(Stat::IsStrictSorted, Precision::exact(false));
958 second.set(Stat::Min, Precision::exact(2));
959 let merged = first.merge_ordered(
960 &second,
961 &DType::Primitive(PType::I32, Nullability::NonNullable),
962 );
963
964 assert!(merged.get(Stat::IsStrictSorted).is_absent());
965 }
966
967 #[test]
968 fn merge_sortedness_missing_min() {
969 let mut first = StatsSet::of(Stat::IsStrictSorted, Precision::exact(true));
970 first.set(Stat::Max, Precision::exact(1));
971 let second = StatsSet::of(Stat::IsStrictSorted, Precision::exact(true));
972 first = first.merge_ordered(
973 &second,
974 &DType::Primitive(PType::I32, Nullability::NonNullable),
975 );
976 assert!(first.get(Stat::IsStrictSorted).is_absent());
977 }
978
979 #[test]
980 fn merge_sortedness_bound_min() {
981 let mut first = StatsSet::of(Stat::IsStrictSorted, Precision::exact(true));
982 first.set(Stat::Max, Precision::exact(1));
983 let mut second = StatsSet::of(Stat::IsStrictSorted, Precision::exact(true));
984 second.set(Stat::Min, Precision::inexact(2));
985 first = first.merge_ordered(
986 &second,
987 &DType::Primitive(PType::I32, Nullability::NonNullable),
988 );
989
990 let first_ref = first.as_typed_ref(&DType::Primitive(PType::I32, Nullability::NonNullable));
991 assert_eq!(
992 first_ref.get_as::<bool>(Stat::IsStrictSorted),
993 Precision::exact(true)
994 );
995 }
996
997 #[test]
998 fn merge_unordered() {
999 let array =
1000 PrimitiveArray::from_option_iter([Some(1), None, Some(2), Some(42), Some(10000), None]);
1001 let all_stats = all::<Stat>()
1002 .filter(|s| !matches!(s, Stat::Sum))
1003 .filter(|s| !matches!(s, Stat::NaNCount))
1004 .collect_vec();
1005 array
1006 .statistics()
1007 .compute_all(&all_stats, &mut array_session().create_execution_ctx())
1008 .unwrap();
1009
1010 let stats = array.statistics().to_owned();
1011 for stat in &all_stats {
1012 assert!(!stats.get(*stat).is_absent(), "Stat {stat} is missing");
1013 }
1014
1015 let merged = stats.clone().merge_unordered(
1016 &stats,
1017 &DType::Primitive(PType::I32, Nullability::NonNullable),
1018 );
1019 for (stat, expected) in [
1020 (Stat::Min, Precision::exact(ScalarValue::from(1i32))),
1021 (Stat::Max, Precision::exact(ScalarValue::from(10000i32))),
1022 (Stat::NullCount, Precision::exact(ScalarValue::from(4u64))),
1023 (
1024 Stat::UncompressedSizeInBytes,
1025 Precision::exact(ScalarValue::from(50u64)),
1026 ),
1027 (Stat::IsConstant, Precision::exact(ScalarValue::from(false))),
1028 (Stat::IsSorted, Precision::Absent),
1029 (Stat::IsStrictSorted, Precision::Absent),
1030 ] {
1031 assert_eq!(merged.get(stat), expected, "{stat}");
1032 }
1033 }
1034
1035 #[test]
1036 fn merge_min_bound_same() {
1037 let merged = StatsSet::of(Stat::Min, Precision::inexact(5)).merge_ordered(
1040 &StatsSet::of(Stat::Min, Precision::exact(5)),
1041 &DType::Primitive(PType::I32, Nullability::NonNullable),
1042 );
1043 let merged_ref =
1044 merged.as_typed_ref(&DType::Primitive(PType::I32, Nullability::NonNullable));
1045 assert_eq!(merged_ref.get_as::<i32>(Stat::Min), Precision::exact(5));
1046 }
1047
1048 #[test]
1049 fn merge_min_bound_bound_lower() {
1050 let merged = StatsSet::of(Stat::Min, Precision::inexact(4)).merge_ordered(
1051 &StatsSet::of(Stat::Min, Precision::exact(5)),
1052 &DType::Primitive(PType::I32, Nullability::NonNullable),
1053 );
1054 let merged_ref =
1055 merged.as_typed_ref(&DType::Primitive(PType::I32, Nullability::NonNullable));
1056 assert_eq!(merged_ref.get_as::<i32>(Stat::Min), Precision::inexact(4));
1057 }
1058
1059 #[test]
1060 fn test_combine_is_constant() {
1061 {
1062 let mut stats = StatsSet::of(Stat::IsConstant, Precision::exact(true));
1063 let stats2 = StatsSet::of(Stat::IsConstant, Precision::exact(true));
1064 let mut stats_ref =
1065 stats.as_mut_typed_ref(&DType::Primitive(PType::I32, Nullability::NonNullable));
1066 stats_ref
1067 .combine_bool_stat::<IsConstant>(
1068 &stats2.as_typed_ref(&DType::Primitive(PType::I32, Nullability::NonNullable)),
1069 )
1070 .unwrap();
1071 assert_eq!(
1072 stats_ref.get_as::<bool>(Stat::IsConstant),
1073 Precision::exact(true)
1074 );
1075 }
1076
1077 {
1078 let mut stats = StatsSet::of(Stat::IsConstant, Precision::exact(true));
1079 let stats2 = StatsSet::of(Stat::IsConstant, Precision::inexact(false));
1080 let mut stats_ref =
1081 stats.as_mut_typed_ref(&DType::Primitive(PType::I32, Nullability::NonNullable));
1082 stats_ref
1083 .combine_bool_stat::<IsConstant>(
1084 &stats2.as_typed_ref(&DType::Primitive(PType::I32, Nullability::NonNullable)),
1085 )
1086 .unwrap();
1087 assert_eq!(
1088 stats_ref.get_as::<bool>(Stat::IsConstant),
1089 Precision::exact(true)
1090 );
1091 }
1092
1093 {
1094 let mut stats = StatsSet::of(Stat::IsConstant, Precision::exact(false));
1095 let stats2 = StatsSet::of(Stat::IsConstant, Precision::inexact(false));
1096 let mut stats_ref =
1097 stats.as_mut_typed_ref(&DType::Primitive(PType::I32, Nullability::NonNullable));
1098 stats_ref
1099 .combine_bool_stat::<IsConstant>(
1100 &stats2.as_typed_ref(&DType::Primitive(PType::I32, Nullability::NonNullable)),
1101 )
1102 .unwrap();
1103 assert_eq!(
1104 stats_ref.get_as::<bool>(Stat::IsConstant),
1105 Precision::exact(false)
1106 );
1107 }
1108 }
1109
1110 #[test]
1111 fn test_combine_sets_boolean_conflict() {
1112 let mut stats1 = StatsSet::from_iter([
1113 (Stat::IsConstant, Precision::exact(true)),
1114 (Stat::IsSorted, Precision::exact(true)),
1115 ]);
1116
1117 let stats2 = StatsSet::from_iter([
1118 (Stat::IsConstant, Precision::exact(false)),
1119 (Stat::IsSorted, Precision::exact(true)),
1120 ]);
1121
1122 let result = stats1.combine_sets(
1123 &stats2,
1124 &DType::Primitive(PType::I32, Nullability::NonNullable),
1125 );
1126 assert!(result.is_err());
1127 }
1128
1129 #[test]
1130 fn test_combine_sets_with_missing_stats() {
1131 let mut stats1 = StatsSet::from_iter([
1132 (Stat::Min, Precision::exact(42)),
1133 (Stat::UncompressedSizeInBytes, Precision::exact(1000u64)),
1134 ]);
1135
1136 let stats2 = StatsSet::from_iter([
1137 (Stat::Max, Precision::exact(100)),
1138 (Stat::IsStrictSorted, Precision::exact(true)),
1139 ]);
1140
1141 stats1
1142 .combine_sets(
1143 &stats2,
1144 &DType::Primitive(PType::I32, Nullability::NonNullable),
1145 )
1146 .unwrap();
1147
1148 let stats_ref =
1149 stats1.as_typed_ref(&DType::Primitive(PType::I32, Nullability::NonNullable));
1150
1151 assert_eq!(
1152 stats_ref.get_as::<u64>(Stat::UncompressedSizeInBytes),
1153 Precision::exact(1000u64)
1154 );
1155 assert_eq!(stats_ref.get_as::<i32>(Stat::Min), Precision::exact(42));
1157 assert_eq!(stats_ref.get_as::<i32>(Stat::Max), Precision::exact(100));
1159 assert_eq!(
1161 stats_ref.get_as::<bool>(Stat::IsStrictSorted),
1162 Precision::exact(true)
1163 );
1164 }
1165
1166 #[test]
1167 fn test_combine_sets_with_inexact() {
1168 let mut stats1 = StatsSet::from_iter([
1169 (Stat::Min, Precision::exact(42)),
1170 (Stat::Max, Precision::inexact(100)),
1171 (Stat::IsConstant, Precision::exact(false)),
1172 ]);
1173
1174 let stats2 = StatsSet::from_iter([
1175 (Stat::Min, Precision::inexact(40)),
1177 (Stat::Max, Precision::exact(90)),
1178 (Stat::IsSorted, Precision::exact(true)),
1179 ]);
1180
1181 stats1
1182 .combine_sets(
1183 &stats2,
1184 &DType::Primitive(PType::I32, Nullability::NonNullable),
1185 )
1186 .unwrap();
1187
1188 let stats_ref =
1189 stats1.as_typed_ref(&DType::Primitive(PType::I32, Nullability::NonNullable));
1190
1191 assert_eq!(
1192 stats_ref.get_as::<bool>(Stat::IsConstant),
1193 Precision::exact(false)
1194 );
1195 assert_eq!(stats_ref.get_as::<i32>(Stat::Min), Precision::exact(42));
1197 assert_eq!(stats_ref.get_as::<i32>(Stat::Max), Precision::exact(90));
1199 assert_eq!(
1201 stats_ref.get_as::<bool>(Stat::IsSorted),
1202 Precision::exact(true)
1203 );
1204 }
1205}