vortex_array/expr/stats/
mod.rs1use std::fmt::Debug;
5use std::fmt::Display;
6use std::fmt::Formatter;
7
8use enum_iterator::Sequence;
9use enum_iterator::all;
10use num_enum::IntoPrimitive;
11use num_enum::TryFromPrimitive;
12
13use crate::dtype::DType;
14use crate::dtype::Nullability::NonNullable;
15
16mod bound;
17mod precision;
18mod provider;
19mod stat_bound;
20
21pub use bound::*;
22pub use precision::*;
23pub use provider::*;
24pub use stat_bound::*;
25
26use crate::aggregate_fn;
27use crate::aggregate_fn::AggregateFnRef;
28use crate::aggregate_fn::AggregateFnVTable;
29use crate::aggregate_fn::AggregateFnVTableExt;
30use crate::aggregate_fn::EmptyOptions;
31use crate::aggregate_fn::NumericalAggregateOpts;
32use crate::aggregate_fn::fns::uncompressed_size_in_bytes::UncompressedSizeOpts;
33
34#[derive(
35 Debug,
36 Clone,
37 Copy,
38 PartialEq,
39 Eq,
40 PartialOrd,
41 Ord,
42 Hash,
43 Sequence,
44 IntoPrimitive,
45 TryFromPrimitive,
46)]
47#[repr(u8)]
48pub enum Stat {
49 IsConstant = 0,
52 IsSorted = 1,
55 IsStrictSorted = 2,
58 Max = 3,
60 Min = 4,
62 Sum = 5,
64 NullCount = 6,
66 UncompressedSizeInBytes = 7,
68 NaNCount = 8,
70}
71
72pub struct Max;
75
76pub struct Min;
77
78pub struct Sum;
79
80pub struct IsConstant;
81
82pub struct IsSorted;
83
84pub struct IsStrictSorted;
85
86pub struct NullCount;
87
88pub struct UncompressedSizeInBytes;
89
90pub struct NaNCount;
91
92impl StatType<bool> for IsConstant {
93 type Bound = Precision<bool>;
94
95 const STAT: Stat = Stat::IsConstant;
96}
97
98impl StatType<bool> for IsSorted {
99 type Bound = Precision<bool>;
100
101 const STAT: Stat = Stat::IsSorted;
102}
103
104impl StatType<bool> for IsStrictSorted {
105 type Bound = Precision<bool>;
106
107 const STAT: Stat = Stat::IsStrictSorted;
108}
109
110impl<T: PartialOrd + Clone> StatType<T> for NullCount {
111 type Bound = UpperBound<T>;
112
113 const STAT: Stat = Stat::NullCount;
114}
115
116impl<T: PartialOrd + Clone> StatType<T> for UncompressedSizeInBytes {
117 type Bound = UpperBound<T>;
118
119 const STAT: Stat = Stat::UncompressedSizeInBytes;
120}
121
122impl<T: PartialOrd + Clone + Debug> StatType<T> for Max {
123 type Bound = UpperBound<T>;
124
125 const STAT: Stat = Stat::Max;
126}
127
128impl<T: PartialOrd + Clone + Debug> StatType<T> for Min {
129 type Bound = LowerBound<T>;
130
131 const STAT: Stat = Stat::Min;
132}
133
134impl<T: PartialOrd + Clone + Debug> StatType<T> for Sum {
135 type Bound = Precision<T>;
136
137 const STAT: Stat = Stat::Sum;
138}
139
140impl<T: PartialOrd + Clone> StatType<T> for NaNCount {
141 type Bound = UpperBound<T>;
142
143 const STAT: Stat = Stat::NaNCount;
144}
145
146impl Stat {
147 pub fn is_commutative(&self) -> bool {
150 match self {
152 Self::IsConstant
153 | Self::Max
154 | Self::Min
155 | Self::NullCount
156 | Self::Sum
157 | Self::NaNCount
158 | Self::UncompressedSizeInBytes => true,
159 Self::IsSorted | Self::IsStrictSorted => false,
160 }
161 }
162
163 pub fn has_same_dtype_as_array(&self) -> bool {
165 matches!(self, Stat::Min | Stat::Max)
166 }
167
168 pub fn dtype(&self, data_type: &DType) -> Option<DType> {
170 Some(match self {
171 Self::IsConstant => DType::Bool(NonNullable),
172 Self::IsSorted => DType::Bool(NonNullable),
173 Self::IsStrictSorted => DType::Bool(NonNullable),
174 Self::Max if matches!(data_type, DType::Null) => return None,
175 Self::Max => data_type.clone(),
176 Self::Min if matches!(data_type, DType::Null) => return None,
177 Self::Min => data_type.clone(),
178 Self::NullCount => {
179 return aggregate_fn::fns::null_count::NullCount
180 .return_dtype(&EmptyOptions, data_type);
181 }
182 Self::UncompressedSizeInBytes => {
183 return aggregate_fn::fns::uncompressed_size_in_bytes::UncompressedSizeInBytes
184 .return_dtype(&UncompressedSizeOpts::exact(), data_type);
185 }
186 Self::NaNCount => {
187 return aggregate_fn::fns::nan_count::NanCount
188 .return_dtype(&EmptyOptions, data_type);
189 }
190 Self::Sum => {
191 return aggregate_fn::fns::sum::Sum
193 .return_dtype(&NumericalAggregateOpts::skip_nans(), data_type);
194 }
195 })
196 }
197
198 pub fn aggregate_fn(&self) -> Option<AggregateFnRef> {
200 Some(match self {
202 Self::Max => aggregate_fn::fns::max::Max.bind(NumericalAggregateOpts::skip_nans()),
203 Self::Min => aggregate_fn::fns::min::Min.bind(NumericalAggregateOpts::skip_nans()),
204 Self::Sum => aggregate_fn::fns::sum::Sum.bind(NumericalAggregateOpts::skip_nans()),
205 Self::NullCount => aggregate_fn::fns::null_count::NullCount.bind(EmptyOptions),
206 Self::NaNCount => aggregate_fn::fns::nan_count::NanCount.bind(EmptyOptions),
207 Self::UncompressedSizeInBytes => {
209 aggregate_fn::fns::uncompressed_size_in_bytes::UncompressedSizeInBytes
210 .bind(UncompressedSizeOpts::exact())
211 }
212 Self::IsConstant | Self::IsSorted | Self::IsStrictSorted => return None,
213 })
214 }
215
216 pub fn from_aggregate_fn(aggregate_fn: &AggregateFnRef) -> Option<Self> {
222 if let Some(options) = aggregate_fn.as_opt::<aggregate_fn::fns::sum::Sum>() {
223 return options.skip_nans.then_some(Self::Sum);
224 }
225 if aggregate_fn.is::<aggregate_fn::fns::nan_count::NanCount>() {
226 return Some(Self::NaNCount);
227 }
228 if aggregate_fn.is::<aggregate_fn::fns::null_count::NullCount>() {
229 return Some(Self::NullCount);
230 }
231 if let Some(options) = aggregate_fn.as_opt::<aggregate_fn::fns::min::Min>() {
232 return options.skip_nans.then_some(Self::Min);
233 }
234 if let Some(options) = aggregate_fn.as_opt::<aggregate_fn::fns::max::Max>() {
235 return options.skip_nans.then_some(Self::Max);
236 }
237 if let Some(options) =
238 aggregate_fn
239 .as_opt::<aggregate_fn::fns::uncompressed_size_in_bytes::UncompressedSizeInBytes>()
240 {
241 return options.is_exact().then_some(Self::UncompressedSizeInBytes);
242 }
243 None
244 }
245
246 pub fn name(&self) -> &str {
247 match self {
248 Self::IsConstant => "is_constant",
249 Self::IsSorted => "is_sorted",
250 Self::IsStrictSorted => "is_strict_sorted",
251 Self::Max => "max",
252 Self::Min => "min",
253 Self::NullCount => "null_count",
254 Self::UncompressedSizeInBytes => "uncompressed_size_in_bytes",
255 Self::Sum => "sum",
256 Self::NaNCount => "nan_count",
257 }
258 }
259
260 pub fn all() -> impl Iterator<Item = Stat> {
261 all::<Self>()
262 }
263}
264
265impl Display for Stat {
266 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
267 write!(f, "{}", self.name())
268 }
269}
270
271#[cfg(test)]
272mod test {
273 use enum_iterator::all;
274
275 use crate::VortexSessionExecute;
276 use crate::array_session;
277 use crate::arrays::PrimitiveArray;
278 use crate::expr::stats::Stat;
279
280 #[test]
281 fn min_of_nulls_is_not_panic() {
282 let min = PrimitiveArray::from_option_iter::<i32, _>([None, None, None, None])
283 .statistics()
284 .compute_as::<i64>(Stat::Min, &mut array_session().create_execution_ctx());
285
286 assert_eq!(min, None);
287 }
288
289 #[test]
290 fn has_same_dtype_as_array() {
291 assert!(Stat::Min.has_same_dtype_as_array());
292 assert!(Stat::Max.has_same_dtype_as_array());
293 for stat in all::<Stat>().filter(|s| !matches!(s, Stat::Min | Stat::Max)) {
294 assert!(!stat.has_same_dtype_as_array());
295 }
296 }
297}