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.values
109 .iter()
110 .find(|(s, _)| *s == stat)
111 .map(|(_, v)| v.clone())
112 .unwrap_or(Precision::Absent)
113 }
114
115 pub fn len(&self) -> usize {
117 self.values.len()
118 }
119
120 pub fn is_empty(&self) -> bool {
122 self.values.is_empty()
123 }
124
125 pub fn get_as<T: for<'a> TryFrom<&'a Scalar, Error = VortexError>>(
127 &self,
128 stat: Stat,
129 dtype: &DType,
130 ) -> Precision<T> {
131 self.get(stat).map(|v| {
132 T::try_from(
133 &Scalar::try_new(dtype.clone(), Some(v))
134 .vortex_expect("failed to construct a scalar statistic"),
135 )
136 .unwrap_or_else(|err| {
137 vortex_panic!(err, "Failed to get stat {} as {}", stat, type_name::<T>())
138 })
139 })
140 }
141}
142
143pub struct StatsSetIntoIter(smallvec::IntoIter<StatsArray>);
149
150impl Iterator for StatsSetIntoIter {
151 type Item = (Stat, Precision<ScalarValue>);
152
153 fn next(&mut self) -> Option<Self::Item> {
154 self.0.next()
155 }
156}
157
158impl IntoIterator for StatsSet {
159 type Item = (Stat, Precision<ScalarValue>);
160 type IntoIter = StatsSetIntoIter;
161
162 fn into_iter(self) -> Self::IntoIter {
163 StatsSetIntoIter(self.values.into_iter())
164 }
165}
166
167impl FromIterator<(Stat, Precision<ScalarValue>)> for StatsSet {
168 fn from_iter<T: IntoIterator<Item = (Stat, Precision<ScalarValue>)>>(iter: T) -> Self {
169 let iter = iter.into_iter();
170
171 let mut this = Self {
172 values: SmallVec::new(),
173 };
174 this.extend(iter);
175 this
176 }
177}
178
179impl Extend<(Stat, Precision<ScalarValue>)> for StatsSet {
180 #[inline]
181 fn extend<T: IntoIterator<Item = (Stat, Precision<ScalarValue>)>>(&mut self, iter: T) {
182 iter.into_iter()
183 .for_each(|(stat, value)| self.set(stat, value));
184 }
185}
186
187impl StatsSet {
189 pub fn merge_ordered(mut self, other: &Self, dtype: &DType) -> Self {
192 self.as_mut_typed_ref(dtype)
193 .merge_ordered(&other.as_typed_ref(dtype));
194 self
195 }
196
197 pub fn merge_unordered(mut self, other: &Self, dtype: &DType) -> Self {
200 self.as_mut_typed_ref(dtype)
201 .merge_unordered(&other.as_typed_ref(dtype));
202 self
203 }
204
205 pub fn combine_sets(&mut self, other: &Self, dtype: &DType) -> VortexResult<()> {
207 self.as_mut_typed_ref(dtype)
208 .combine_sets(&other.as_typed_ref(dtype))
209 }
210}
211
212pub struct TypedStatsSetRef<'a, 'b> {
213 pub values: &'a StatsSet,
214 pub dtype: &'b DType,
215}
216
217impl StatsProvider for TypedStatsSetRef<'_, '_> {
218 fn get(&self, stat: Stat) -> Precision<Scalar> {
219 self.values.get(stat).map(|sv| {
220 Scalar::try_new(
221 stat.dtype(self.dtype)
222 .vortex_expect("Must have valid dtype if value is present"),
223 Some(sv),
224 )
225 .vortex_expect("failed to construct a scalar statistic")
226 })
227 }
228
229 fn len(&self) -> usize {
230 self.values.len()
231 }
232}
233
234pub struct MutTypedStatsSetRef<'a, 'b> {
235 pub values: &'a mut StatsSet,
236 pub dtype: &'b DType,
237}
238
239impl MutTypedStatsSetRef<'_, '_> {
240 pub fn set(&mut self, stat: Stat, value: Precision<ScalarValue>) {
242 self.values.set(stat, value);
243 }
244
245 pub fn clear(&mut self, stat: Stat) {
247 self.values.clear(stat);
248 }
249}
250
251impl StatsProvider for MutTypedStatsSetRef<'_, '_> {
252 fn get(&self, stat: Stat) -> Precision<Scalar> {
253 self.values.get(stat).map(|sv| {
254 Scalar::try_new(
255 stat.dtype(self.dtype)
256 .vortex_expect("Must have valid dtype if value is present"),
257 Some(sv),
258 )
259 .vortex_expect("failed to construct a scalar statistic")
260 })
261 }
262
263 fn len(&self) -> usize {
264 self.values.len()
265 }
266}
267
268impl MutTypedStatsSetRef<'_, '_> {
270 pub fn merge_ordered(mut self, other: &TypedStatsSetRef) -> Self {
273 for s in all::<Stat>() {
274 match s {
275 Stat::IsConstant => self.merge_is_constant(other),
276 Stat::IsSorted => self.merge_is_sorted(other),
277 Stat::IsStrictSorted => self.merge_is_strict_sorted(other),
278 Stat::Max => self.merge_max(other),
279 Stat::Min => self.merge_min(other),
280 Stat::Sum => self.merge_sum(other),
281 Stat::NullCount => self.merge_null_count(other),
282 Stat::UncompressedSizeInBytes => self.merge_uncompressed_size_in_bytes(other),
283 Stat::NaNCount => self.merge_nan_count(other),
284 }
285 }
286
287 self
288 }
289
290 pub fn merge_unordered(mut self, other: &TypedStatsSetRef) -> Self {
293 for s in all::<Stat>() {
294 if !s.is_commutative() {
295 self.clear(s);
296 continue;
297 }
298
299 match s {
300 Stat::IsConstant => self.merge_is_constant(other),
301 Stat::Max => self.merge_max(other),
302 Stat::Min => self.merge_min(other),
303 Stat::Sum => self.merge_sum(other),
304 Stat::NullCount => self.merge_null_count(other),
305 Stat::UncompressedSizeInBytes => self.merge_uncompressed_size_in_bytes(other),
306 Stat::IsSorted | Stat::IsStrictSorted => {
307 unreachable!("not commutative")
308 }
309 Stat::NaNCount => self.merge_nan_count(other),
310 }
311 }
312
313 self
314 }
315
316 pub fn combine_sets(&mut self, other: &TypedStatsSetRef) -> VortexResult<()> {
318 let other_stats: Vec<_> = other.values.iter().map(|(stat, _)| *stat).collect();
319 for s in other_stats {
320 match s {
321 Stat::Max => self.combine_bound::<Max>(other)?,
322 Stat::Min => self.combine_bound::<Min>(other)?,
323 Stat::UncompressedSizeInBytes => {
324 self.combine_bound::<UncompressedSizeInBytes>(other)?
325 }
326 Stat::IsConstant => self.combine_bool_stat::<IsConstant>(other)?,
327 Stat::IsSorted => self.combine_bool_stat::<IsSorted>(other)?,
328 Stat::IsStrictSorted => self.combine_bool_stat::<IsStrictSorted>(other)?,
329 Stat::NullCount => self.combine_bound::<NullCount>(other)?,
330 Stat::Sum => self.combine_bound::<Sum>(other)?,
331 Stat::NaNCount => self.combine_bound::<NaNCount>(other)?,
332 }
333 }
334 Ok(())
335 }
336
337 fn combine_bound<S: StatType<Scalar>>(&mut self, other: &TypedStatsSetRef) -> VortexResult<()>
338 where
339 S::Bound: StatBound<Scalar> + Debug + Eq + PartialEq,
340 {
341 match (self.get_scalar_bound::<S>(), other.get_scalar_bound::<S>()) {
342 (Some(m1), Some(m2)) => {
343 let meet = m1
344 .intersection(&m2)
345 .vortex_expect("can always compare scalar")
346 .ok_or_else(|| {
347 vortex_err!("{:?} bounds ({m1:?}, {m2:?}) do not overlap", S::STAT)
348 })?;
349 if meet != m1 {
350 self.set(
351 S::STAT,
352 meet.into_value().map(|s| {
353 s.into_value()
354 .vortex_expect("stat scalar value cannot be null")
355 }),
356 );
357 }
358 }
359 (None, Some(m)) => self.set(
360 S::STAT,
361 m.into_value().map(|s| {
362 s.into_value()
363 .vortex_expect("stat scalar value cannot be null")
364 }),
365 ),
366 (Some(_), _) => (),
367 (None, None) => self.clear(S::STAT),
368 }
369 Ok(())
370 }
371
372 fn combine_bool_stat<S: StatType<bool>>(&mut self, other: &TypedStatsSetRef) -> VortexResult<()>
373 where
374 S::Bound: StatBound<bool> + Debug + Eq + PartialEq,
375 {
376 match (
377 self.get_as_bound::<S, bool>(),
378 other.get_as_bound::<S, bool>(),
379 ) {
380 (Some(m1), Some(m2)) => {
381 let intersection = m1
382 .intersection(&m2)
383 .vortex_expect("can always compare boolean")
384 .ok_or_else(|| {
385 vortex_err!("{:?} bounds ({m1:?}, {m2:?}) do not overlap", S::STAT)
386 })?;
387 if intersection != m1 {
388 self.set(S::STAT, intersection.into_value().map(ScalarValue::from));
389 }
390 }
391 (None, Some(m)) => self.set(S::STAT, m.into_value().map(ScalarValue::from)),
392 (Some(_), None) => (),
393 (None, None) => self.clear(S::STAT),
394 }
395 Ok(())
396 }
397
398 fn merge_min(&mut self, other: &TypedStatsSetRef) {
399 match (
400 self.get_scalar_bound::<Min>(),
401 other.get_scalar_bound::<Min>(),
402 ) {
403 (Some(m1), Some(m2)) => {
404 let meet = m1.union(&m2).vortex_expect("can compare scalar");
405 if meet != m1 {
406 self.set(
407 Stat::Min,
408 meet.into_value().map(|s| {
409 s.into_value()
410 .vortex_expect("stat scalar value cannot be null")
411 }),
412 );
413 }
414 }
415 _ => self.clear(Stat::Min),
416 }
417 }
418
419 fn merge_max(&mut self, other: &TypedStatsSetRef) {
420 match (
421 self.get_scalar_bound::<Max>(),
422 other.get_scalar_bound::<Max>(),
423 ) {
424 (Some(m1), Some(m2)) => {
425 let meet = m1.union(&m2).vortex_expect("can compare scalar");
426 if meet != m1 {
427 self.set(
428 Stat::Max,
429 meet.into_value().map(|s| {
430 s.into_value()
431 .vortex_expect("stat scalar value cannot be null")
432 }),
433 );
434 }
435 }
436 _ => self.clear(Stat::Max),
437 }
438 }
439
440 fn merge_sum(&mut self, other: &TypedStatsSetRef) {
441 match (
442 self.get_scalar_bound::<Sum>(),
443 other.get_scalar_bound::<Sum>(),
444 ) {
445 (Some(m1), Some(m2)) => {
446 if let Some(scalar_value) =
448 m1.zip(m2).as_exact().and_then(|(s1, s2)| match s1.dtype() {
449 DType::Primitive(..) => s1
450 .as_primitive()
451 .checked_add(&s2.as_primitive())
452 .and_then(|pscalar| pscalar.pvalue().map(ScalarValue::Primitive)),
453 DType::Decimal(..) => s1
454 .as_decimal()
455 .checked_binary_numeric(
456 &s2.as_decimal(),
457 crate::scalar::NumericOperator::Add,
458 )
459 .map(|scalar| {
460 ScalarValue::Decimal(
461 scalar
462 .decimal_value()
463 .vortex_expect("no decimal value in scalar"),
464 )
465 }),
466 _ => None,
467 })
468 {
469 self.set(Stat::Sum, Precision::Exact(scalar_value));
470 }
471 }
472 _ => self.clear(Stat::Sum),
473 }
474 }
475
476 fn merge_is_constant(&mut self, other: &TypedStatsSetRef) {
477 let self_const = self.get_as(Stat::IsConstant);
478 let other_const = other.get_as(Stat::IsConstant);
479 let self_min = self.get(Stat::Min);
480 let other_min = other.get(Stat::Min);
481
482 if let (Some(self_const), Some(other_const), Some(self_min), Some(other_min)) = (
483 self_const.as_exact(),
484 other_const.as_exact(),
485 self_min.as_exact(),
486 other_min.as_exact(),
487 ) {
488 if self_const && other_const && self_min == other_min {
489 self.set(Stat::IsConstant, Precision::exact(true));
490 } else {
491 self.set(Stat::IsConstant, Precision::inexact(false));
492 }
493 }
494 self.set(Stat::IsConstant, Precision::exact(false));
495 }
496
497 fn merge_is_sorted(&mut self, other: &TypedStatsSetRef) {
498 self.merge_sortedness_stat(other, Stat::IsSorted, PartialOrd::le)
499 }
500
501 fn merge_is_strict_sorted(&mut self, other: &TypedStatsSetRef) {
502 self.merge_sortedness_stat(other, Stat::IsStrictSorted, PartialOrd::lt)
503 }
504
505 fn merge_sortedness_stat<F: Fn(&Scalar, &Scalar) -> bool>(
506 &mut self,
507 other: &TypedStatsSetRef,
508 stat: Stat,
509 cmp: F,
510 ) {
511 if (Precision::Exact(true), Precision::Exact(true))
512 == (self.get_as(stat), other.get_as(stat))
513 {
514 if let (Some(self_max), Some(other_min)) = (
519 self.get_scalar_bound::<Max>().and_then(|v| v.max_value()),
520 other.get_scalar_bound::<Min>().and_then(|v| v.min_value()),
521 ) {
522 return if cmp(&self_max, &other_min) {
523 } else {
525 self.set(stat, Precision::inexact(false));
526 };
527 }
528 }
529 self.clear(stat);
530 }
531
532 fn merge_null_count(&mut self, other: &TypedStatsSetRef) {
533 self.merge_sum_stat(Stat::NullCount, other)
534 }
535
536 fn merge_nan_count(&mut self, other: &TypedStatsSetRef) {
537 self.merge_sum_stat(Stat::NaNCount, other)
538 }
539
540 fn merge_uncompressed_size_in_bytes(&mut self, other: &TypedStatsSetRef) {
541 self.merge_sum_stat(Stat::UncompressedSizeInBytes, other)
542 }
543
544 fn merge_sum_stat(&mut self, stat: Stat, other: &TypedStatsSetRef) {
545 let merged = self
546 .get_as::<usize>(stat)
547 .zip(other.get_as::<usize>(stat))
548 .map(|(l, r)| ScalarValue::from(l + r));
549
550 if merged.is_absent() {
551 self.clear(stat);
552 } else {
553 self.set(stat, merged);
554 }
555 }
556}
557
558#[cfg(test)]
559mod test {
560 use enum_iterator::all;
561 use itertools::Itertools;
562 use smallvec::smallvec;
563
564 use crate::VortexSessionExecute;
565 use crate::array_session;
566 use crate::arrays::PrimitiveArray;
567 use crate::dtype::DType;
568 use crate::dtype::Nullability;
569 use crate::dtype::PType;
570 use crate::expr::stats::IsConstant;
571 use crate::expr::stats::Precision;
572 use crate::expr::stats::Stat;
573 use crate::expr::stats::StatsProvider;
574 use crate::expr::stats::StatsProviderExt;
575 use crate::stats::StatsSet;
576 use crate::stats::stats_set::Scalar;
577
578 #[test]
579 fn test_iter() {
580 let set = unsafe {
582 StatsSet::new_unchecked(smallvec![
583 (Stat::Max, Precision::exact(100)),
584 (Stat::Min, Precision::exact(42)),
585 ])
586 };
587 let mut iter = set.iter();
588 let first = iter.next().unwrap().clone();
589 assert_eq!(first.0, Stat::Max);
590 assert_eq!(
591 first.1.map(
592 |f| i32::try_from(&Scalar::try_new(PType::I32.into(), Some(f)).unwrap()).unwrap()
593 ),
594 Precision::exact(100)
595 );
596 let snd = iter.next().unwrap().clone();
597 assert_eq!(snd.0, Stat::Min);
598 assert_eq!(
599 snd.1.map(
600 |s| i32::try_from(&Scalar::try_new(PType::I32.into(), Some(s)).unwrap()).unwrap()
601 ),
602 Precision::exact(42)
603 );
604 }
605
606 #[test]
607 fn into_iter() {
608 let mut set = unsafe {
610 StatsSet::new_unchecked(smallvec![
611 (Stat::Max, Precision::exact(100)),
612 (Stat::Min, Precision::exact(42)),
613 ])
614 }
615 .into_iter();
616 let (stat, first) = set.next().unwrap();
617 assert_eq!(stat, Stat::Max);
618 assert_eq!(
619 first.map(
620 |f| i32::try_from(&Scalar::try_new(PType::I32.into(), Some(f)).unwrap()).unwrap()
621 ),
622 Precision::exact(100)
623 );
624 let snd = set.next().unwrap();
625 assert_eq!(snd.0, Stat::Min);
626 assert_eq!(
627 snd.1.map(
628 |s| i32::try_from(&Scalar::try_new(PType::I32.into(), Some(s)).unwrap()).unwrap()
629 ),
630 Precision::exact(42)
631 );
632 }
633
634 #[test]
635 fn merge_constant() {
636 let first = StatsSet::from_iter([
637 (Stat::Min, Precision::exact(42)),
638 (Stat::IsConstant, Precision::exact(true)),
639 ])
640 .merge_ordered(
641 &StatsSet::from_iter([
642 (Stat::Min, Precision::inexact(42)),
643 (Stat::IsConstant, Precision::exact(true)),
644 ]),
645 &DType::Primitive(PType::I32, Nullability::NonNullable),
646 );
647
648 let first_ref = first.as_typed_ref(&DType::Primitive(PType::I32, Nullability::NonNullable));
649 assert_eq!(
650 first_ref.get_as::<bool>(Stat::IsConstant),
651 Precision::exact(false)
652 );
653 assert_eq!(first_ref.get_as::<i32>(Stat::Min), Precision::exact(42));
654 }
655
656 #[test]
657 fn merge_into_min() {
658 let first = StatsSet::of(Stat::Min, Precision::exact(42)).merge_ordered(
659 &StatsSet::default(),
660 &DType::Primitive(PType::I32, Nullability::NonNullable),
661 );
662
663 let first_ref = first.as_typed_ref(&DType::Primitive(PType::I32, Nullability::NonNullable));
664 assert!(first_ref.get(Stat::Min).is_absent());
665 }
666
667 #[test]
668 fn merge_from_min() {
669 let first = StatsSet::default().merge_ordered(
670 &StatsSet::of(Stat::Min, Precision::exact(42)),
671 &DType::Primitive(PType::I32, Nullability::NonNullable),
672 );
673
674 let first_ref = first.as_typed_ref(&DType::Primitive(PType::I32, Nullability::NonNullable));
675 assert!(first_ref.get(Stat::Min).is_absent());
676 }
677
678 #[test]
679 fn merge_mins() {
680 let first = StatsSet::of(Stat::Min, Precision::exact(37)).merge_ordered(
681 &StatsSet::of(Stat::Min, Precision::exact(42)),
682 &DType::Primitive(PType::I32, Nullability::NonNullable),
683 );
684
685 let first_ref = first.as_typed_ref(&DType::Primitive(PType::I32, Nullability::NonNullable));
686 assert_eq!(first_ref.get_as::<i32>(Stat::Min), Precision::exact(37));
687 }
688
689 #[test]
690 fn merge_into_bound_max() {
691 let first = StatsSet::of(Stat::Max, Precision::exact(42)).merge_ordered(
692 &StatsSet::default(),
693 &DType::Primitive(PType::I32, Nullability::NonNullable),
694 );
695 assert!(first.get(Stat::Max).is_absent());
696 }
697
698 #[test]
699 fn merge_from_max() {
700 let first = StatsSet::default().merge_ordered(
701 &StatsSet::of(Stat::Max, Precision::exact(42)),
702 &DType::Primitive(PType::I32, Nullability::NonNullable),
703 );
704 assert!(first.get(Stat::Max).is_absent());
705 }
706
707 #[test]
708 fn merge_maxes() {
709 let first = StatsSet::of(Stat::Max, Precision::exact(37)).merge_ordered(
710 &StatsSet::of(Stat::Max, Precision::exact(42)),
711 &DType::Primitive(PType::I32, Nullability::NonNullable),
712 );
713 let first_ref = first.as_typed_ref(&DType::Primitive(PType::I32, Nullability::NonNullable));
714 assert_eq!(first_ref.get_as::<i32>(Stat::Max), Precision::exact(42));
715 }
716
717 #[test]
718 fn merge_maxes_bound() {
719 let dtype = DType::Primitive(PType::I32, Nullability::NonNullable);
720 let first = StatsSet::of(Stat::Max, Precision::exact(42i32))
721 .merge_ordered(&StatsSet::of(Stat::Max, Precision::inexact(43i32)), &dtype);
722 let first_ref = first.as_typed_ref(&dtype);
723 assert_eq!(first_ref.get_as::<i32>(Stat::Max), Precision::inexact(43));
724 }
725
726 #[test]
727 fn merge_into_scalar() {
728 let first = StatsSet::of(Stat::Sum, Precision::exact(42i64)).merge_ordered(
731 &StatsSet::default(),
732 &DType::Primitive(PType::I32, Nullability::NonNullable),
733 );
734 let first_ref = first.as_typed_ref(&DType::Primitive(PType::I32, Nullability::NonNullable));
735 assert!(first_ref.get(Stat::Sum).is_absent());
736 }
737
738 #[test]
739 fn merge_from_scalar() {
740 let first = StatsSet::default().merge_ordered(
743 &StatsSet::of(Stat::Sum, Precision::exact(42i64)),
744 &DType::Primitive(PType::I32, Nullability::NonNullable),
745 );
746 let first_ref = first.as_typed_ref(&DType::Primitive(PType::I32, Nullability::NonNullable));
747 assert!(first_ref.get(Stat::Sum).is_absent());
748 }
749
750 #[test]
751 fn merge_scalars() {
752 let first = StatsSet::of(Stat::Sum, Precision::exact(37i64)).merge_ordered(
755 &StatsSet::of(Stat::Sum, Precision::exact(42i64)),
756 &DType::Primitive(PType::I32, Nullability::NonNullable),
757 );
758 let first_ref = first.as_typed_ref(&DType::Primitive(PType::I32, Nullability::NonNullable));
759 assert_eq!(first_ref.get_as::<i64>(Stat::Sum), Precision::exact(79i64));
760 }
761
762 #[test]
763 fn merge_into_sortedness() {
764 let first = StatsSet::of(Stat::IsStrictSorted, Precision::exact(true)).merge_ordered(
765 &StatsSet::default(),
766 &DType::Primitive(PType::I32, Nullability::NonNullable),
767 );
768 assert!(first.get(Stat::IsStrictSorted).is_absent());
769 }
770
771 #[test]
772 fn merge_from_sortedness() {
773 let first = StatsSet::default().merge_ordered(
774 &StatsSet::of(Stat::IsStrictSorted, Precision::exact(true)),
775 &DType::Primitive(PType::I32, Nullability::NonNullable),
776 );
777 assert!(first.get(Stat::IsStrictSorted).is_absent());
778 }
779
780 #[test]
781 fn merge_sortedness() {
782 let mut first = StatsSet::of(Stat::IsStrictSorted, Precision::exact(true));
783 first.set(Stat::Max, Precision::exact(1));
784 let mut second = StatsSet::of(Stat::IsStrictSorted, Precision::exact(true));
785 second.set(Stat::Min, Precision::exact(2));
786 first = first.merge_ordered(
787 &second,
788 &DType::Primitive(PType::I32, Nullability::NonNullable),
789 );
790
791 let first_ref = first.as_typed_ref(&DType::Primitive(PType::I32, Nullability::NonNullable));
792 assert_eq!(
793 first_ref.get_as::<bool>(Stat::IsStrictSorted),
794 Precision::exact(true)
795 );
796 }
797
798 #[test]
799 fn merge_sortedness_out_of_order() {
800 let mut first = StatsSet::of(Stat::IsStrictSorted, Precision::exact(true));
801 first.set(Stat::Min, Precision::exact(1));
802 let mut second = StatsSet::of(Stat::IsStrictSorted, Precision::exact(true));
803 second.set(Stat::Max, Precision::exact(2));
804 second = second.merge_ordered(
805 &first,
806 &DType::Primitive(PType::I32, Nullability::NonNullable),
807 );
808
809 let second_ref =
810 second.as_typed_ref(&DType::Primitive(PType::I32, Nullability::NonNullable));
811 assert_eq!(
812 second_ref.get_as::<bool>(Stat::IsStrictSorted),
813 Precision::inexact(false)
814 );
815 }
816
817 #[test]
818 fn merge_sortedness_only_one_sorted() {
819 let mut first = StatsSet::of(Stat::IsStrictSorted, Precision::exact(true));
820 first.set(Stat::Max, Precision::exact(1));
821 let mut second = StatsSet::of(Stat::IsStrictSorted, Precision::exact(false));
822 second.set(Stat::Min, Precision::exact(2));
823 first.merge_ordered(
824 &second,
825 &DType::Primitive(PType::I32, Nullability::NonNullable),
826 );
827
828 let second_ref =
829 second.as_typed_ref(&DType::Primitive(PType::I32, Nullability::NonNullable));
830 assert_eq!(
831 second_ref.get_as::<bool>(Stat::IsStrictSorted),
832 Precision::exact(false)
833 );
834 }
835
836 #[test]
837 fn merge_sortedness_missing_min() {
838 let mut first = StatsSet::of(Stat::IsStrictSorted, Precision::exact(true));
839 first.set(Stat::Max, Precision::exact(1));
840 let second = StatsSet::of(Stat::IsStrictSorted, Precision::exact(true));
841 first = first.merge_ordered(
842 &second,
843 &DType::Primitive(PType::I32, Nullability::NonNullable),
844 );
845 assert!(first.get(Stat::IsStrictSorted).is_absent());
846 }
847
848 #[test]
849 fn merge_sortedness_bound_min() {
850 let mut first = StatsSet::of(Stat::IsStrictSorted, Precision::exact(true));
851 first.set(Stat::Max, Precision::exact(1));
852 let mut second = StatsSet::of(Stat::IsStrictSorted, Precision::exact(true));
853 second.set(Stat::Min, Precision::inexact(2));
854 first = first.merge_ordered(
855 &second,
856 &DType::Primitive(PType::I32, Nullability::NonNullable),
857 );
858
859 let first_ref = first.as_typed_ref(&DType::Primitive(PType::I32, Nullability::NonNullable));
860 assert_eq!(
861 first_ref.get_as::<bool>(Stat::IsStrictSorted),
862 Precision::exact(true)
863 );
864 }
865
866 #[test]
867 fn merge_unordered() {
868 let array =
869 PrimitiveArray::from_option_iter([Some(1), None, Some(2), Some(42), Some(10000), None]);
870 let all_stats = all::<Stat>()
871 .filter(|s| !matches!(s, Stat::Sum))
872 .filter(|s| !matches!(s, Stat::NaNCount))
873 .collect_vec();
874 array
875 .statistics()
876 .compute_all(&all_stats, &mut array_session().create_execution_ctx())
877 .unwrap();
878
879 let stats = array.statistics().to_owned();
880 for stat in &all_stats {
881 assert!(!stats.get(*stat).is_absent(), "Stat {stat} is missing");
882 }
883
884 let merged = stats.clone().merge_unordered(
885 &stats,
886 &DType::Primitive(PType::I32, Nullability::NonNullable),
887 );
888 for stat in &all_stats {
889 assert_eq!(
890 !merged.get(*stat).is_absent(),
891 stat.is_commutative(),
892 "Stat {stat} remains after merge_unordered despite not being commutative, or was removed despite being commutative"
893 )
894 }
895
896 let merged_ref = merged.as_typed_ref(&DType::Primitive(PType::I32, Nullability::Nullable));
897 let stats_ref = stats.as_typed_ref(&DType::Primitive(PType::I32, Nullability::Nullable));
898
899 assert_eq!(
900 merged_ref.get_as::<i32>(Stat::Min),
901 stats_ref.get_as::<i32>(Stat::Min)
902 );
903 assert_eq!(
904 merged_ref.get_as::<i32>(Stat::Max),
905 stats_ref.get_as::<i32>(Stat::Max)
906 );
907 assert_eq!(
908 merged_ref.get_as::<u64>(Stat::NullCount),
909 stats_ref.get_as::<u64>(Stat::NullCount).map(|s| s * 2)
910 );
911 }
912
913 #[test]
914 fn merge_min_bound_same() {
915 let merged = StatsSet::of(Stat::Min, Precision::inexact(5)).merge_ordered(
918 &StatsSet::of(Stat::Min, Precision::exact(5)),
919 &DType::Primitive(PType::I32, Nullability::NonNullable),
920 );
921 let merged_ref =
922 merged.as_typed_ref(&DType::Primitive(PType::I32, Nullability::NonNullable));
923 assert_eq!(merged_ref.get_as::<i32>(Stat::Min), Precision::exact(5));
924 }
925
926 #[test]
927 fn merge_min_bound_bound_lower() {
928 let merged = StatsSet::of(Stat::Min, Precision::inexact(4)).merge_ordered(
929 &StatsSet::of(Stat::Min, Precision::exact(5)),
930 &DType::Primitive(PType::I32, Nullability::NonNullable),
931 );
932 let merged_ref =
933 merged.as_typed_ref(&DType::Primitive(PType::I32, Nullability::NonNullable));
934 assert_eq!(merged_ref.get_as::<i32>(Stat::Min), Precision::inexact(4));
935 }
936
937 #[test]
938 fn test_combine_is_constant() {
939 {
940 let mut stats = StatsSet::of(Stat::IsConstant, Precision::exact(true));
941 let stats2 = StatsSet::of(Stat::IsConstant, Precision::exact(true));
942 let mut stats_ref =
943 stats.as_mut_typed_ref(&DType::Primitive(PType::I32, Nullability::NonNullable));
944 stats_ref
945 .combine_bool_stat::<IsConstant>(
946 &stats2.as_typed_ref(&DType::Primitive(PType::I32, Nullability::NonNullable)),
947 )
948 .unwrap();
949 assert_eq!(
950 stats_ref.get_as::<bool>(Stat::IsConstant),
951 Precision::exact(true)
952 );
953 }
954
955 {
956 let mut stats = StatsSet::of(Stat::IsConstant, Precision::exact(true));
957 let stats2 = StatsSet::of(Stat::IsConstant, Precision::inexact(false));
958 let mut stats_ref =
959 stats.as_mut_typed_ref(&DType::Primitive(PType::I32, Nullability::NonNullable));
960 stats_ref
961 .combine_bool_stat::<IsConstant>(
962 &stats2.as_typed_ref(&DType::Primitive(PType::I32, Nullability::NonNullable)),
963 )
964 .unwrap();
965 assert_eq!(
966 stats_ref.get_as::<bool>(Stat::IsConstant),
967 Precision::exact(true)
968 );
969 }
970
971 {
972 let mut stats = StatsSet::of(Stat::IsConstant, Precision::exact(false));
973 let stats2 = StatsSet::of(Stat::IsConstant, Precision::inexact(false));
974 let mut stats_ref =
975 stats.as_mut_typed_ref(&DType::Primitive(PType::I32, Nullability::NonNullable));
976 stats_ref
977 .combine_bool_stat::<IsConstant>(
978 &stats2.as_typed_ref(&DType::Primitive(PType::I32, Nullability::NonNullable)),
979 )
980 .unwrap();
981 assert_eq!(
982 stats_ref.get_as::<bool>(Stat::IsConstant),
983 Precision::exact(false)
984 );
985 }
986 }
987
988 #[test]
989 fn test_combine_sets_boolean_conflict() {
990 let mut stats1 = StatsSet::from_iter([
991 (Stat::IsConstant, Precision::exact(true)),
992 (Stat::IsSorted, Precision::exact(true)),
993 ]);
994
995 let stats2 = StatsSet::from_iter([
996 (Stat::IsConstant, Precision::exact(false)),
997 (Stat::IsSorted, Precision::exact(true)),
998 ]);
999
1000 let result = stats1.combine_sets(
1001 &stats2,
1002 &DType::Primitive(PType::I32, Nullability::NonNullable),
1003 );
1004 assert!(result.is_err());
1005 }
1006
1007 #[test]
1008 fn test_combine_sets_with_missing_stats() {
1009 let mut stats1 = StatsSet::from_iter([
1010 (Stat::Min, Precision::exact(42)),
1011 (Stat::UncompressedSizeInBytes, Precision::exact(1000)),
1012 ]);
1013
1014 let stats2 = StatsSet::from_iter([
1015 (Stat::Max, Precision::exact(100)),
1016 (Stat::IsStrictSorted, Precision::exact(true)),
1017 ]);
1018
1019 stats1
1020 .combine_sets(
1021 &stats2,
1022 &DType::Primitive(PType::I32, Nullability::NonNullable),
1023 )
1024 .unwrap();
1025
1026 let stats_ref =
1027 stats1.as_typed_ref(&DType::Primitive(PType::I32, Nullability::NonNullable));
1028
1029 assert_eq!(stats_ref.get_as::<i32>(Stat::Min), Precision::exact(42));
1031 assert_eq!(stats_ref.get_as::<i32>(Stat::Max), Precision::exact(100));
1033 assert_eq!(
1035 stats_ref.get_as::<bool>(Stat::IsStrictSorted),
1036 Precision::exact(true)
1037 );
1038 }
1039
1040 #[test]
1041 fn test_combine_sets_with_inexact() {
1042 let mut stats1 = StatsSet::from_iter([
1043 (Stat::Min, Precision::exact(42)),
1044 (Stat::Max, Precision::inexact(100)),
1045 (Stat::IsConstant, Precision::exact(false)),
1046 ]);
1047
1048 let stats2 = StatsSet::from_iter([
1049 (Stat::Min, Precision::inexact(40)),
1051 (Stat::Max, Precision::exact(90)),
1052 (Stat::IsSorted, Precision::exact(true)),
1053 ]);
1054
1055 stats1
1056 .combine_sets(
1057 &stats2,
1058 &DType::Primitive(PType::I32, Nullability::NonNullable),
1059 )
1060 .unwrap();
1061
1062 let stats_ref =
1063 stats1.as_typed_ref(&DType::Primitive(PType::I32, Nullability::NonNullable));
1064
1065 assert_eq!(stats_ref.get_as::<i32>(Stat::Min), Precision::exact(42));
1067 assert_eq!(stats_ref.get_as::<i32>(Stat::Max), Precision::exact(90));
1069 assert_eq!(
1071 stats_ref.get_as::<bool>(Stat::IsSorted),
1072 Precision::exact(true)
1073 );
1074 }
1075}