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

vortex-data / vortex / 16728097825

04 Aug 2025 04:00PM UTC coverage: 48.355% (-35.1%) from 83.429%
16728097825

Pull #4108

github

web-flow
Merge 1b2d27fd8 into 649ba9576
Pull Request #4108: perf[vortex-array]: use all_valid instead of `invalid_count() == 0`

1 of 1 new or added line in 1 file covered. (100.0%)

11596 existing lines in 378 files now uncovered.

18635 of 38538 relevant lines covered (48.35%)

151786.4 hits per line

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

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

4
//! Stats as they are stored on arrays.
5

6
use std::sync::Arc;
7

8
use parking_lot::RwLock;
9
use vortex_error::{VortexError, VortexResult, vortex_panic};
10
use vortex_scalar::ScalarValue;
11

12
use super::{
13
    Precision, Stat, StatType, StatsProvider, StatsProviderExt, StatsSet, StatsSetIntoIter,
14
};
15
use crate::Array;
16
use crate::compute::{
17
    MinMaxResult, is_constant, is_sorted, is_strict_sorted, min_max, nan_count, sum,
18
};
19

20
/// A shared [`StatsSet`] stored in an array. Can be shared by copies of the array and can also be mutated in place.
21
// TODO(adamg): This is a very bad name.
22
#[derive(Clone, Default, Debug)]
23
pub struct ArrayStats {
24
    inner: Arc<RwLock<StatsSet>>,
25
}
26

27
/// Reference to an array's [`StatsSet`]. Can be used to get and mutate the underlying stats.
28
///
29
/// Constructed by calling [`ArrayStats::to_ref`].
30
pub struct StatsSetRef<'a> {
31
    // We need to reference back to the array
32
    dyn_array_ref: &'a dyn Array,
33
    array_stats: &'a ArrayStats,
34
}
35

36
impl ArrayStats {
37
    pub fn to_ref<'a>(&'a self, array: &'a dyn Array) -> StatsSetRef<'a> {
510,588✔
38
        StatsSetRef {
510,588✔
39
            dyn_array_ref: array,
510,588✔
40
            array_stats: self,
510,588✔
41
        }
510,588✔
42
    }
510,588✔
43

44
    pub fn set(&self, stat: Stat, value: Precision<ScalarValue>) {
72,888✔
45
        self.inner.write().set(stat, value);
72,888✔
46
    }
72,888✔
47

48
    pub fn clear(&self, stat: Stat) {
×
49
        self.inner.write().clear(stat);
×
50
    }
×
51

52
    pub fn retain(&self, stats: &[Stat]) {
×
53
        self.inner.write().retain_only(stats);
×
54
    }
×
55
}
56

57
impl From<StatsSet> for ArrayStats {
58
    fn from(value: StatsSet) -> Self {
×
59
        Self {
×
60
            inner: Arc::new(RwLock::new(value)),
×
61
        }
×
62
    }
×
63
}
64

65
impl From<ArrayStats> for StatsSet {
66
    fn from(value: ArrayStats) -> Self {
×
67
        value.inner.read().clone()
×
68
    }
×
69
}
70

71
impl StatsProvider for ArrayStats {
72
    fn get(&self, stat: Stat) -> Option<Precision<ScalarValue>> {
193,276✔
73
        let guard = self.inner.read();
193,276✔
74
        guard.get(stat)
193,276✔
75
    }
193,276✔
76

77
    fn len(&self) -> usize {
×
78
        let guard = self.inner.read();
×
79
        guard.len()
×
80
    }
×
81
}
82

83
impl StatsSetRef<'_> {
UNCOV
84
    pub fn set_iter(&self, iter: StatsSetIntoIter) {
×
UNCOV
85
        let mut guard = self.array_stats.inner.write();
×
UNCOV
86
        for (stat, value) in iter {
×
UNCOV
87
            guard.set(stat, value);
×
UNCOV
88
        }
×
UNCOV
89
    }
×
90

91
    pub fn inherit_from(&self, stats: StatsSetRef<'_>) {
×
92
        stats.with_iter(|iter| self.inherit(iter));
×
93
    }
×
94

95
    pub fn inherit<'a>(&self, iter: impl Iterator<Item = &'a (Stat, Precision<ScalarValue>)>) {
30,530✔
96
        // TODO(ngates): depending on statistic, this should choose the more precise one
97
        let mut guard = self.array_stats.inner.write();
30,530✔
98
        for (stat, value) in iter {
31,344✔
99
            guard.set(*stat, value.clone());
814✔
100
        }
814✔
101
    }
30,530✔
102

103
    pub fn replace(&self, stats: StatsSet) {
96,658✔
104
        *self.array_stats.inner.write() = stats;
96,658✔
105
    }
96,658✔
106

107
    pub fn to_owned(&self) -> StatsSet {
100,340✔
108
        self.array_stats.inner.read().clone()
100,340✔
109
    }
100,340✔
110

111
    pub fn with_iter<
30,530✔
112
        F: for<'a> FnOnce(&mut dyn Iterator<Item = &'a (Stat, Precision<ScalarValue>)>) -> R,
30,530✔
113
        R,
30,530✔
114
    >(
30,530✔
115
        &self,
30,530✔
116
        f: F,
30,530✔
117
    ) -> R {
30,530✔
118
        let lock = self.array_stats.inner.read();
30,530✔
119
        f(&mut lock.iter())
30,530✔
120
    }
30,530✔
121

122
    pub fn compute_stat(&self, stat: Stat) -> VortexResult<Option<ScalarValue>> {
52,480✔
123
        // If it's already computed and exact, we can return it.
124
        if let Some(Precision::Exact(stat)) = self.get(stat) {
52,480✔
125
            return Ok(Some(stat));
30,958✔
126
        }
21,522✔
127

128
        Ok(match stat {
21,522✔
129
            Stat::Min => {
130
                min_max(self.dyn_array_ref)?.map(|MinMaxResult { min, max: _ }| min.into_value())
9,518✔
131
            }
132
            Stat::Max => {
133
                min_max(self.dyn_array_ref)?.map(|MinMaxResult { min: _, max }| max.into_value())
760✔
134
            }
135
            Stat::Sum => {
136
                Stat::Sum
3,668✔
137
                    .dtype(self.dyn_array_ref.dtype())
3,668✔
138
                    .is_some()
3,668✔
139
                    .then(|| {
3,668✔
140
                        // Sum is supported for this dtype.
141
                        sum(self.dyn_array_ref)
1,506✔
142
                    })
1,506✔
143
                    .transpose()?
3,668✔
144
                    .map(|s| s.into_value())
3,668✔
145
            }
146
            Stat::NullCount => Some(self.dyn_array_ref.invalid_count()?.into()),
1,032✔
147
            Stat::IsConstant => {
148
                if self.dyn_array_ref.is_empty() {
788✔
UNCOV
149
                    None
×
150
                } else {
151
                    is_constant(self.dyn_array_ref)?.map(ScalarValue::from)
788✔
152
                }
153
            }
154
            Stat::IsSorted => Some(is_sorted(self.dyn_array_ref)?.into()),
638✔
155
            Stat::IsStrictSorted => Some(is_strict_sorted(self.dyn_array_ref)?.into()),
918✔
156
            Stat::UncompressedSizeInBytes => {
157
                let nbytes: ScalarValue =
638✔
158
                    self.dyn_array_ref.to_canonical()?.as_ref().nbytes().into();
638✔
159
                self.set(stat, Precision::exact(nbytes.clone()));
638✔
160
                Some(nbytes)
638✔
161
            }
162
            Stat::NaNCount => {
163
                Stat::NaNCount
3,562✔
164
                    .dtype(self.dyn_array_ref.dtype())
3,562✔
165
                    .is_some()
3,562✔
166
                    .then(|| {
3,562✔
167
                        // NaNCount is supported for this dtype.
UNCOV
168
                        nan_count(self.dyn_array_ref)
×
UNCOV
169
                    })
×
170
                    .transpose()?
3,562✔
171
                    .map(|s| s.into())
3,562✔
172
            }
173
        })
174
    }
52,480✔
175

176
    pub fn compute_all(&self, stats: &[Stat]) -> VortexResult<StatsSet> {
3,562✔
177
        let mut stats_set = StatsSet::default();
3,562✔
178
        for &stat in stats {
23,924✔
179
            if let Some(s) = self.compute_stat(stat)? {
20,362✔
180
                stats_set.set(stat, Precision::exact(s))
14,394✔
181
            }
5,968✔
182
        }
183
        Ok(stats_set)
3,562✔
184
    }
3,562✔
185
}
186

187
impl StatsSetRef<'_> {
188
    pub fn get_as<U: for<'a> TryFrom<&'a ScalarValue, Error = VortexError>>(
87,488✔
189
        &self,
87,488✔
190
        stat: Stat,
87,488✔
191
    ) -> Option<Precision<U>> {
87,488✔
192
        StatsProviderExt::get_as::<U>(self, stat)
87,488✔
193
    }
87,488✔
194

195
    pub fn get_as_bound<S, U>(&self) -> Option<S::Bound>
×
196
    where
×
197
        S: StatType<U>,
×
198
        U: for<'a> TryFrom<&'a ScalarValue, Error = VortexError>,
×
199
    {
200
        StatsProviderExt::get_as_bound::<S, U>(self)
×
201
    }
×
202

203
    pub fn compute_as<U: for<'a> TryFrom<&'a ScalarValue, Error = VortexError>>(
20,412✔
204
        &self,
20,412✔
205
        stat: Stat,
20,412✔
206
    ) -> Option<U> {
20,412✔
207
        self.compute_stat(stat)
20,412✔
208
            .inspect_err(|e| log::warn!("Failed to compute stat {stat}: {e}"))
20,412✔
209
            .ok()
20,412✔
210
            .flatten()
20,412✔
211
            .map(|s| U::try_from(&s))
20,412✔
212
            .transpose()
20,412✔
213
            .unwrap_or_else(|err| {
20,412✔
214
                vortex_panic!(
×
215
                    err,
×
216
                    "Failed to compute stat {} as {}",
×
217
                    stat,
218
                    std::any::type_name::<U>()
×
219
                )
220
            })
221
    }
20,412✔
222

223
    pub fn set(&self, stat: Stat, value: Precision<ScalarValue>) {
72,888✔
224
        self.array_stats.set(stat, value);
72,888✔
225
    }
72,888✔
226

227
    pub fn clear(&self, stat: Stat) {
×
228
        self.array_stats.clear(stat);
×
229
    }
×
230

231
    pub fn retain(&self, stats: &[Stat]) {
×
232
        self.array_stats.retain(stats);
×
233
    }
×
234

235
    pub fn compute_min<U: for<'a> TryFrom<&'a ScalarValue, Error = VortexError>>(
4,016✔
236
        &self,
4,016✔
237
    ) -> Option<U> {
4,016✔
238
        self.compute_as(Stat::Min)
4,016✔
239
    }
4,016✔
240

UNCOV
241
    pub fn compute_max<U: for<'a> TryFrom<&'a ScalarValue, Error = VortexError>>(
×
UNCOV
242
        &self,
×
UNCOV
243
    ) -> Option<U> {
×
UNCOV
244
        self.compute_as(Stat::Max)
×
UNCOV
245
    }
×
246

247
    pub fn compute_is_sorted(&self) -> Option<bool> {
×
248
        self.compute_as(Stat::IsSorted)
×
249
    }
×
250

251
    pub fn compute_is_strict_sorted(&self) -> Option<bool> {
822✔
252
        self.compute_as(Stat::IsStrictSorted)
822✔
253
    }
822✔
254

255
    pub fn compute_is_constant(&self) -> Option<bool> {
150✔
256
        self.compute_as(Stat::IsConstant)
150✔
257
    }
150✔
258

259
    pub fn compute_null_count(&self) -> Option<usize> {
1,392✔
260
        self.compute_as(Stat::NullCount)
1,392✔
261
    }
1,392✔
262

263
    pub fn compute_uncompressed_size_in_bytes(&self) -> Option<usize> {
×
264
        self.compute_as(Stat::UncompressedSizeInBytes)
×
265
    }
×
266
}
267

268
impl StatsProvider for StatsSetRef<'_> {
269
    fn get(&self, stat: Stat) -> Option<Precision<ScalarValue>> {
193,276✔
270
        self.array_stats.get(stat)
193,276✔
271
    }
193,276✔
272

273
    fn len(&self) -> usize {
×
274
        self.array_stats.len()
×
275
    }
×
276
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc