1use std::sync::Arc;
7use std::sync::OnceLock;
8
9use parking_lot::RwLock;
10use vortex_array::ExecutionCtx;
11use vortex_error::VortexError;
12use vortex_error::VortexResult;
13use vortex_error::vortex_panic;
14
15use super::MutTypedStatsSetRef;
16use super::StatsSet;
17use super::StatsSetIntoIter;
18use super::TypedStatsSetRef;
19use crate::ArrayRef;
20use crate::aggregate_fn::NumericalAggregateOpts;
21use crate::aggregate_fn::fns::is_constant::is_constant;
22use crate::aggregate_fn::fns::is_sorted::is_sorted;
23use crate::aggregate_fn::fns::is_sorted::is_strict_sorted;
24use crate::aggregate_fn::fns::min_max::MinMaxResult;
25use crate::aggregate_fn::fns::min_max::min_max;
26use crate::aggregate_fn::fns::nan_count::nan_count;
27use crate::aggregate_fn::fns::sum::sum;
28use crate::aggregate_fn::fns::uncompressed_size_in_bytes::uncompressed_size_in_bytes;
29use crate::expr::stats::Precision;
30use crate::expr::stats::Stat;
31use crate::expr::stats::StatsProvider;
32use crate::scalar::Scalar;
33use crate::scalar::ScalarValue;
34
35#[derive(Default, Debug)]
39pub struct ArrayStats {
40 inner: OnceLock<Arc<RwLock<StatsSet>>>,
41}
42
43impl Clone for ArrayStats {
44 fn clone(&self) -> Self {
45 Self {
46 inner: OnceLock::from(Arc::clone(self.shared())),
47 }
48 }
49}
50
51pub struct StatsSetRef<'a> {
55 dyn_array_ref: &'a ArrayRef,
57 array_stats: &'a ArrayStats,
58}
59
60impl ArrayStats {
61 pub fn to_ref<'a>(&'a self, array: &'a ArrayRef) -> StatsSetRef<'a> {
62 StatsSetRef {
63 dyn_array_ref: array,
64 array_stats: self,
65 }
66 }
67
68 fn shared(&self) -> &Arc<RwLock<StatsSet>> {
69 self.inner.get_or_init(Default::default)
70 }
71
72 fn read<R>(&self, f: impl FnOnce(&StatsSet) -> R) -> R {
73 match self.inner.get() {
74 Some(shared) => f(&shared.read()),
75 None => f(&StatsSet::default()),
76 }
77 }
78
79 pub fn set(&self, stat: Stat, value: Precision<ScalarValue>) {
80 self.shared().write().set(stat, value);
81 }
82
83 pub fn clear(&self, stat: Stat) {
84 if let Some(shared) = self.inner.get() {
85 shared.write().clear(stat);
86 }
87 }
88
89 pub fn retain(&self, stats: &[Stat]) {
90 if let Some(shared) = self.inner.get() {
91 shared.write().retain_only(stats);
92 }
93 }
94}
95
96impl From<StatsSet> for ArrayStats {
97 fn from(value: StatsSet) -> Self {
98 Self {
99 inner: OnceLock::from(Arc::new(RwLock::new(value))),
100 }
101 }
102}
103
104impl From<ArrayStats> for StatsSet {
105 fn from(value: ArrayStats) -> Self {
106 value.read(|stats| stats.clone())
107 }
108}
109
110impl StatsSetRef<'_> {
111 pub(crate) fn replace(&self, stats: StatsSet) {
112 if stats.is_empty() && self.array_stats.inner.get().is_none() {
113 return;
114 }
115 *self.array_stats.shared().write() = stats;
116 }
117
118 pub fn set_iter(&self, iter: StatsSetIntoIter) {
119 let mut iter = iter.peekable();
120 if iter.peek().is_none() {
121 return;
122 }
123 let mut guard = self.array_stats.shared().write();
124 for (stat, value) in iter {
125 guard.set(stat, value);
126 }
127 }
128
129 pub fn inherit_from(&self, stats: StatsSetRef<'_>) {
130 let Some(source) = stats.array_stats.inner.get() else {
131 return;
132 };
133 if self
135 .array_stats
136 .inner
137 .get()
138 .is_none_or(|shared| !Arc::ptr_eq(shared, source))
139 {
140 stats.with_iter(|iter| self.inherit(iter));
141 }
142 }
143
144 pub fn inherit<'a>(&self, iter: impl Iterator<Item = &'a (Stat, Precision<ScalarValue>)>) {
145 let mut iter = iter.peekable();
146 if iter.peek().is_none() {
147 return;
148 }
149 let mut guard = self.array_stats.shared().write();
150 for (stat, value) in iter {
151 if !value.is_exact() {
152 if !guard.get(*stat).is_exact() {
153 guard.set(*stat, value.clone());
154 }
155 } else {
156 guard.set(*stat, value.clone());
157 }
158 }
159 }
160
161 pub fn with_typed_stats_set<U, F: FnOnce(TypedStatsSetRef) -> U>(&self, apply: F) -> U {
162 self.array_stats
163 .read(|stats| apply(stats.as_typed_ref(self.dyn_array_ref.dtype())))
164 }
165
166 pub fn with_mut_typed_stats_set<U, F: FnOnce(MutTypedStatsSetRef) -> U>(&self, apply: F) -> U {
167 apply(
168 self.array_stats
169 .shared()
170 .write()
171 .as_mut_typed_ref(self.dyn_array_ref.dtype()),
172 )
173 }
174
175 pub fn to_owned(&self) -> StatsSet {
176 self.array_stats.read(|stats| stats.clone())
177 }
178
179 pub fn to_array_stats(&self) -> ArrayStats {
181 self.array_stats.clone()
182 }
183
184 pub(crate) fn share_existing(&self) -> ArrayStats {
186 ArrayStats {
187 inner: match self.array_stats.inner.get() {
188 Some(shared) => OnceLock::from(Arc::clone(shared)),
189 None => OnceLock::new(),
190 },
191 }
192 }
193
194 pub fn with_iter<
195 F: for<'a> FnOnce(&mut dyn Iterator<Item = &'a (Stat, Precision<ScalarValue>)>) -> R,
196 R,
197 >(
198 &self,
199 f: F,
200 ) -> R {
201 self.array_stats.read(|stats| f(&mut stats.iter()))
202 }
203
204 pub fn compute_stat(&self, stat: Stat, ctx: &mut ExecutionCtx) -> VortexResult<Option<Scalar>> {
207 if let Precision::Exact(s) = self.get(stat) {
209 return Ok(Some(s));
210 }
211
212 Ok(match stat {
213 Stat::Min => min_max(self.dyn_array_ref, ctx, NumericalAggregateOpts::default())?
214 .map(|MinMaxResult { min, max: _ }| min),
215 Stat::Max => min_max(self.dyn_array_ref, ctx, NumericalAggregateOpts::default())?
216 .map(|MinMaxResult { min: _, max }| max),
217 Stat::Sum => {
218 Stat::Sum
219 .dtype(self.dyn_array_ref.dtype())
220 .is_some()
221 .then(|| {
222 sum(self.dyn_array_ref, ctx)
224 })
225 .transpose()?
226 }
227 Stat::NullCount => self.dyn_array_ref.invalid_count(ctx).ok().map(Into::into),
228 Stat::IsConstant => {
229 if self.dyn_array_ref.is_empty() {
230 None
231 } else {
232 Some(is_constant(self.dyn_array_ref, ctx)?.into())
233 }
234 }
235 Stat::IsSorted => Some(is_sorted(self.dyn_array_ref, ctx)?.into()),
236 Stat::IsStrictSorted => Some(is_strict_sorted(self.dyn_array_ref, ctx)?.into()),
237 Stat::UncompressedSizeInBytes => Stat::UncompressedSizeInBytes
238 .dtype(self.dyn_array_ref.dtype())
239 .is_some()
240 .then(|| uncompressed_size_in_bytes(self.dyn_array_ref, ctx))
241 .transpose()?
242 .map(|s| s.into()),
243 Stat::NaNCount => {
244 Stat::NaNCount
245 .dtype(self.dyn_array_ref.dtype())
246 .is_some()
247 .then(|| {
248 nan_count(self.dyn_array_ref, ctx)
250 })
251 .transpose()?
252 .map(|s| s.into())
253 }
254 })
255 }
256
257 pub fn compute_all(&self, stats: &[Stat], ctx: &mut ExecutionCtx) -> VortexResult<StatsSet> {
258 let mut stats_set = StatsSet::default();
259 for &stat in stats {
260 if let Some(s) = self.compute_stat(stat, ctx)?
261 && let Some(value) = s.into_value()
262 {
263 stats_set.set(stat, Precision::exact(value));
264 }
265 }
266 Ok(stats_set)
267 }
268}
269
270impl StatsSetRef<'_> {
271 pub fn compute_as<U: for<'a> TryFrom<&'a Scalar, Error = VortexError>>(
272 &self,
273 stat: Stat,
274 ctx: &mut ExecutionCtx,
275 ) -> Option<U> {
276 self.compute_stat(stat, ctx)
277 .inspect_err(|e| tracing::warn!("Failed to compute stat {stat}: {e}"))
278 .ok()
279 .flatten()
280 .map(|s| U::try_from(&s))
281 .transpose()
282 .unwrap_or_else(|err| {
283 vortex_panic!(
284 err,
285 "Failed to compute stat {} as {}",
286 stat,
287 std::any::type_name::<U>()
288 )
289 })
290 }
291
292 pub fn set(&self, stat: Stat, value: Precision<ScalarValue>) {
293 self.array_stats.set(stat, value);
294 }
295
296 pub fn clear(&self, stat: Stat) {
297 self.array_stats.clear(stat);
298 }
299
300 pub fn compute_min<U: for<'a> TryFrom<&'a Scalar, Error = VortexError>>(
301 &self,
302 ctx: &mut ExecutionCtx,
303 ) -> Option<U> {
304 self.compute_as(Stat::Min, ctx)
305 }
306
307 pub fn compute_max<U: for<'a> TryFrom<&'a Scalar, Error = VortexError>>(
308 &self,
309 ctx: &mut ExecutionCtx,
310 ) -> Option<U> {
311 self.compute_as(Stat::Max, ctx)
312 }
313
314 pub fn compute_is_sorted(&self, ctx: &mut ExecutionCtx) -> Option<bool> {
315 self.compute_bool(Stat::IsSorted, ctx)
316 }
317
318 pub fn compute_is_strict_sorted(&self, ctx: &mut ExecutionCtx) -> Option<bool> {
319 self.compute_bool(Stat::IsStrictSorted, ctx)
320 }
321
322 pub fn compute_is_constant(&self, ctx: &mut ExecutionCtx) -> Option<bool> {
323 self.compute_bool(Stat::IsConstant, ctx)
324 }
325
326 pub fn compute_null_count(&self, ctx: &mut ExecutionCtx) -> Option<usize> {
327 self.compute_usize(Stat::NullCount, ctx)
328 }
329
330 pub fn compute_uncompressed_size_in_bytes(&self, ctx: &mut ExecutionCtx) -> Option<usize> {
331 self.compute_usize(Stat::UncompressedSizeInBytes, ctx)
332 }
333
334 fn compute_bool(&self, stat: Stat, ctx: &mut ExecutionCtx) -> Option<bool> {
336 let cached = self.array_stats.read(|stats| stats.get_bool(stat));
338 if let Precision::Exact(value) = cached {
339 return Some(value);
340 }
341
342 self.compute_as(stat, ctx)
343 }
344
345 fn compute_usize(&self, stat: Stat, ctx: &mut ExecutionCtx) -> Option<usize> {
347 let cached = self.array_stats.read(|stats| stats.get_usize(stat));
349 if let Precision::Exact(value) = cached {
350 return Some(value);
351 }
352
353 self.compute_as(stat, ctx)
354 }
355}
356
357impl StatsProvider for StatsSetRef<'_> {
358 fn get(&self, stat: Stat) -> Precision<Scalar> {
359 self.array_stats
360 .read(|stats| stats.as_typed_ref(self.dyn_array_ref.dtype()).get(stat))
361 }
362
363 fn len(&self) -> usize {
364 self.array_stats.read(|stats| stats.len())
365 }
366}
367
368#[cfg(test)]
369mod tests {
370 use super::ArrayStats;
371 use super::StatsSet;
372 use crate::expr::stats::Precision;
373 use crate::expr::stats::Stat;
374 use crate::scalar::ScalarValue;
375
376 #[test]
377 fn empty_stats() {
378 let stats = ArrayStats::default();
379 assert_eq!(StatsSet::from(stats).len(), 0);
380 }
381
382 #[test]
383 fn clone_stats() {
384 let stats = ArrayStats::default();
385 let copy = stats.clone();
386
387 stats.set(Stat::NullCount, Precision::exact(ScalarValue::from(3u64)));
388 let through_copy = StatsSet::from(copy);
389 assert_eq!(
390 through_copy.get(Stat::NullCount),
391 Precision::exact(ScalarValue::from(3u64))
392 );
393 }
394
395 #[test]
396 fn clone_writes() {
397 let stats = ArrayStats::default();
398 let copy = stats.clone();
399
400 copy.set(Stat::NullCount, Precision::exact(ScalarValue::from(7u64)));
401 assert_eq!(
402 StatsSet::from(stats).get(Stat::NullCount),
403 Precision::exact(ScalarValue::from(7u64))
404 );
405 }
406}