• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

vortex-data / vortex / 16832413242

08 Aug 2025 02:04PM UTC coverage: 84.935% (+0.06%) from 84.877%
16832413242

Pull #4161

github

web-flow
Merge 04e9b0a07 into c88d9ada1
Pull Request #4161: feat(python): Add Arrow FFI streaming support to write API

50657 of 59642 relevant lines covered (84.94%)

568241.59 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.9
/vortex-array/src/stats/stats_set.rs
1
// SPDX-License-Identifier: Apache-2.0
2
// SPDX-FileCopyrightText: Copyright the Vortex contributors
3

4
use std::fmt::Debug;
5

6
use enum_iterator::{Sequence, all};
7
use num_traits::CheckedAdd;
8
use vortex_dtype::DType;
9
use vortex_error::{VortexError, VortexExpect, VortexResult, vortex_err, vortex_panic};
10
use vortex_scalar::{Scalar, ScalarValue};
11

12
use super::{
13
    IsSorted, IsStrictSorted, NaNCount, NullCount, StatType, StatsProvider, UncompressedSizeInBytes,
14
};
15
use crate::stats::{IsConstant, Max, Min, Precision, Stat, StatBound, StatsProviderExt, Sum};
16

17
#[derive(Default, Debug, Clone)]
18
pub struct StatsSet {
19
    values: Vec<(Stat, Precision<ScalarValue>)>,
20
}
21

22
impl StatsSet {
23
    /// Create new StatSet without validating uniqueness of all the entries
24
    ///
25
    /// # Safety
26
    ///
27
    /// This method will not panic or trigger UB, but may lead to duplicate stats being stored.
28
    pub fn new_unchecked(values: Vec<(Stat, Precision<ScalarValue>)>) -> Self {
38✔
29
        Self { values }
38✔
30
    }
38✔
31

32
    /// Create StatsSet from single stat and value
33
    pub fn of(stat: Stat, value: Precision<ScalarValue>) -> Self {
36✔
34
        Self::new_unchecked(vec![(stat, value)])
36✔
35
    }
36✔
36

37
    fn reserve_full_capacity(&mut self) {
869,364✔
38
        if self.values.capacity() < Stat::CARDINALITY {
869,364✔
39
            self.values
459,254✔
40
                .reserve_exact(Stat::CARDINALITY - self.values.capacity());
459,254✔
41
        }
465,137✔
42
    }
869,364✔
43

44
    /// Wrap stats set with a dtype for mutable typed scalar access
45
    pub fn as_mut_typed_ref<'a, 'b>(&'a mut self, dtype: &'b DType) -> MutTypedStatsSetRef<'a, 'b> {
101,942✔
46
        MutTypedStatsSetRef {
101,942✔
47
            values: self,
101,942✔
48
            dtype,
101,942✔
49
        }
101,942✔
50
    }
101,942✔
51

52
    /// Wrap stats set with a dtype for typed scalar access
53
    pub fn as_typed_ref<'a, 'b>(&'a self, dtype: &'b DType) -> TypedStatsSetRef<'a, 'b> {
2,562,476✔
54
        TypedStatsSetRef {
2,562,476✔
55
            values: self,
2,562,476✔
56
            dtype,
2,562,476✔
57
        }
2,562,476✔
58
    }
2,562,476✔
59
}
60

61
// Getters and setters for individual stats.
62
impl StatsSet {
63
    /// Set the stat `stat` to `value`.
64
    pub fn set(&mut self, stat: Stat, value: Precision<ScalarValue>) {
767,400✔
65
        self.reserve_full_capacity();
767,400✔
66

67
        if let Some(existing) = self.values.iter_mut().find(|(s, _)| *s == stat) {
777,969✔
68
            *existing = (stat, value);
2,831✔
69
        } else {
764,569✔
70
            self.values.push((stat, value));
764,569✔
71
        }
764,569✔
72
    }
767,400✔
73

74
    /// Clear the stat `stat` from the set.
75
    pub fn clear(&mut self, stat: Stat) {
154✔
76
        self.values.retain(|(s, _)| *s != stat);
273✔
77
    }
154✔
78

79
    /// Only keep given stats
80
    pub fn retain_only(&mut self, stats: &[Stat]) {
×
81
        self.values.retain(|(s, _)| stats.contains(s));
×
82
    }
×
83

84
    /// Keep given stats as inexact values
85
    pub fn keep_inexact_stats(self, inexact_keep: &[Stat]) -> Self {
101,955✔
86
        self.values
101,955✔
87
            .into_iter()
101,955✔
88
            .filter_map(|(s, v)| inexact_keep.contains(&s).then(|| (s, v.into_inexact())))
103,407✔
89
            .collect()
101,955✔
90
    }
101,955✔
91

92
    /// Iterate over the statistic names and values in-place.
93
    ///
94
    /// See [Iterator].
95
    pub fn iter(&self) -> impl Iterator<Item = &(Stat, Precision<ScalarValue>)> {
3,553,305✔
96
        self.values.iter()
3,553,305✔
97
    }
3,553,305✔
98

99
    /// Get value for a given stat
100
    pub fn get(&self, stat: Stat) -> Option<Precision<ScalarValue>> {
2,756,674✔
101
        self.values
2,756,674✔
102
            .iter()
2,756,674✔
103
            .find(|(s, _)| *s == stat)
2,764,114✔
104
            .map(|(_, v)| v.clone())
2,756,674✔
105
    }
2,756,674✔
106

107
    /// Length of the stats set
108
    pub fn len(&self) -> usize {
1✔
109
        self.values.len()
1✔
110
    }
1✔
111

112
    /// Check whether the statset is empty
113
    pub fn is_empty(&self) -> bool {
×
114
        self.values.is_empty()
×
115
    }
×
116

117
    /// Get scalar value of a given dtype
118
    pub fn get_as<T: for<'a> TryFrom<&'a Scalar, Error = VortexError>>(
192,756✔
119
        &self,
192,756✔
120
        stat: Stat,
192,756✔
121
        dtype: &DType,
192,756✔
122
    ) -> Option<Precision<T>> {
192,756✔
123
        self.get(stat).map(|v| {
192,756✔
124
            v.map(|v| {
22,533✔
125
                T::try_from(&Scalar::new(dtype.clone(), v)).unwrap_or_else(|err| {
22,533✔
126
                    vortex_panic!(
×
127
                        err,
×
128
                        "Failed to get stat {} as {}",
×
129
                        stat,
130
                        std::any::type_name::<T>()
×
131
                    )
132
                })
133
            })
22,533✔
134
        })
22,533✔
135
    }
192,756✔
136
}
137

138
// StatSetIntoIter just exists to protect current implementation from exposure on the public API.
139

140
/// Owned iterator over the stats.
141
///
142
/// See [IntoIterator].
143
pub struct StatsSetIntoIter(std::vec::IntoIter<(Stat, Precision<ScalarValue>)>);
144

145
impl Iterator for StatsSetIntoIter {
146
    type Item = (Stat, Precision<ScalarValue>);
147

148
    fn next(&mut self) -> Option<Self::Item> {
153,405✔
149
        self.0.next()
153,405✔
150
    }
153,405✔
151
}
152

153
impl IntoIterator for StatsSet {
154
    type Item = (Stat, Precision<ScalarValue>);
155
    type IntoIter = StatsSetIntoIter;
156

157
    fn into_iter(self) -> Self::IntoIter {
125,938✔
158
        StatsSetIntoIter(self.values.into_iter())
125,938✔
159
    }
125,938✔
160
}
161

162
impl FromIterator<(Stat, Precision<ScalarValue>)> for StatsSet {
163
    fn from_iter<T: IntoIterator<Item = (Stat, Precision<ScalarValue>)>>(iter: T) -> Self {
101,964✔
164
        let iter = iter.into_iter();
101,964✔
165
        let mut values = Vec::default();
101,964✔
166
        values.reserve_exact(Stat::CARDINALITY);
101,964✔
167

168
        let mut this = Self { values };
101,964✔
169
        this.extend(iter);
101,964✔
170
        this
101,964✔
171
    }
101,964✔
172
}
173

174
impl Extend<(Stat, Precision<ScalarValue>)> for StatsSet {
175
    #[inline]
176
    fn extend<T: IntoIterator<Item = (Stat, Precision<ScalarValue>)>>(&mut self, iter: T) {
101,964✔
177
        let iter = iter.into_iter();
101,964✔
178
        self.reserve_full_capacity();
101,964✔
179

180
        iter.for_each(|(stat, value)| self.set(stat, value));
102,264✔
181
    }
101,964✔
182
}
183

184
/// Merge helpers
185
impl StatsSet {
186
    /// Merge stats set `other` into `self`, with the semantic assumption that `other`
187
    /// contains stats from a disjoint array that is *appended* to the array represented by `self`.
188
    pub fn merge_ordered(mut self, other: &Self, dtype: &DType) -> Self {
20✔
189
        self.as_mut_typed_ref(dtype)
20✔
190
            .merge_ordered(&other.as_typed_ref(dtype));
20✔
191
        self
20✔
192
    }
20✔
193

194
    /// Merge stats set `other` into `self`, from a disjoint array, with no ordering assumptions.
195
    /// Stats that are not commutative (e.g., is_sorted) are dropped from the result.
196
    pub fn merge_unordered(mut self, other: &Self, dtype: &DType) -> Self {
1✔
197
        self.as_mut_typed_ref(dtype)
1✔
198
            .merge_unordered(&other.as_typed_ref(dtype));
1✔
199
        self
1✔
200
    }
1✔
201

202
    /// Given two sets of stats (of differing precision) for the same array, combine them
203
    pub fn combine_sets(&mut self, other: &Self, dtype: &DType) -> VortexResult<()> {
101,918✔
204
        self.as_mut_typed_ref(dtype)
101,918✔
205
            .combine_sets(&other.as_typed_ref(dtype))
101,918✔
206
    }
101,918✔
207
}
208

209
pub struct TypedStatsSetRef<'a, 'b> {
210
    pub values: &'a StatsSet,
211
    pub dtype: &'b DType,
212
}
213

214
impl StatsProvider for TypedStatsSetRef<'_, '_> {
215
    fn get(&self, stat: Stat) -> Option<Precision<Scalar>> {
2,462,883✔
216
        self.values.get(stat).map(|p| {
2,462,883✔
217
            p.map(|sv| {
947,155✔
218
                Scalar::new(
947,155✔
219
                    stat.dtype(self.dtype)
947,155✔
220
                        .vortex_expect("Must have valid dtype if value is present"),
947,155✔
221
                    sv,
947,155✔
222
                )
223
            })
947,155✔
224
        })
947,155✔
225
    }
2,462,883✔
226

227
    fn len(&self) -> usize {
×
228
        self.values.len()
×
229
    }
×
230
}
231

232
pub struct MutTypedStatsSetRef<'a, 'b> {
233
    pub values: &'a mut StatsSet,
234
    pub dtype: &'b DType,
235
}
236

237
impl MutTypedStatsSetRef<'_, '_> {
238
    /// Set the stat `stat` to `value`.
239
    pub fn set(&mut self, stat: Stat, value: Precision<ScalarValue>) {
2,143✔
240
        self.values.set(stat, value);
2,143✔
241
    }
2,143✔
242

243
    /// Clear the stat `stat` from the set.
244
    pub fn clear(&mut self, stat: Stat) {
154✔
245
        self.values.clear(stat);
154✔
246
    }
154✔
247
}
248

249
impl StatsProvider for MutTypedStatsSetRef<'_, '_> {
250
    fn get(&self, stat: Stat) -> Option<Precision<Scalar>> {
2,334✔
251
        self.values.get(stat).map(|p| {
2,334✔
252
            p.map(|sv| {
46✔
253
                Scalar::new(
46✔
254
                    stat.dtype(self.dtype)
46✔
255
                        .vortex_expect("Must have valid dtype if value is present"),
46✔
256
                    sv,
46✔
257
                )
258
            })
46✔
259
        })
46✔
260
    }
2,334✔
261

262
    fn len(&self) -> usize {
×
263
        self.values.len()
×
264
    }
×
265
}
266

267
// Merge helpers
268
impl MutTypedStatsSetRef<'_, '_> {
269
    /// Merge stats set `other` into `self`, with the semantic assumption that `other`
270
    /// contains stats from a disjoint array that is *appended* to the array represented by `self`.
271
    pub fn merge_ordered(mut self, other: &TypedStatsSetRef) -> Self {
20✔
272
        for s in all::<Stat>() {
180✔
273
            match s {
180✔
274
                Stat::IsConstant => self.merge_is_constant(other),
20✔
275
                Stat::IsSorted => self.merge_is_sorted(other),
20✔
276
                Stat::IsStrictSorted => self.merge_is_strict_sorted(other),
20✔
277
                Stat::Max => self.merge_max(other),
20✔
278
                Stat::Min => self.merge_min(other),
20✔
279
                Stat::Sum => self.merge_sum(other),
20✔
280
                Stat::NullCount => self.merge_null_count(other),
20✔
281
                Stat::UncompressedSizeInBytes => self.merge_uncompressed_size_in_bytes(other),
20✔
282
                Stat::NaNCount => self.merge_nan_count(other),
20✔
283
            }
284
        }
285

286
        self
20✔
287
    }
20✔
288

289
    /// Merge stats set `other` into `self`, from a disjoint array, with no ordering assumptions.
290
    /// Stats that are not commutative (e.g., is_sorted) are dropped from the result.
291
    pub fn merge_unordered(mut self, other: &TypedStatsSetRef) -> Self {
1✔
292
        for s in all::<Stat>() {
9✔
293
            if !s.is_commutative() {
9✔
294
                self.clear(s);
2✔
295
                continue;
2✔
296
            }
7✔
297

298
            match s {
7✔
299
                Stat::IsConstant => self.merge_is_constant(other),
1✔
300
                Stat::Max => self.merge_max(other),
1✔
301
                Stat::Min => self.merge_min(other),
1✔
302
                Stat::Sum => self.merge_sum(other),
1✔
303
                Stat::NullCount => self.merge_null_count(other),
1✔
304
                Stat::UncompressedSizeInBytes => self.merge_uncompressed_size_in_bytes(other),
1✔
305
                Stat::IsSorted | Stat::IsStrictSorted => {
306
                    unreachable!("not commutative")
×
307
                }
308
                Stat::NaNCount => self.merge_nan_count(other),
1✔
309
            }
310
        }
311

312
        self
1✔
313
    }
1✔
314

315
    /// Given two sets of stats (of differing precision) for the same array, combine them
316
    pub fn combine_sets(&mut self, other: &TypedStatsSetRef) -> VortexResult<()> {
101,918✔
317
        let other_stats: Vec<_> = other.values.iter().map(|(stat, _)| *stat).collect();
101,918✔
318
        for s in other_stats {
104,033✔
319
            match s {
2,116✔
320
                Stat::Max => self.combine_bound::<Max>(other)?,
1,057✔
321
                Stat::Min => self.combine_bound::<Min>(other)?,
1,056✔
322
                Stat::UncompressedSizeInBytes => {
323
                    self.combine_bound::<UncompressedSizeInBytes>(other)?
×
324
                }
325
                Stat::IsConstant => self.combine_bool_stat::<IsConstant>(other)?,
1✔
326
                Stat::IsSorted => self.combine_bool_stat::<IsSorted>(other)?,
1✔
327
                Stat::IsStrictSorted => self.combine_bool_stat::<IsStrictSorted>(other)?,
1✔
328
                Stat::NullCount => self.combine_bound::<NullCount>(other)?,
×
329
                Stat::Sum => self.combine_bound::<Sum>(other)?,
×
330
                Stat::NaNCount => self.combine_bound::<NaNCount>(other)?,
×
331
            }
332
        }
333
        Ok(())
101,917✔
334
    }
101,918✔
335

336
    fn combine_bound<S: StatType<Scalar>>(&mut self, other: &TypedStatsSetRef) -> VortexResult<()>
2,113✔
337
    where
2,113✔
338
        S::Bound: StatBound<Scalar> + Debug + Eq + PartialEq,
2,113✔
339
    {
340
        match (self.get_scalar_bound::<S>(), other.get_scalar_bound::<S>()) {
2,113✔
341
            (Some(m1), Some(m2)) => {
2✔
342
                let meet = m1
2✔
343
                    .intersection(&m2)
2✔
344
                    .vortex_expect("can always compare scalar")
2✔
345
                    .ok_or_else(|| {
2✔
346
                        vortex_err!("{:?} bounds ({m1:?}, {m2:?}) do not overlap", S::STAT)
×
347
                    })?;
×
348
                if meet != m1 {
2✔
349
                    self.set(S::STAT, meet.into_value().map(Scalar::into_value));
1✔
350
                }
1✔
351
            }
352
            (None, Some(m)) => self.set(S::STAT, m.into_value().map(Scalar::into_value)),
2,111✔
353
            (Some(_), _) => (),
×
354
            (None, None) => self.clear(S::STAT),
×
355
        }
356
        Ok(())
2,113✔
357
    }
2,113✔
358

359
    fn combine_bool_stat<S: StatType<bool>>(&mut self, other: &TypedStatsSetRef) -> VortexResult<()>
6✔
360
    where
6✔
361
        S::Bound: StatBound<bool> + Debug + Eq + PartialEq,
6✔
362
    {
363
        match (
364
            self.get_as_bound::<S, bool>(),
6✔
365
            other.get_as_bound::<S, bool>(),
6✔
366
        ) {
367
            (Some(m1), Some(m2)) => {
4✔
368
                let intersection = m1
4✔
369
                    .intersection(&m2)
4✔
370
                    .vortex_expect("can always compare boolean")
4✔
371
                    .ok_or_else(|| {
4✔
372
                        vortex_err!("{:?} bounds ({m1:?}, {m2:?}) do not overlap", S::STAT)
1✔
373
                    })?;
1✔
374
                if intersection != m1 {
3✔
375
                    self.set(S::STAT, intersection.into_value().map(ScalarValue::from));
×
376
                }
3✔
377
            }
378
            (None, Some(m)) => self.set(S::STAT, m.into_value().map(ScalarValue::from)),
2✔
379
            (Some(_), None) => (),
×
380
            (None, None) => self.clear(S::STAT),
×
381
        }
382
        Ok(())
5✔
383
    }
6✔
384

385
    fn merge_min(&mut self, other: &TypedStatsSetRef) {
21✔
386
        match (
387
            self.get_scalar_bound::<Min>(),
21✔
388
            other.get_scalar_bound::<Min>(),
21✔
389
        ) {
390
            (Some(m1), Some(m2)) => {
5✔
391
                let meet = m1.union(&m2).vortex_expect("can compare scalar");
5✔
392
                if meet != m1 {
5✔
393
                    self.set(Stat::Min, meet.into_value().map(Scalar::into_value));
1✔
394
                }
4✔
395
            }
396
            _ => self.clear(Stat::Min),
16✔
397
        }
398
    }
21✔
399

400
    fn merge_max(&mut self, other: &TypedStatsSetRef) {
21✔
401
        match (
402
            self.get_scalar_bound::<Max>(),
21✔
403
            other.get_scalar_bound::<Max>(),
21✔
404
        ) {
405
            (Some(m1), Some(m2)) => {
3✔
406
                let meet = m1.union(&m2).vortex_expect("can compare scalar");
3✔
407
                if meet != m1 {
3✔
408
                    self.set(Stat::Max, meet.into_value().map(Scalar::into_value));
2✔
409
                }
2✔
410
            }
411
            _ => self.clear(Stat::Max),
18✔
412
        }
413
    }
21✔
414

415
    fn merge_sum(&mut self, other: &TypedStatsSetRef) {
21✔
416
        match (
417
            self.get_scalar_bound::<Sum>(),
21✔
418
            other.get_scalar_bound::<Sum>(),
21✔
419
        ) {
420
            (Some(m1), Some(m2)) => {
1✔
421
                // If the combine sum is exact, then we can sum them.
422
                if let Some(scalar_value) = m1.zip(m2).as_exact().and_then(|(s1, s2)| {
1✔
423
                    s1.as_primitive()
1✔
424
                        .checked_add(&s2.as_primitive())
1✔
425
                        .map(|pscalar| {
1✔
426
                            pscalar
1✔
427
                                .pvalue()
1✔
428
                                .map(|pvalue| {
1✔
429
                                    Scalar::primitive_value(
1✔
430
                                        pvalue,
1✔
431
                                        pscalar.ptype(),
1✔
432
                                        pscalar.dtype().nullability(),
1✔
433
                                    )
434
                                    .into_value()
1✔
435
                                })
1✔
436
                                .unwrap_or_else(ScalarValue::null)
1✔
437
                        })
1✔
438
                }) {
1✔
439
                    self.set(Stat::Sum, Precision::Exact(scalar_value));
1✔
440
                }
1✔
441
            }
442
            _ => self.clear(Stat::Sum),
20✔
443
        }
444
    }
21✔
445

446
    fn merge_is_constant(&mut self, other: &TypedStatsSetRef) {
21✔
447
        let self_const = self.get_as(Stat::IsConstant);
21✔
448
        let other_const = other.get_as(Stat::IsConstant);
21✔
449
        let self_min = self.get(Stat::Min);
21✔
450
        let other_min = other.get(Stat::Min);
21✔
451

452
        if let (
453
            Some(Precision::Exact(self_const)),
1✔
454
            Some(Precision::Exact(other_const)),
1✔
455
            Some(Precision::Exact(self_min)),
1✔
456
            Some(Precision::Exact(other_min)),
1✔
457
        ) = (self_const, other_const, self_min, other_min)
21✔
458
        {
459
            if self_const && other_const && self_min == other_min {
1✔
460
                self.set(Stat::IsConstant, Precision::exact(true));
×
461
            } else {
1✔
462
                self.set(Stat::IsConstant, Precision::inexact(false));
1✔
463
            }
1✔
464
        }
20✔
465
        self.set(Stat::IsConstant, Precision::exact(false));
21✔
466
    }
21✔
467

468
    fn merge_is_sorted(&mut self, other: &TypedStatsSetRef) {
20✔
469
        self.merge_sortedness_stat(other, Stat::IsSorted, PartialOrd::le)
20✔
470
    }
20✔
471

472
    fn merge_is_strict_sorted(&mut self, other: &TypedStatsSetRef) {
20✔
473
        self.merge_sortedness_stat(other, Stat::IsStrictSorted, PartialOrd::lt)
20✔
474
    }
20✔
475

476
    fn merge_sortedness_stat<F: Fn(&Scalar, &Scalar) -> bool>(
40✔
477
        &mut self,
40✔
478
        other: &TypedStatsSetRef,
40✔
479
        stat: Stat,
40✔
480
        cmp: F,
40✔
481
    ) {
40✔
482
        if (Some(Precision::Exact(true)), Some(Precision::Exact(true)))
40✔
483
            == (self.get_as(stat), other.get_as(stat))
40✔
484
        {
485
            // There might be no stat because it was dropped, or it doesn't exist
486
            // (e.g. an all null array).
487
            // We assume that it was the dropped case since the doesn't exist might imply sorted,
488
            // but this in-precision is correct.
489
            if let (Some(self_max), Some(other_min)) = (
3✔
490
                self.get_scalar_bound::<Max>(),
4✔
491
                other.get_scalar_bound::<Min>(),
4✔
492
            ) {
493
                return if cmp(&self_max.max_value(), &other_min.min_value()) {
3✔
494
                    // keep value
2✔
495
                } else {
2✔
496
                    self.set(stat, Precision::inexact(false));
1✔
497
                };
1✔
498
            }
1✔
499
        }
36✔
500
        self.clear(stat);
37✔
501
    }
40✔
502

503
    fn merge_null_count(&mut self, other: &TypedStatsSetRef) {
21✔
504
        self.merge_sum_stat(Stat::NullCount, other)
21✔
505
    }
21✔
506

507
    fn merge_nan_count(&mut self, other: &TypedStatsSetRef) {
21✔
508
        self.merge_sum_stat(Stat::NaNCount, other)
21✔
509
    }
21✔
510

511
    fn merge_uncompressed_size_in_bytes(&mut self, other: &TypedStatsSetRef) {
21✔
512
        self.merge_sum_stat(Stat::UncompressedSizeInBytes, other)
21✔
513
    }
21✔
514

515
    fn merge_sum_stat(&mut self, stat: Stat, other: &TypedStatsSetRef) {
63✔
516
        match (self.get_as::<usize>(stat), other.get_as::<usize>(stat)) {
63✔
517
            (Some(nc1), Some(nc2)) => {
2✔
518
                self.set(
2✔
519
                    stat,
2✔
520
                    nc1.zip(nc2).map(|(nc1, nc2)| ScalarValue::from(nc1 + nc2)),
2✔
521
                );
522
            }
523
            _ => self.clear(stat),
61✔
524
        }
525
    }
63✔
526
}
527

528
#[cfg(test)]
529
mod test {
530
    use enum_iterator::all;
531
    use itertools::Itertools;
532
    use vortex_dtype::{DType, Nullability, PType};
533

534
    use crate::arrays::PrimitiveArray;
535
    use crate::stats::stats_set::Scalar;
536
    use crate::stats::{IsConstant, Precision, Stat, StatsProvider, StatsProviderExt, StatsSet};
537

538
    #[test]
539
    fn test_iter() {
1✔
540
        let set = StatsSet::new_unchecked(vec![
1✔
541
            (Stat::Max, Precision::exact(100)),
1✔
542
            (Stat::Min, Precision::exact(42)),
1✔
543
        ]);
544
        let mut iter = set.iter();
1✔
545
        let first = iter.next().unwrap().clone();
1✔
546
        assert_eq!(first.0, Stat::Max);
1✔
547
        assert_eq!(
1✔
548
            first
1✔
549
                .1
1✔
550
                .map(|f| i32::try_from(&Scalar::new(PType::I32.into(), f)).unwrap()),
1✔
551
            Precision::exact(100)
1✔
552
        );
553
        let snd = iter.next().unwrap().clone();
1✔
554
        assert_eq!(snd.0, Stat::Min);
1✔
555
        assert_eq!(
1✔
556
            snd.1
1✔
557
                .map(|s| i32::try_from(&Scalar::new(PType::I32.into(), s)).unwrap()),
1✔
558
            42
559
        );
560
    }
1✔
561

562
    #[test]
563
    fn into_iter() {
1✔
564
        let mut set = StatsSet::new_unchecked(vec![
1✔
565
            (Stat::Max, Precision::exact(100)),
1✔
566
            (Stat::Min, Precision::exact(42)),
1✔
567
        ])
568
        .into_iter();
1✔
569
        let (stat, first) = set.next().unwrap();
1✔
570
        assert_eq!(stat, Stat::Max);
1✔
571
        assert_eq!(
1✔
572
            first.map(|f| i32::try_from(&Scalar::new(PType::I32.into(), f)).unwrap()),
1✔
573
            Precision::exact(100)
1✔
574
        );
575
        let snd = set.next().unwrap();
1✔
576
        assert_eq!(snd.0, Stat::Min);
1✔
577
        assert_eq!(
1✔
578
            snd.1
1✔
579
                .map(|s| i32::try_from(&Scalar::new(PType::I32.into(), s)).unwrap()),
1✔
580
            Precision::exact(42)
1✔
581
        );
582
    }
1✔
583

584
    #[test]
585
    fn merge_constant() {
1✔
586
        let first = StatsSet::from_iter([
1✔
587
            (Stat::Min, Precision::exact(42)),
1✔
588
            (Stat::IsConstant, Precision::exact(true)),
1✔
589
        ])
1✔
590
        .merge_ordered(
1✔
591
            &StatsSet::from_iter([
1✔
592
                (Stat::Min, Precision::inexact(42)),
1✔
593
                (Stat::IsConstant, Precision::exact(true)),
1✔
594
            ]),
1✔
595
            &DType::Primitive(PType::I32, Nullability::NonNullable),
1✔
596
        );
597

598
        let first_ref = first.as_typed_ref(&DType::Primitive(PType::I32, Nullability::NonNullable));
1✔
599
        assert_eq!(
1✔
600
            first_ref.get_as::<bool>(Stat::IsConstant),
1✔
601
            Some(Precision::exact(false))
1✔
602
        );
603
        assert_eq!(
1✔
604
            first_ref.get_as::<i32>(Stat::Min),
1✔
605
            Some(Precision::exact(42))
1✔
606
        );
607
    }
1✔
608

609
    #[test]
610
    fn merge_into_min() {
1✔
611
        let first = StatsSet::of(Stat::Min, Precision::exact(42)).merge_ordered(
1✔
612
            &StatsSet::default(),
1✔
613
            &DType::Primitive(PType::I32, Nullability::NonNullable),
1✔
614
        );
615

616
        let first_ref = first.as_typed_ref(&DType::Primitive(PType::I32, Nullability::NonNullable));
1✔
617
        assert!(first_ref.get(Stat::Min).is_none());
1✔
618
    }
1✔
619

620
    #[test]
621
    fn merge_from_min() {
1✔
622
        let first = StatsSet::default().merge_ordered(
1✔
623
            &StatsSet::of(Stat::Min, Precision::exact(42)),
1✔
624
            &DType::Primitive(PType::I32, Nullability::NonNullable),
1✔
625
        );
626

627
        let first_ref = first.as_typed_ref(&DType::Primitive(PType::I32, Nullability::NonNullable));
1✔
628
        assert!(first_ref.get(Stat::Min).is_none());
1✔
629
    }
1✔
630

631
    #[test]
632
    fn merge_mins() {
1✔
633
        let first = StatsSet::of(Stat::Min, Precision::exact(37)).merge_ordered(
1✔
634
            &StatsSet::of(Stat::Min, Precision::exact(42)),
1✔
635
            &DType::Primitive(PType::I32, Nullability::NonNullable),
1✔
636
        );
637

638
        let first_ref = first.as_typed_ref(&DType::Primitive(PType::I32, Nullability::NonNullable));
1✔
639
        assert_eq!(
1✔
640
            first_ref.get_as::<i32>(Stat::Min),
1✔
641
            Some(Precision::exact(37))
1✔
642
        );
643
    }
1✔
644

645
    #[test]
646
    fn merge_into_bound_max() {
1✔
647
        let first = StatsSet::of(Stat::Max, Precision::exact(42)).merge_ordered(
1✔
648
            &StatsSet::default(),
1✔
649
            &DType::Primitive(PType::I32, Nullability::NonNullable),
1✔
650
        );
651
        assert!(first.get(Stat::Max).is_none());
1✔
652
    }
1✔
653

654
    #[test]
655
    fn merge_from_max() {
1✔
656
        let first = StatsSet::default().merge_ordered(
1✔
657
            &StatsSet::of(Stat::Max, Precision::exact(42)),
1✔
658
            &DType::Primitive(PType::I32, Nullability::NonNullable),
1✔
659
        );
660
        assert!(first.get(Stat::Max).is_none());
1✔
661
    }
1✔
662

663
    #[test]
664
    fn merge_maxes() {
1✔
665
        let first = StatsSet::of(Stat::Max, Precision::exact(37)).merge_ordered(
1✔
666
            &StatsSet::of(Stat::Max, Precision::exact(42)),
1✔
667
            &DType::Primitive(PType::I32, Nullability::NonNullable),
1✔
668
        );
669
        let first_ref = first.as_typed_ref(&DType::Primitive(PType::I32, Nullability::NonNullable));
1✔
670
        assert_eq!(
1✔
671
            first_ref.get_as::<i32>(Stat::Max),
1✔
672
            Some(Precision::exact(42))
1✔
673
        );
674
    }
1✔
675

676
    #[test]
677
    fn merge_maxes_bound() {
1✔
678
        let dtype = DType::Primitive(PType::I32, Nullability::NonNullable);
1✔
679
        let first = StatsSet::of(Stat::Max, Precision::exact(42i32))
1✔
680
            .merge_ordered(&StatsSet::of(Stat::Max, Precision::inexact(43i32)), &dtype);
1✔
681
        let first_ref = first.as_typed_ref(&dtype);
1✔
682
        assert_eq!(
1✔
683
            first_ref.get_as::<i32>(Stat::Max),
1✔
684
            Some(Precision::inexact(43))
1✔
685
        );
686
    }
1✔
687

688
    #[test]
689
    fn merge_into_scalar() {
1✔
690
        let first = StatsSet::of(Stat::Sum, Precision::exact(42)).merge_ordered(
1✔
691
            &StatsSet::default(),
1✔
692
            &DType::Primitive(PType::I32, Nullability::NonNullable),
1✔
693
        );
694
        let first_ref = first.as_typed_ref(&DType::Primitive(PType::I32, Nullability::NonNullable));
1✔
695
        assert!(first_ref.get(Stat::Sum).is_none());
1✔
696
    }
1✔
697

698
    #[test]
699
    fn merge_from_scalar() {
1✔
700
        let first = StatsSet::default().merge_ordered(
1✔
701
            &StatsSet::of(Stat::Sum, Precision::exact(42)),
1✔
702
            &DType::Primitive(PType::I32, Nullability::NonNullable),
1✔
703
        );
704
        let first_ref = first.as_typed_ref(&DType::Primitive(PType::I32, Nullability::NonNullable));
1✔
705
        assert!(first_ref.get(Stat::Sum).is_none());
1✔
706
    }
1✔
707

708
    #[test]
709
    fn merge_scalars() {
1✔
710
        let first = StatsSet::of(Stat::Sum, Precision::exact(37)).merge_ordered(
1✔
711
            &StatsSet::of(Stat::Sum, Precision::exact(42)),
1✔
712
            &DType::Primitive(PType::I32, Nullability::NonNullable),
1✔
713
        );
714
        let first_ref = first.as_typed_ref(&DType::Primitive(PType::I32, Nullability::NonNullable));
1✔
715
        assert_eq!(
1✔
716
            first_ref.get_as::<usize>(Stat::Sum),
1✔
717
            Some(Precision::exact(79usize))
1✔
718
        );
719
    }
1✔
720

721
    #[test]
722
    fn merge_into_sortedness() {
1✔
723
        let first = StatsSet::of(Stat::IsStrictSorted, Precision::exact(true)).merge_ordered(
1✔
724
            &StatsSet::default(),
1✔
725
            &DType::Primitive(PType::I32, Nullability::NonNullable),
1✔
726
        );
727
        assert!(first.get(Stat::IsStrictSorted).is_none());
1✔
728
    }
1✔
729

730
    #[test]
731
    fn merge_from_sortedness() {
1✔
732
        let first = StatsSet::default().merge_ordered(
1✔
733
            &StatsSet::of(Stat::IsStrictSorted, Precision::exact(true)),
1✔
734
            &DType::Primitive(PType::I32, Nullability::NonNullable),
1✔
735
        );
736
        assert!(first.get(Stat::IsStrictSorted).is_none());
1✔
737
    }
1✔
738

739
    #[test]
740
    fn merge_sortedness() {
1✔
741
        let mut first = StatsSet::of(Stat::IsStrictSorted, Precision::exact(true));
1✔
742
        first.set(Stat::Max, Precision::exact(1));
1✔
743
        let mut second = StatsSet::of(Stat::IsStrictSorted, Precision::exact(true));
1✔
744
        second.set(Stat::Min, Precision::exact(2));
1✔
745
        first = first.merge_ordered(
1✔
746
            &second,
1✔
747
            &DType::Primitive(PType::I32, Nullability::NonNullable),
1✔
748
        );
749

750
        let first_ref = first.as_typed_ref(&DType::Primitive(PType::I32, Nullability::NonNullable));
1✔
751
        assert_eq!(
1✔
752
            first_ref.get_as::<bool>(Stat::IsStrictSorted),
1✔
753
            Some(Precision::exact(true))
1✔
754
        );
755
    }
1✔
756

757
    #[test]
758
    fn merge_sortedness_out_of_order() {
1✔
759
        let mut first = StatsSet::of(Stat::IsStrictSorted, Precision::exact(true));
1✔
760
        first.set(Stat::Min, Precision::exact(1));
1✔
761
        let mut second = StatsSet::of(Stat::IsStrictSorted, Precision::exact(true));
1✔
762
        second.set(Stat::Max, Precision::exact(2));
1✔
763
        second = second.merge_ordered(
1✔
764
            &first,
1✔
765
            &DType::Primitive(PType::I32, Nullability::NonNullable),
1✔
766
        );
767

768
        let second_ref =
1✔
769
            second.as_typed_ref(&DType::Primitive(PType::I32, Nullability::NonNullable));
1✔
770
        assert_eq!(
1✔
771
            second_ref.get_as::<bool>(Stat::IsStrictSorted),
1✔
772
            Some(Precision::inexact(false))
1✔
773
        );
774
    }
1✔
775

776
    #[test]
777
    fn merge_sortedness_only_one_sorted() {
1✔
778
        let mut first = StatsSet::of(Stat::IsStrictSorted, Precision::exact(true));
1✔
779
        first.set(Stat::Max, Precision::exact(1));
1✔
780
        let mut second = StatsSet::of(Stat::IsStrictSorted, Precision::exact(false));
1✔
781
        second.set(Stat::Min, Precision::exact(2));
1✔
782
        first.merge_ordered(
1✔
783
            &second,
1✔
784
            &DType::Primitive(PType::I32, Nullability::NonNullable),
1✔
785
        );
786

787
        let second_ref =
1✔
788
            second.as_typed_ref(&DType::Primitive(PType::I32, Nullability::NonNullable));
1✔
789
        assert_eq!(
1✔
790
            second_ref.get_as::<bool>(Stat::IsStrictSorted),
1✔
791
            Some(Precision::exact(false))
1✔
792
        );
793
    }
1✔
794

795
    #[test]
796
    fn merge_sortedness_missing_min() {
1✔
797
        let mut first = StatsSet::of(Stat::IsStrictSorted, Precision::exact(true));
1✔
798
        first.set(Stat::Max, Precision::exact(1));
1✔
799
        let second = StatsSet::of(Stat::IsStrictSorted, Precision::exact(true));
1✔
800
        first = first.merge_ordered(
1✔
801
            &second,
1✔
802
            &DType::Primitive(PType::I32, Nullability::NonNullable),
1✔
803
        );
804
        assert!(first.get(Stat::IsStrictSorted).is_none());
1✔
805
    }
1✔
806

807
    #[test]
808
    fn merge_sortedness_bound_min() {
1✔
809
        let mut first = StatsSet::of(Stat::IsStrictSorted, Precision::exact(true));
1✔
810
        first.set(Stat::Max, Precision::exact(1));
1✔
811
        let mut second = StatsSet::of(Stat::IsStrictSorted, Precision::exact(true));
1✔
812
        second.set(Stat::Min, Precision::inexact(2));
1✔
813
        first = first.merge_ordered(
1✔
814
            &second,
1✔
815
            &DType::Primitive(PType::I32, Nullability::NonNullable),
1✔
816
        );
817

818
        let first_ref = first.as_typed_ref(&DType::Primitive(PType::I32, Nullability::NonNullable));
1✔
819
        assert_eq!(
1✔
820
            first_ref.get_as::<bool>(Stat::IsStrictSorted),
1✔
821
            Some(Precision::exact(true))
1✔
822
        );
823
    }
1✔
824

825
    #[test]
826
    fn merge_unordered() {
1✔
827
        let array =
1✔
828
            PrimitiveArray::from_option_iter([Some(1), None, Some(2), Some(42), Some(10000), None]);
1✔
829
        let all_stats = all::<Stat>()
1✔
830
            .filter(|s| !matches!(s, Stat::Sum))
9✔
831
            .filter(|s| !matches!(s, Stat::NaNCount))
8✔
832
            .collect_vec();
1✔
833
        array.statistics().compute_all(&all_stats).unwrap();
1✔
834

835
        let stats = array.statistics().to_owned();
1✔
836
        for stat in &all_stats {
8✔
837
            assert!(stats.get(*stat).is_some(), "Stat {stat} is missing");
7✔
838
        }
839

840
        let merged = stats.clone().merge_unordered(
1✔
841
            &stats,
1✔
842
            &DType::Primitive(PType::I32, Nullability::NonNullable),
1✔
843
        );
844
        for stat in &all_stats {
8✔
845
            assert_eq!(
7✔
846
                merged.get(*stat).is_some(),
7✔
847
                stat.is_commutative(),
7✔
848
                "Stat {stat} remains after merge_unordered despite not being commutative, or was removed despite being commutative"
849
            )
850
        }
851

852
        let merged_ref = merged.as_typed_ref(&DType::Primitive(PType::I32, Nullability::Nullable));
1✔
853
        let stats_ref = stats.as_typed_ref(&DType::Primitive(PType::I32, Nullability::Nullable));
1✔
854

855
        assert_eq!(
1✔
856
            merged_ref.get_as::<i32>(Stat::Min),
1✔
857
            stats_ref.get_as::<i32>(Stat::Min)
1✔
858
        );
859
        assert_eq!(
1✔
860
            merged_ref.get_as::<i32>(Stat::Max),
1✔
861
            stats_ref.get_as::<i32>(Stat::Max)
1✔
862
        );
863
        assert_eq!(
1✔
864
            merged_ref.get_as::<u64>(Stat::NullCount).unwrap(),
1✔
865
            stats_ref
1✔
866
                .get_as::<u64>(Stat::NullCount)
1✔
867
                .unwrap()
1✔
868
                .map(|s| s * 2)
1✔
869
        );
870
    }
1✔
871

872
    #[test]
873
    fn merge_min_bound_same() {
1✔
874
        // Merging a stat with a bound and another with an exact results in exact stat.
875
        // since bound for min is a lower bound, it can in fact contain any value >= bound.
876
        let merged = StatsSet::of(Stat::Min, Precision::inexact(5)).merge_ordered(
1✔
877
            &StatsSet::of(Stat::Min, Precision::exact(5)),
1✔
878
            &DType::Primitive(PType::I32, Nullability::NonNullable),
1✔
879
        );
880
        let merged_ref =
1✔
881
            merged.as_typed_ref(&DType::Primitive(PType::I32, Nullability::NonNullable));
1✔
882
        assert_eq!(
1✔
883
            merged_ref.get_as::<i32>(Stat::Min),
1✔
884
            Some(Precision::exact(5))
1✔
885
        );
886
    }
1✔
887

888
    #[test]
889
    fn merge_min_bound_bound_lower() {
1✔
890
        let merged = StatsSet::of(Stat::Min, Precision::inexact(4)).merge_ordered(
1✔
891
            &StatsSet::of(Stat::Min, Precision::exact(5)),
1✔
892
            &DType::Primitive(PType::I32, Nullability::NonNullable),
1✔
893
        );
894
        let merged_ref =
1✔
895
            merged.as_typed_ref(&DType::Primitive(PType::I32, Nullability::NonNullable));
1✔
896
        assert_eq!(
1✔
897
            merged_ref.get_as::<i32>(Stat::Min),
1✔
898
            Some(Precision::inexact(4))
1✔
899
        );
900
    }
1✔
901

902
    #[test]
903
    fn retain_approx() {
1✔
904
        let set = StatsSet::from_iter([
1✔
905
            (Stat::Max, Precision::exact(100)),
1✔
906
            (Stat::Min, Precision::exact(50)),
1✔
907
            (Stat::Sum, Precision::inexact(10)),
1✔
908
        ]);
1✔
909

910
        let set = set.keep_inexact_stats(&[Stat::Min, Stat::Max]);
1✔
911

912
        let set_ref = set.as_typed_ref(&DType::Primitive(PType::I32, Nullability::NonNullable));
1✔
913

914
        assert_eq!(set.len(), 2);
1✔
915
        assert_eq!(
1✔
916
            set_ref.get_as::<i32>(Stat::Max),
1✔
917
            Some(Precision::inexact(100))
1✔
918
        );
919
        assert_eq!(
1✔
920
            set_ref.get_as::<i32>(Stat::Min),
1✔
921
            Some(Precision::inexact(50))
1✔
922
        );
923
        assert_eq!(set_ref.get_as::<i32>(Stat::Sum), None);
1✔
924
    }
1✔
925

926
    #[test]
927
    fn test_combine_is_constant() {
1✔
928
        {
929
            let mut stats = StatsSet::of(Stat::IsConstant, Precision::exact(true));
1✔
930
            let stats2 = StatsSet::of(Stat::IsConstant, Precision::exact(true));
1✔
931
            let mut stats_ref =
1✔
932
                stats.as_mut_typed_ref(&DType::Primitive(PType::I32, Nullability::NonNullable));
1✔
933
            stats_ref
1✔
934
                .combine_bool_stat::<IsConstant>(
1✔
935
                    &stats2.as_typed_ref(&DType::Primitive(PType::I32, Nullability::NonNullable)),
1✔
936
                )
937
                .unwrap();
1✔
938
            assert_eq!(
1✔
939
                stats_ref.get_as::<bool>(Stat::IsConstant),
1✔
940
                Some(Precision::exact(true))
1✔
941
            );
942
        }
943

944
        {
945
            let mut stats = StatsSet::of(Stat::IsConstant, Precision::exact(true));
1✔
946
            let stats2 = StatsSet::of(Stat::IsConstant, Precision::inexact(false));
1✔
947
            let mut stats_ref =
1✔
948
                stats.as_mut_typed_ref(&DType::Primitive(PType::I32, Nullability::NonNullable));
1✔
949
            stats_ref
1✔
950
                .combine_bool_stat::<IsConstant>(
1✔
951
                    &stats2.as_typed_ref(&DType::Primitive(PType::I32, Nullability::NonNullable)),
1✔
952
                )
953
                .unwrap();
1✔
954
            assert_eq!(
1✔
955
                stats_ref.get_as::<bool>(Stat::IsConstant),
1✔
956
                Some(Precision::exact(true))
1✔
957
            );
958
        }
959

960
        {
961
            let mut stats = StatsSet::of(Stat::IsConstant, Precision::exact(false));
1✔
962
            let stats2 = StatsSet::of(Stat::IsConstant, Precision::inexact(false));
1✔
963
            let mut stats_ref =
1✔
964
                stats.as_mut_typed_ref(&DType::Primitive(PType::I32, Nullability::NonNullable));
1✔
965
            stats_ref
1✔
966
                .combine_bool_stat::<IsConstant>(
1✔
967
                    &stats2.as_typed_ref(&DType::Primitive(PType::I32, Nullability::NonNullable)),
1✔
968
                )
969
                .unwrap();
1✔
970
            assert_eq!(
1✔
971
                stats_ref.get_as::<bool>(Stat::IsConstant),
1✔
972
                Some(Precision::exact(false))
1✔
973
            );
974
        }
975
    }
1✔
976

977
    #[test]
978
    fn test_combine_sets_boolean_conflict() {
1✔
979
        let mut stats1 = StatsSet::from_iter([
1✔
980
            (Stat::IsConstant, Precision::exact(true)),
1✔
981
            (Stat::IsSorted, Precision::exact(true)),
1✔
982
        ]);
1✔
983

984
        let stats2 = StatsSet::from_iter([
1✔
985
            (Stat::IsConstant, Precision::exact(false)),
1✔
986
            (Stat::IsSorted, Precision::exact(true)),
1✔
987
        ]);
1✔
988

989
        let result = stats1.combine_sets(
1✔
990
            &stats2,
1✔
991
            &DType::Primitive(PType::I32, Nullability::NonNullable),
1✔
992
        );
993
        assert!(result.is_err());
1✔
994
    }
1✔
995

996
    #[test]
997
    fn test_combine_sets_with_missing_stats() {
1✔
998
        let mut stats1 = StatsSet::from_iter([
1✔
999
            (Stat::Min, Precision::exact(42)),
1✔
1000
            (Stat::UncompressedSizeInBytes, Precision::exact(1000)),
1✔
1001
        ]);
1✔
1002

1003
        let stats2 = StatsSet::from_iter([
1✔
1004
            (Stat::Max, Precision::exact(100)),
1✔
1005
            (Stat::IsStrictSorted, Precision::exact(true)),
1✔
1006
        ]);
1✔
1007

1008
        stats1
1✔
1009
            .combine_sets(
1✔
1010
                &stats2,
1✔
1011
                &DType::Primitive(PType::I32, Nullability::NonNullable),
1✔
1012
            )
1013
            .unwrap();
1✔
1014

1015
        let stats_ref =
1✔
1016
            stats1.as_typed_ref(&DType::Primitive(PType::I32, Nullability::NonNullable));
1✔
1017

1018
        // Min should remain unchanged
1019
        assert_eq!(
1✔
1020
            stats_ref.get_as::<i32>(Stat::Min),
1✔
1021
            Some(Precision::exact(42))
1✔
1022
        );
1023
        // Max should be added
1024
        assert_eq!(
1✔
1025
            stats_ref.get_as::<i32>(Stat::Max),
1✔
1026
            Some(Precision::exact(100))
1✔
1027
        );
1028
        // IsStrictSorted should be added
1029
        assert_eq!(
1✔
1030
            stats_ref.get_as::<bool>(Stat::IsStrictSorted),
1✔
1031
            Some(Precision::exact(true))
1✔
1032
        );
1033
    }
1✔
1034

1035
    #[test]
1036
    fn test_combine_sets_with_inexact() {
1✔
1037
        let mut stats1 = StatsSet::from_iter([
1✔
1038
            (Stat::Min, Precision::exact(42)),
1✔
1039
            (Stat::Max, Precision::inexact(100)),
1✔
1040
            (Stat::IsConstant, Precision::exact(false)),
1✔
1041
        ]);
1✔
1042

1043
        let stats2 = StatsSet::from_iter([
1✔
1044
            // Must ensure Min from stats2 is <= Min from stats1
1✔
1045
            (Stat::Min, Precision::inexact(40)),
1✔
1046
            (Stat::Max, Precision::exact(90)),
1✔
1047
            (Stat::IsSorted, Precision::exact(true)),
1✔
1048
        ]);
1✔
1049

1050
        stats1
1✔
1051
            .combine_sets(
1✔
1052
                &stats2,
1✔
1053
                &DType::Primitive(PType::I32, Nullability::NonNullable),
1✔
1054
            )
1055
            .unwrap();
1✔
1056

1057
        let stats_ref =
1✔
1058
            stats1.as_typed_ref(&DType::Primitive(PType::I32, Nullability::NonNullable));
1✔
1059

1060
        // Min should remain unchanged since it's more restrictive than the inexact value
1061
        assert_eq!(
1✔
1062
            stats_ref.get_as::<i32>(Stat::Min),
1✔
1063
            Some(Precision::exact(42))
1✔
1064
        );
1065
        // Check that max was updated with the exact value
1066
        assert_eq!(
1✔
1067
            stats_ref.get_as::<i32>(Stat::Max),
1✔
1068
            Some(Precision::exact(90))
1✔
1069
        );
1070
        // Check that IsSorted was added
1071
        assert_eq!(
1✔
1072
            stats_ref.get_as::<bool>(Stat::IsSorted),
1✔
1073
            Some(Precision::exact(true))
1✔
1074
        );
1075
    }
1✔
1076
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc