Skip to main content

parquet/file/
statistics.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18//! Contains definitions for working with Parquet statistics.
19//!
20//! Though some common methods are available on enum, use pattern match to extract
21//! actual min and max values from statistics, see below:
22//!
23//! # Examples
24//! ```rust
25//! use parquet::file::statistics::Statistics;
26//!
27//! let stats = Statistics::int32(Some(1), Some(10), None, Some(3), true);
28//! assert_eq!(stats.null_count_opt(), Some(3));
29//! assert!(stats.is_min_max_deprecated());
30//! assert!(stats.min_is_exact());
31//! assert!(stats.max_is_exact());
32//!
33//! match stats {
34//!     Statistics::Int32(ref typed) => {
35//!         assert_eq!(typed.min_opt(), Some(&1));
36//!         assert_eq!(typed.max_opt(), Some(&10));
37//!     }
38//!     _ => {}
39//! }
40//! ```
41
42use std::fmt;
43
44use crate::basic::Type;
45use crate::data_type::private::ParquetValueType;
46use crate::data_type::*;
47use crate::errors::{ParquetError, Result};
48use crate::file::metadata::thrift::PageStatistics;
49use crate::util::bit_util::FromBytes;
50
51pub(crate) mod private {
52    use super::*;
53
54    pub trait MakeStatistics {
55        fn make_statistics(statistics: ValueStatistics<Self>) -> Statistics
56        where
57            Self: Sized;
58    }
59
60    macro_rules! gen_make_statistics {
61        ($value_ty:ty, $stat:ident) => {
62            impl MakeStatistics for $value_ty {
63                fn make_statistics(statistics: ValueStatistics<Self>) -> Statistics
64                where
65                    Self: Sized,
66                {
67                    Statistics::$stat(statistics)
68                }
69            }
70        };
71    }
72
73    gen_make_statistics!(bool, Boolean);
74    gen_make_statistics!(i32, Int32);
75    gen_make_statistics!(i64, Int64);
76    gen_make_statistics!(Int96, Int96);
77    gen_make_statistics!(f32, Float);
78    gen_make_statistics!(f64, Double);
79    gen_make_statistics!(ByteArray, ByteArray);
80    gen_make_statistics!(FixedLenByteArray, FixedLenByteArray);
81}
82
83/// Macro to generate methods to create Statistics.
84macro_rules! statistics_new_func {
85    ($func:ident, $vtype:ty, $stat:ident) => {
86        #[doc = concat!("Creates new statistics for `", stringify!($stat), "` column type.")]
87        pub fn $func(
88            min: $vtype,
89            max: $vtype,
90            distinct: Option<u64>,
91            nulls: Option<u64>,
92            is_deprecated: bool,
93        ) -> Self {
94            Statistics::$stat(ValueStatistics::new(
95                min,
96                max,
97                distinct,
98                nulls,
99                is_deprecated,
100            ))
101        }
102    };
103}
104
105// Macro to generate getter functions for Statistics.
106macro_rules! statistics_enum_func {
107    ($self:ident, $func:ident) => {{
108        match *$self {
109            Statistics::Boolean(ref typed) => typed.$func(),
110            Statistics::Int32(ref typed) => typed.$func(),
111            Statistics::Int64(ref typed) => typed.$func(),
112            Statistics::Int96(ref typed) => typed.$func(),
113            Statistics::Float(ref typed) => typed.$func(),
114            Statistics::Double(ref typed) => typed.$func(),
115            Statistics::ByteArray(ref typed) => typed.$func(),
116            Statistics::FixedLenByteArray(ref typed) => typed.$func(),
117        }
118    }};
119}
120
121/// Converts Thrift definition into `Statistics`.
122pub(crate) fn from_thrift_page_stats(
123    physical_type: Type,
124    thrift_stats: Option<PageStatistics>,
125) -> Result<Option<Statistics>> {
126    Ok(match thrift_stats {
127        Some(stats) => {
128            // Generic null count.
129            let null_count = stats
130                .null_count
131                .map(|null_count| {
132                    if null_count < 0 {
133                        return Err(ParquetError::General(format!(
134                            "Statistics null count is negative {null_count}",
135                        )));
136                    }
137                    Ok(null_count as u64)
138                })
139                .transpose()?;
140            // Generic distinct count (count of distinct values occurring)
141            let distinct_count = stats.distinct_count.map(|value| value as u64);
142            // Generic nan count for floating point types
143            let nan_count = stats
144                .nan_count
145                .map(|nan_count| {
146                    if nan_count < 0 {
147                        return Err(ParquetError::General(format!(
148                            "Statistics NaN count is negative {nan_count}",
149                        )));
150                    }
151                    Ok(nan_count as u64)
152                })
153                .transpose()?;
154            // Whether or not statistics use deprecated min/max fields.
155            let old_format = stats.min_value.is_none() && stats.max_value.is_none();
156            // Generic min value as bytes.
157            let min = if old_format {
158                stats.min
159            } else {
160                stats.min_value
161            };
162            // Generic max value as bytes.
163            let max = if old_format {
164                stats.max
165            } else {
166                stats.max_value
167            };
168
169            fn check_len(min: &Option<Vec<u8>>, max: &Option<Vec<u8>>, len: usize) -> Result<()> {
170                if let Some(min) = min
171                    && min.len() < len
172                {
173                    return Err(ParquetError::General(
174                        "Insufficient bytes to parse min statistic".to_string(),
175                    ));
176                }
177                if let Some(max) = max
178                    && max.len() < len
179                {
180                    return Err(ParquetError::General(
181                        "Insufficient bytes to parse max statistic".to_string(),
182                    ));
183                }
184                Ok(())
185            }
186
187            match physical_type {
188                Type::BOOLEAN => check_len(&min, &max, 1),
189                Type::INT32 | Type::FLOAT => check_len(&min, &max, 4),
190                Type::INT64 | Type::DOUBLE => check_len(&min, &max, 8),
191                Type::INT96 => check_len(&min, &max, 12),
192                _ => Ok(()),
193            }?;
194
195            // Values are encoded using PLAIN encoding definition, except that
196            // variable-length byte arrays do not include a length prefix.
197            //
198            // Instead of using actual decoder, we manually convert values.
199            let res = match physical_type {
200                Type::BOOLEAN => Statistics::boolean(
201                    min.map(|data| data[0] != 0),
202                    max.map(|data| data[0] != 0),
203                    distinct_count,
204                    null_count,
205                    old_format,
206                ),
207                Type::INT32 => Statistics::int32(
208                    min.map(|data| i32::from_le_bytes(data[..4].try_into().unwrap())),
209                    max.map(|data| i32::from_le_bytes(data[..4].try_into().unwrap())),
210                    distinct_count,
211                    null_count,
212                    old_format,
213                ),
214                Type::INT64 => Statistics::int64(
215                    min.map(|data| i64::from_le_bytes(data[..8].try_into().unwrap())),
216                    max.map(|data| i64::from_le_bytes(data[..8].try_into().unwrap())),
217                    distinct_count,
218                    null_count,
219                    old_format,
220                ),
221                Type::INT96 => {
222                    // INT96 statistics may not be correct, because comparison is signed
223                    let min = if let Some(data) = min {
224                        if data.len() != 12 {
225                            return Err(ParquetError::General(
226                                "Incorrect Int96 min statistics".to_string(),
227                            ));
228                        }
229                        Some(Int96::try_from_le_slice(&data)?)
230                    } else {
231                        None
232                    };
233                    let max = if let Some(data) = max {
234                        if data.len() != 12 {
235                            return Err(ParquetError::General(
236                                "Incorrect Int96 max statistics".to_string(),
237                            ));
238                        }
239                        Some(Int96::try_from_le_slice(&data)?)
240                    } else {
241                        None
242                    };
243                    Statistics::int96(min, max, distinct_count, null_count, old_format)
244                }
245                Type::FLOAT => Statistics::Float(
246                    ValueStatistics::new(
247                        min.map(|data| f32::from_le_bytes(data[..4].try_into().unwrap())),
248                        max.map(|data| f32::from_le_bytes(data[..4].try_into().unwrap())),
249                        distinct_count,
250                        null_count,
251                        old_format,
252                    )
253                    .with_nan_count(nan_count)
254                    .with_max_is_exact(stats.is_max_value_exact.unwrap_or(false))
255                    .with_min_is_exact(stats.is_min_value_exact.unwrap_or(false)),
256                ),
257                Type::DOUBLE => Statistics::Double(
258                    ValueStatistics::new(
259                        min.map(|data| f64::from_le_bytes(data[..8].try_into().unwrap())),
260                        max.map(|data| f64::from_le_bytes(data[..8].try_into().unwrap())),
261                        distinct_count,
262                        null_count,
263                        old_format,
264                    )
265                    .with_nan_count(nan_count)
266                    .with_max_is_exact(stats.is_max_value_exact.unwrap_or(false))
267                    .with_min_is_exact(stats.is_min_value_exact.unwrap_or(false)),
268                ),
269                Type::BYTE_ARRAY => Statistics::ByteArray(
270                    ValueStatistics::new(
271                        min.map(ByteArray::from),
272                        max.map(ByteArray::from),
273                        distinct_count,
274                        null_count,
275                        old_format,
276                    )
277                    .with_max_is_exact(stats.is_max_value_exact.unwrap_or(false))
278                    .with_min_is_exact(stats.is_min_value_exact.unwrap_or(false)),
279                ),
280                Type::FIXED_LEN_BYTE_ARRAY => Statistics::FixedLenByteArray(
281                    ValueStatistics::new(
282                        min.map(ByteArray::from).map(FixedLenByteArray::from),
283                        max.map(ByteArray::from).map(FixedLenByteArray::from),
284                        distinct_count,
285                        null_count,
286                        old_format,
287                    )
288                    // Note: We set nan_count here even though we can't verify if this is Float16.
289                    // The spec says nan_count should only be set for Float16 logical type,
290                    // but this function doesn't have access to logical type information.
291                    // Writers should only set nan_count for Float16, and readers should
292                    // handle this gracefully.
293                    .with_nan_count(nan_count)
294                    .with_max_is_exact(stats.is_max_value_exact.unwrap_or(false))
295                    .with_min_is_exact(stats.is_min_value_exact.unwrap_or(false)),
296                ),
297            };
298
299            Some(res)
300        }
301        None => None,
302    })
303}
304
305/// Convert Statistics into Thrift definition.
306pub(crate) fn page_stats_to_thrift(stats: Option<&Statistics>) -> Option<PageStatistics> {
307    let stats = stats?;
308
309    // record null count if it can fit in i64
310    let null_count = stats
311        .null_count_opt()
312        .and_then(|value| i64::try_from(value).ok());
313
314    // record distinct count if it can fit in i64
315    let distinct_count = stats
316        .distinct_count_opt()
317        .and_then(|value| i64::try_from(value).ok());
318
319    // record nan count if it can fit in i64
320    let nan_count = stats
321        .nan_count_opt()
322        .and_then(|value| i64::try_from(value).ok());
323
324    let mut thrift_stats = PageStatistics {
325        max: None,
326        min: None,
327        null_count,
328        distinct_count,
329        max_value: None,
330        min_value: None,
331        is_max_value_exact: None,
332        is_min_value_exact: None,
333        nan_count,
334    };
335
336    // Get min/max if set.
337    let (min, max, min_exact, max_exact) = (
338        stats.min_bytes_opt().map(|x| x.to_vec()),
339        stats.max_bytes_opt().map(|x| x.to_vec()),
340        Some(stats.min_is_exact()),
341        Some(stats.max_is_exact()),
342    );
343    if stats.is_min_max_backwards_compatible() {
344        // Copy to deprecated min, max values for compatibility with older readers
345        thrift_stats.min.clone_from(&min);
346        thrift_stats.max.clone_from(&max);
347    }
348
349    if !stats.is_min_max_deprecated() {
350        thrift_stats.min_value = min;
351        thrift_stats.max_value = max;
352    }
353
354    thrift_stats.is_min_value_exact = min_exact;
355    thrift_stats.is_max_value_exact = max_exact;
356
357    Some(thrift_stats)
358}
359
360/// Strongly typed statistics for a column chunk within a row group.
361///
362/// This structure is a natively typed, in memory representation of the thrift
363/// `Statistics` structure in a Parquet file footer. The statistics stored in
364/// this structure can be used by query engines to skip decoding pages while
365/// reading parquet data.
366///
367/// Page level statistics are stored separately, in [ColumnIndexMetaData].
368///
369/// [ColumnIndexMetaData]: crate::file::page_index::column_index::ColumnIndexMetaData
370#[derive(Debug, Clone, PartialEq)]
371pub enum Statistics {
372    /// Statistics for Boolean column
373    Boolean(ValueStatistics<bool>),
374    /// Statistics for Int32 column
375    Int32(ValueStatistics<i32>),
376    /// Statistics for Int64 column
377    Int64(ValueStatistics<i64>),
378    /// Statistics for Int96 column
379    Int96(ValueStatistics<Int96>),
380    /// Statistics for Float column
381    Float(ValueStatistics<f32>),
382    /// Statistics for Double column
383    Double(ValueStatistics<f64>),
384    /// Statistics for ByteArray column
385    ByteArray(ValueStatistics<ByteArray>),
386    /// Statistics for FixedLenByteArray column
387    FixedLenByteArray(ValueStatistics<FixedLenByteArray>),
388}
389
390impl<T: ParquetValueType> From<ValueStatistics<T>> for Statistics {
391    fn from(t: ValueStatistics<T>) -> Self {
392        T::make_statistics(t)
393    }
394}
395
396impl Statistics {
397    /// Creates new statistics for a column type
398    pub fn new<T: ParquetValueType>(
399        min: Option<T>,
400        max: Option<T>,
401        distinct_count: Option<u64>,
402        null_count: Option<u64>,
403        is_deprecated: bool,
404    ) -> Self {
405        Self::from(ValueStatistics::new(
406            min,
407            max,
408            distinct_count,
409            null_count,
410            is_deprecated,
411        ))
412    }
413
414    statistics_new_func![boolean, Option<bool>, Boolean];
415
416    statistics_new_func![int32, Option<i32>, Int32];
417
418    statistics_new_func![int64, Option<i64>, Int64];
419
420    statistics_new_func![int96, Option<Int96>, Int96];
421
422    statistics_new_func![float, Option<f32>, Float];
423
424    statistics_new_func![double, Option<f64>, Double];
425
426    statistics_new_func![byte_array, Option<ByteArray>, ByteArray];
427
428    statistics_new_func![
429        fixed_len_byte_array,
430        Option<FixedLenByteArray>,
431        FixedLenByteArray
432    ];
433
434    /// Returns `true` if statistics have old `min` and `max` fields set.
435    /// This means that the column order is likely to be undefined, which, for old files
436    /// could mean a signed sort order of values.
437    ///
438    /// Refer to [`ColumnOrder`](crate::basic::ColumnOrder) and
439    /// [`SortOrder`](crate::basic::SortOrder) for more information.
440    pub fn is_min_max_deprecated(&self) -> bool {
441        statistics_enum_func![self, is_min_max_deprecated]
442    }
443
444    /// Old versions of parquet stored statistics in `min` and `max` fields, ordered
445    /// using signed comparison. This resulted in an undefined ordering for unsigned
446    /// quantities, such as booleans and unsigned integers.
447    ///
448    /// These fields were therefore deprecated in favour of `min_value` and `max_value`,
449    /// which have a type-defined sort order.
450    ///
451    /// However, not all readers have been updated. For backwards compatibility, this method
452    /// returns `true` if the statistics within this have a signed sort order, that is
453    /// compatible with being stored in the deprecated `min` and `max` fields
454    pub fn is_min_max_backwards_compatible(&self) -> bool {
455        statistics_enum_func![self, is_min_max_backwards_compatible]
456    }
457
458    /// Returns optional value of number of distinct values occurring.
459    /// When it is `None`, the value should be ignored.
460    pub fn distinct_count_opt(&self) -> Option<u64> {
461        statistics_enum_func![self, distinct_count]
462    }
463
464    /// Returns number of null values for the column, if known.
465    /// Note that this includes all nulls when column is part of the complex type.
466    ///
467    /// Note: Versions of this library prior to `58.1.0` returned `0` if the null count
468    /// was not available. This method now returns `None` in that case.
469    ///
470    /// Also, versions of this library prior to `53.1.0` did not store a null count
471    /// statistic when the null count was `0`.
472    ///
473    /// It is unsound to assume that missing nullcount stats mean the column contains no nulls,
474    /// but code that depends on the old behavior can restore it by defaulting to zero:
475    ///
476    /// ```no_run
477    /// # use parquet::file::statistics::Statistics;
478    /// # let statistics: Statistics = todo!();
479    /// let null_count = statistics.null_count_opt().unwrap_or(0);
480    /// ```
481    pub fn null_count_opt(&self) -> Option<u64> {
482        statistics_enum_func![self, null_count_opt]
483    }
484
485    /// Returns NaN count for floating point types, if known.
486    pub fn nan_count_opt(&self) -> Option<u64> {
487        statistics_enum_func![self, nan_count_opt]
488    }
489
490    /// Returns `true` if the min value is set, and is an exact min value.
491    pub fn min_is_exact(&self) -> bool {
492        statistics_enum_func![self, min_is_exact]
493    }
494
495    /// Returns `true` if the max value is set, and is an exact max value.
496    pub fn max_is_exact(&self) -> bool {
497        statistics_enum_func![self, max_is_exact]
498    }
499
500    /// Returns slice of bytes that represent min value, if min value is known.
501    pub fn min_bytes_opt(&self) -> Option<&[u8]> {
502        statistics_enum_func![self, min_bytes_opt]
503    }
504
505    /// Returns slice of bytes that represent max value, if max value is known.
506    pub fn max_bytes_opt(&self) -> Option<&[u8]> {
507        statistics_enum_func![self, max_bytes_opt]
508    }
509
510    /// Returns physical type associated with statistics.
511    pub fn physical_type(&self) -> Type {
512        match self {
513            Statistics::Boolean(_) => Type::BOOLEAN,
514            Statistics::Int32(_) => Type::INT32,
515            Statistics::Int64(_) => Type::INT64,
516            Statistics::Int96(_) => Type::INT96,
517            Statistics::Float(_) => Type::FLOAT,
518            Statistics::Double(_) => Type::DOUBLE,
519            Statistics::ByteArray(_) => Type::BYTE_ARRAY,
520            Statistics::FixedLenByteArray(_) => Type::FIXED_LEN_BYTE_ARRAY,
521        }
522    }
523}
524
525impl fmt::Display for Statistics {
526    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
527        match self {
528            Statistics::Boolean(typed) => write!(f, "{typed}"),
529            Statistics::Int32(typed) => write!(f, "{typed}"),
530            Statistics::Int64(typed) => write!(f, "{typed}"),
531            Statistics::Int96(typed) => write!(f, "{typed}"),
532            Statistics::Float(typed) => write!(f, "{typed}"),
533            Statistics::Double(typed) => write!(f, "{typed}"),
534            Statistics::ByteArray(typed) => write!(f, "{typed}"),
535            Statistics::FixedLenByteArray(typed) => write!(f, "{typed}"),
536        }
537    }
538}
539
540/// Typed implementation for [`Statistics`].
541pub type TypedStatistics<T> = ValueStatistics<<T as DataType>::T>;
542
543/// Typed statistics for one column chunk
544///
545/// See [`Statistics`] for more details
546#[derive(Clone, Eq, PartialEq)]
547pub struct ValueStatistics<T> {
548    min: Option<T>,
549    max: Option<T>,
550    // Distinct count could be omitted in some cases
551    distinct_count: Option<u64>,
552    null_count: Option<u64>,
553    // NaN count for floating point types
554    nan_count: Option<u64>,
555
556    // Whether or not the min or max values are exact, or truncated.
557    is_max_value_exact: bool,
558    is_min_value_exact: bool,
559
560    /// If `true` populate the deprecated `min` and `max` fields instead of
561    /// `min_value` and `max_value`
562    is_min_max_deprecated: bool,
563
564    /// If `true` the statistics are compatible with the deprecated `min` and
565    /// `max` fields. See [`ValueStatistics::is_min_max_backwards_compatible`]
566    is_min_max_backwards_compatible: bool,
567}
568
569impl<T> ValueStatistics<T> {
570    /// Creates new typed statistics.
571    pub fn new(
572        min: Option<T>,
573        max: Option<T>,
574        distinct_count: Option<u64>,
575        null_count: Option<u64>,
576        is_min_max_deprecated: bool,
577    ) -> Self {
578        Self {
579            is_max_value_exact: max.is_some(),
580            is_min_value_exact: min.is_some(),
581            min,
582            max,
583            distinct_count,
584            null_count,
585            nan_count: None,
586            is_min_max_deprecated,
587            is_min_max_backwards_compatible: is_min_max_deprecated,
588        }
589    }
590
591    /// Set whether the stored `min` field represents the exact
592    /// minimum, or just a bound on the minimum value.
593    ///
594    /// see [`Self::min_is_exact`]
595    pub fn with_min_is_exact(self, is_min_value_exact: bool) -> Self {
596        Self {
597            is_min_value_exact,
598            ..self
599        }
600    }
601
602    /// Set whether the stored `max` field represents the exact
603    /// maximum, or just a bound on the maximum value.
604    ///
605    /// see [`Self::max_is_exact`]
606    pub fn with_max_is_exact(self, is_max_value_exact: bool) -> Self {
607        Self {
608            is_max_value_exact,
609            ..self
610        }
611    }
612
613    /// Set whether to write the deprecated `min` and `max` fields
614    /// for compatibility with older parquet writers
615    ///
616    /// This should only be enabled if the field is signed,
617    /// see [`Self::is_min_max_backwards_compatible`]
618    pub fn with_backwards_compatible_min_max(self, backwards_compatible: bool) -> Self {
619        Self {
620            is_min_max_backwards_compatible: backwards_compatible,
621            ..self
622        }
623    }
624
625    /// Returns NaN count for floating point types.
626    pub fn nan_count_opt(&self) -> Option<u64> {
627        self.nan_count
628    }
629
630    /// Set the NaN count for floating point types.
631    pub fn with_nan_count(self, nan_count: Option<u64>) -> Self {
632        Self { nan_count, ..self }
633    }
634
635    /// Returns min value of the statistics, if known.
636    pub fn min_opt(&self) -> Option<&T> {
637        self.min.as_ref()
638    }
639
640    /// Returns max value of the statistics, if known.
641    pub fn max_opt(&self) -> Option<&T> {
642        self.max.as_ref()
643    }
644
645    /// Whether or not min and max values are set.
646    /// Normally both min/max values will be set to `Some(value)` or `None`.
647    pub(crate) fn _internal_has_min_max_set(&self) -> bool {
648        self.min.is_some() && self.max.is_some()
649    }
650
651    /// Whether or not max value is set, and is an exact value.
652    pub fn max_is_exact(&self) -> bool {
653        self.max.is_some() && self.is_max_value_exact
654    }
655
656    /// Whether or not min value is set, and is an exact value.
657    pub fn min_is_exact(&self) -> bool {
658        self.min.is_some() && self.is_min_value_exact
659    }
660
661    /// Returns optional value of number of distinct values occurring.
662    pub fn distinct_count(&self) -> Option<u64> {
663        self.distinct_count
664    }
665
666    /// Returns null count.
667    pub fn null_count_opt(&self) -> Option<u64> {
668        self.null_count
669    }
670
671    /// Returns `true` if statistics were created using old min/max fields.
672    fn is_min_max_deprecated(&self) -> bool {
673        self.is_min_max_deprecated
674    }
675
676    /// Old versions of parquet stored statistics in `min` and `max` fields, ordered
677    /// using signed comparison. This resulted in an undefined ordering for unsigned
678    /// quantities, such as booleans and unsigned integers.
679    ///
680    /// These fields were therefore deprecated in favour of `min_value` and `max_value`,
681    /// which have a type-defined sort order.
682    ///
683    /// However, not all readers have been updated. For backwards compatibility, this method
684    /// returns `true` if the statistics within this have a signed sort order, that is
685    /// compatible with being stored in the deprecated `min` and `max` fields
686    pub fn is_min_max_backwards_compatible(&self) -> bool {
687        self.is_min_max_backwards_compatible
688    }
689}
690
691impl<T: AsBytes> ValueStatistics<T> {
692    /// Returns min value as bytes of the statistics, if min value is known.
693    pub fn min_bytes_opt(&self) -> Option<&[u8]> {
694        self.min_opt().map(AsBytes::as_bytes)
695    }
696
697    /// Returns max value as bytes of the statistics, if max value is known.
698    pub fn max_bytes_opt(&self) -> Option<&[u8]> {
699        self.max_opt().map(AsBytes::as_bytes)
700    }
701}
702
703impl<T: ParquetValueType> fmt::Display for ValueStatistics<T> {
704    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
705        write!(f, "{{")?;
706        write!(f, "min: ")?;
707        match self.min {
708            Some(ref value) => write!(f, "{value}")?,
709            None => write!(f, "N/A")?,
710        }
711        write!(f, ", max: ")?;
712        match self.max {
713            Some(ref value) => write!(f, "{value}")?,
714            None => write!(f, "N/A")?,
715        }
716        write!(f, ", distinct_count: ")?;
717        match self.distinct_count {
718            Some(value) => write!(f, "{value}")?,
719            None => write!(f, "N/A")?,
720        }
721        write!(f, ", null_count: ")?;
722        match self.null_count {
723            Some(value) => write!(f, "{value}")?,
724            None => write!(f, "N/A")?,
725        }
726        write!(f, ", min_max_deprecated: {}", self.is_min_max_deprecated)?;
727        write!(f, ", max_value_exact: {}", self.is_max_value_exact)?;
728        write!(f, ", min_value_exact: {}", self.is_min_value_exact)?;
729        write!(f, "}}")
730    }
731}
732
733impl<T: ParquetValueType> fmt::Debug for ValueStatistics<T> {
734    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
735        write!(
736            f,
737            "{{min: {:?}, max: {:?}, distinct_count: {:?}, null_count: {:?}, \
738             min_max_deprecated: {}, min_max_backwards_compatible: {}, max_value_exact: {}, min_value_exact: {}}}",
739            self.min,
740            self.max,
741            self.distinct_count,
742            self.null_count,
743            self.is_min_max_deprecated,
744            self.is_min_max_backwards_compatible,
745            self.is_max_value_exact,
746            self.is_min_value_exact
747        )
748    }
749}
750
751#[cfg(test)]
752mod tests {
753    use core::f32;
754
755    use super::*;
756
757    #[test]
758    fn test_statistics_min_max_bytes() {
759        let stats = Statistics::int32(Some(-123), Some(234), None, Some(1), false);
760        assert_eq!(stats.min_bytes_opt(), Some((-123).as_bytes()));
761        assert_eq!(stats.max_bytes_opt(), Some(234.as_bytes()));
762
763        let stats = Statistics::byte_array(
764            Some(ByteArray::from(vec![1, 2, 3])),
765            Some(ByteArray::from(vec![3, 4, 5])),
766            None,
767            Some(1),
768            true,
769        );
770        assert_eq!(stats.min_bytes_opt().unwrap(), &[1, 2, 3]);
771        assert_eq!(stats.max_bytes_opt().unwrap(), &[3, 4, 5]);
772    }
773
774    #[test]
775    #[should_panic(expected = "General(\"Statistics null count is negative -10\")")]
776    fn test_statistics_negative_null_count() {
777        let thrift_stats = PageStatistics {
778            max: None,
779            min: None,
780            null_count: Some(-10),
781            distinct_count: None,
782            max_value: None,
783            min_value: None,
784            is_max_value_exact: None,
785            is_min_value_exact: None,
786            nan_count: None,
787        };
788
789        from_thrift_page_stats(Type::INT32, Some(thrift_stats)).unwrap();
790    }
791
792    #[test]
793    fn test_statistics_thrift_none() {
794        assert_eq!(from_thrift_page_stats(Type::INT32, None).unwrap(), None);
795        assert_eq!(
796            from_thrift_page_stats(Type::BYTE_ARRAY, None).unwrap(),
797            None
798        );
799    }
800
801    #[test]
802    fn test_statistics_debug() {
803        let stats = Statistics::int32(Some(1), Some(12), None, Some(12), true);
804        assert_eq!(
805            format!("{stats:?}"),
806            "Int32({min: Some(1), max: Some(12), distinct_count: None, null_count: Some(12), \
807             min_max_deprecated: true, min_max_backwards_compatible: true, max_value_exact: true, min_value_exact: true})"
808        );
809
810        let stats = Statistics::int32(None, None, None, Some(7), false);
811        assert_eq!(
812            format!("{stats:?}"),
813            "Int32({min: None, max: None, distinct_count: None, null_count: Some(7), \
814             min_max_deprecated: false, min_max_backwards_compatible: false, max_value_exact: false, min_value_exact: false})"
815        )
816    }
817
818    #[test]
819    fn test_statistics_display() {
820        let stats = Statistics::int32(Some(1), Some(12), None, Some(12), true);
821        assert_eq!(
822            format!("{stats}"),
823            "{min: 1, max: 12, distinct_count: N/A, null_count: 12, min_max_deprecated: true, max_value_exact: true, min_value_exact: true}"
824        );
825
826        let stats = Statistics::int64(None, None, None, Some(7), false);
827        assert_eq!(
828            format!("{stats}"),
829            "{min: N/A, max: N/A, distinct_count: N/A, null_count: 7, min_max_deprecated: \
830             false, max_value_exact: false, min_value_exact: false}"
831        );
832
833        let stats = Statistics::int96(
834            Some(Int96::from(vec![1, 0, 0])),
835            Some(Int96::from(vec![2, 3, 4])),
836            None,
837            Some(3),
838            true,
839        );
840        assert_eq!(
841            format!("{stats}"),
842            "{min: [1, 0, 0], max: [2, 3, 4], distinct_count: N/A, null_count: 3, \
843             min_max_deprecated: true, max_value_exact: true, min_value_exact: true}"
844        );
845
846        let stats = Statistics::ByteArray(
847            ValueStatistics::new(
848                Some(ByteArray::from(vec![1u8])),
849                Some(ByteArray::from(vec![2u8])),
850                Some(5),
851                Some(7),
852                false,
853            )
854            .with_max_is_exact(false)
855            .with_min_is_exact(false),
856        );
857        assert_eq!(
858            format!("{stats}"),
859            "{min: [1], max: [2], distinct_count: 5, null_count: 7, min_max_deprecated: false, max_value_exact: false, min_value_exact: false}"
860        );
861    }
862
863    #[test]
864    fn test_statistics_partial_eq() {
865        let expected = Statistics::int32(Some(12), Some(45), None, Some(11), true);
866
867        assert!(Statistics::int32(Some(12), Some(45), None, Some(11), true) == expected);
868        assert!(Statistics::int32(Some(11), Some(45), None, Some(11), true) != expected);
869        assert!(Statistics::int32(Some(12), Some(44), None, Some(11), true) != expected);
870        assert!(Statistics::int32(Some(12), Some(45), None, Some(23), true) != expected);
871        assert!(Statistics::int32(Some(12), Some(45), None, Some(11), false) != expected);
872
873        assert!(
874            Statistics::int32(Some(12), Some(45), None, Some(11), false)
875                != Statistics::int64(Some(12), Some(45), None, Some(11), false)
876        );
877
878        assert!(
879            Statistics::boolean(Some(false), Some(true), None, None, true)
880                != Statistics::double(Some(1.2), Some(4.5), None, None, true)
881        );
882
883        assert!(
884            Statistics::byte_array(
885                Some(ByteArray::from(vec![1, 2, 3])),
886                Some(ByteArray::from(vec![1, 2, 3])),
887                None,
888                None,
889                true
890            ) != Statistics::fixed_len_byte_array(
891                Some(ByteArray::from(vec![1, 2, 3]).into()),
892                Some(ByteArray::from(vec![1, 2, 3]).into()),
893                None,
894                None,
895                true,
896            )
897        );
898
899        assert!(
900            Statistics::byte_array(
901                Some(ByteArray::from(vec![1, 2, 3])),
902                Some(ByteArray::from(vec![1, 2, 3])),
903                None,
904                None,
905                true,
906            ) != Statistics::ByteArray(
907                ValueStatistics::new(
908                    Some(ByteArray::from(vec![1, 2, 3])),
909                    Some(ByteArray::from(vec![1, 2, 3])),
910                    None,
911                    None,
912                    true,
913                )
914                .with_max_is_exact(false)
915            )
916        );
917
918        assert!(
919            Statistics::fixed_len_byte_array(
920                Some(FixedLenByteArray::from(vec![1, 2, 3])),
921                Some(FixedLenByteArray::from(vec![1, 2, 3])),
922                None,
923                None,
924                true,
925            ) != Statistics::FixedLenByteArray(
926                ValueStatistics::new(
927                    Some(FixedLenByteArray::from(vec![1, 2, 3])),
928                    Some(FixedLenByteArray::from(vec![1, 2, 3])),
929                    None,
930                    None,
931                    true,
932                )
933                .with_min_is_exact(false)
934            )
935        );
936    }
937
938    #[test]
939    fn test_statistics_from_thrift() {
940        // Helper method to check statistics conversion.
941        fn check_stats(stats: Statistics) {
942            let tpe = stats.physical_type();
943            let thrift_stats = page_stats_to_thrift(Some(&stats));
944            assert_eq!(
945                from_thrift_page_stats(tpe, thrift_stats).unwrap(),
946                Some(stats)
947            );
948        }
949
950        check_stats(Statistics::boolean(
951            Some(false),
952            Some(true),
953            None,
954            Some(7),
955            true,
956        ));
957        check_stats(Statistics::boolean(
958            Some(false),
959            Some(true),
960            None,
961            Some(7),
962            true,
963        ));
964        check_stats(Statistics::boolean(
965            Some(false),
966            Some(true),
967            None,
968            Some(0),
969            false,
970        ));
971        check_stats(Statistics::boolean(
972            Some(true),
973            Some(true),
974            None,
975            Some(7),
976            true,
977        ));
978        check_stats(Statistics::boolean(
979            Some(false),
980            Some(false),
981            None,
982            Some(7),
983            true,
984        ));
985        check_stats(Statistics::boolean(None, None, None, Some(7), true));
986
987        check_stats(Statistics::int32(
988            Some(-100),
989            Some(500),
990            None,
991            Some(7),
992            true,
993        ));
994        check_stats(Statistics::int32(
995            Some(-100),
996            Some(500),
997            None,
998            Some(0),
999            false,
1000        ));
1001        check_stats(Statistics::int32(None, None, None, Some(7), true));
1002
1003        check_stats(Statistics::int64(
1004            Some(-100),
1005            Some(200),
1006            None,
1007            Some(7),
1008            true,
1009        ));
1010        check_stats(Statistics::int64(
1011            Some(-100),
1012            Some(200),
1013            None,
1014            Some(0),
1015            false,
1016        ));
1017        check_stats(Statistics::int64(None, None, None, Some(7), true));
1018
1019        check_stats(Statistics::float(Some(1.2), Some(3.4), None, Some(7), true));
1020        check_stats(Statistics::float(
1021            Some(1.2),
1022            Some(3.4),
1023            None,
1024            Some(0),
1025            false,
1026        ));
1027        check_stats(Statistics::float(None, None, None, Some(7), true));
1028
1029        check_stats(Statistics::double(
1030            Some(1.2),
1031            Some(3.4),
1032            None,
1033            Some(7),
1034            true,
1035        ));
1036        check_stats(Statistics::double(
1037            Some(1.2),
1038            Some(3.4),
1039            None,
1040            Some(0),
1041            false,
1042        ));
1043        check_stats(Statistics::double(None, None, None, Some(7), true));
1044
1045        check_stats(Statistics::byte_array(
1046            Some(ByteArray::from(vec![1, 2, 3])),
1047            Some(ByteArray::from(vec![3, 4, 5])),
1048            None,
1049            Some(7),
1050            true,
1051        ));
1052        check_stats(Statistics::byte_array(None, None, None, Some(7), true));
1053
1054        check_stats(Statistics::fixed_len_byte_array(
1055            Some(ByteArray::from(vec![1, 2, 3]).into()),
1056            Some(ByteArray::from(vec![3, 4, 5]).into()),
1057            None,
1058            Some(7),
1059            true,
1060        ));
1061        check_stats(Statistics::fixed_len_byte_array(
1062            None,
1063            None,
1064            None,
1065            Some(7),
1066            true,
1067        ));
1068    }
1069
1070    #[test]
1071    fn test_count_encoding() {
1072        statistics_count_test(None, None);
1073        statistics_count_test(Some(0), Some(0));
1074        statistics_count_test(Some(100), Some(2000));
1075        statistics_count_test(Some(1), None);
1076        statistics_count_test(None, Some(1));
1077    }
1078
1079    #[test]
1080    fn test_count_encoding_distinct_too_large() {
1081        // statistics are stored using i64, so test trying to store larger values
1082        let statistics = make_bool_stats(Some(u64::MAX), Some(100));
1083        let thrift_stats = page_stats_to_thrift(Some(&statistics)).unwrap();
1084        assert_eq!(thrift_stats.distinct_count, None); // can't store u64 max --> null
1085        assert_eq!(thrift_stats.null_count, Some(100));
1086    }
1087
1088    #[test]
1089    fn test_count_encoding_null_too_large() {
1090        // statistics are stored using i64, so test trying to store larger values
1091        let statistics = make_bool_stats(Some(100), Some(u64::MAX));
1092        let thrift_stats = page_stats_to_thrift(Some(&statistics)).unwrap();
1093        assert_eq!(thrift_stats.distinct_count, Some(100));
1094        assert_eq!(thrift_stats.null_count, None); // can' store u64 max --> null
1095    }
1096
1097    #[test]
1098    fn test_count_decoding_null_invalid() {
1099        let tstatistics = PageStatistics {
1100            null_count: Some(-42),
1101            max: None,
1102            min: None,
1103            distinct_count: None,
1104            max_value: None,
1105            min_value: None,
1106            is_max_value_exact: None,
1107            is_min_value_exact: None,
1108            nan_count: None,
1109        };
1110        let err = from_thrift_page_stats(Type::BOOLEAN, Some(tstatistics)).unwrap_err();
1111        assert_eq!(
1112            err.to_string(),
1113            "Parquet error: Statistics null count is negative -42"
1114        );
1115    }
1116
1117    /// Writes statistics to thrift and reads them back and ensures:
1118    /// - The statistics are the same
1119    /// - The statistics written to thrift are the same as the original statistics
1120    fn statistics_count_test(distinct_count: Option<u64>, null_count: Option<u64>) {
1121        let statistics = make_bool_stats(distinct_count, null_count);
1122
1123        let thrift_stats = page_stats_to_thrift(Some(&statistics)).unwrap();
1124        assert_eq!(thrift_stats.null_count.map(|c| c as u64), null_count);
1125        assert_eq!(
1126            thrift_stats.distinct_count.map(|c| c as u64),
1127            distinct_count
1128        );
1129
1130        let round_tripped = from_thrift_page_stats(Type::BOOLEAN, Some(thrift_stats))
1131            .unwrap()
1132            .unwrap();
1133        assert_eq!(round_tripped, statistics);
1134    }
1135
1136    fn make_bool_stats(distinct_count: Option<u64>, null_count: Option<u64>) -> Statistics {
1137        let min = Some(true);
1138        let max = Some(false);
1139        let is_min_max_deprecated = false;
1140
1141        // test is about the counts, so we aren't really testing the min/max values
1142        Statistics::Boolean(ValueStatistics::new(
1143            min,
1144            max,
1145            distinct_count,
1146            null_count,
1147            is_min_max_deprecated,
1148        ))
1149    }
1150
1151    #[test]
1152    fn test_int96_invalid_statistics() {
1153        let mut thrift_stats = PageStatistics {
1154            max: None,
1155            min: Some((0..13).collect()),
1156            null_count: Some(0),
1157            distinct_count: None,
1158            max_value: None,
1159            min_value: None,
1160            is_max_value_exact: None,
1161            is_min_value_exact: None,
1162            nan_count: None,
1163        };
1164
1165        let err = from_thrift_page_stats(Type::INT96, Some(thrift_stats.clone())).unwrap_err();
1166        assert_eq!(
1167            err.to_string(),
1168            "Parquet error: Incorrect Int96 min statistics"
1169        );
1170
1171        thrift_stats.min = None;
1172        thrift_stats.max = Some((0..13).collect());
1173        let err = from_thrift_page_stats(Type::INT96, Some(thrift_stats)).unwrap_err();
1174        assert_eq!(
1175            err.to_string(),
1176            "Parquet error: Incorrect Int96 max statistics"
1177        );
1178    }
1179
1180    // Ensures that we can call ValueStatistics::min_opt from a
1181    // generic function without reyling on a bound to a private trait.
1182    fn generic_statistics_handler<T: std::fmt::Display>(stats: ValueStatistics<T>) -> String {
1183        match stats.min_opt() {
1184            Some(s) => format!("min: {}", s),
1185            None => "min: NA".to_string(),
1186        }
1187    }
1188
1189    #[test]
1190    fn test_generic_access() {
1191        let stats = Statistics::int32(Some(12), Some(45), None, Some(11), false);
1192
1193        match stats {
1194            Statistics::Int32(v) => {
1195                let stats_string = generic_statistics_handler(v);
1196                assert_eq!(&stats_string, "min: 12");
1197            }
1198            _ => unreachable!(),
1199        }
1200    }
1201
1202    #[test]
1203    fn test_nan_count_float() {
1204        // Test NaN count for f32
1205        let stats = Statistics::Float(
1206            ValueStatistics::new(Some(1.0_f32), Some(5.0_f32), None, Some(0), false)
1207                .with_nan_count(Some(3)),
1208        );
1209
1210        assert_eq!(stats.nan_count_opt(), Some(3));
1211
1212        // Verify round-trip through thrift
1213        let thrift_stats = page_stats_to_thrift(Some(&stats)).unwrap();
1214        assert_eq!(thrift_stats.nan_count, Some(3));
1215
1216        let round_tripped = from_thrift_page_stats(Type::FLOAT, Some(thrift_stats))
1217            .unwrap()
1218            .unwrap();
1219        assert_eq!(round_tripped.nan_count_opt(), Some(3));
1220    }
1221
1222    #[test]
1223    fn test_nan_count_double() {
1224        // Test NaN count for f64
1225        let stats = Statistics::Double(
1226            ValueStatistics::new(Some(1.0_f64), Some(5.0_f64), None, Some(0), false)
1227                .with_nan_count(Some(5)),
1228        );
1229
1230        assert_eq!(stats.nan_count_opt(), Some(5));
1231
1232        // Verify round-trip through thrift
1233        let thrift_stats = page_stats_to_thrift(Some(&stats)).unwrap();
1234        assert_eq!(thrift_stats.nan_count, Some(5));
1235
1236        let round_tripped = from_thrift_page_stats(Type::DOUBLE, Some(thrift_stats))
1237            .unwrap()
1238            .unwrap();
1239        assert_eq!(round_tripped.nan_count_opt(), Some(5));
1240    }
1241
1242    #[test]
1243    fn test_nan_count_none_for_non_float() {
1244        // NaN count should not be set for non-floating point types
1245        let stats = Statistics::int32(Some(1), Some(100), None, Some(0), false);
1246        assert_eq!(stats.nan_count_opt(), None);
1247
1248        let thrift_stats = page_stats_to_thrift(Some(&stats)).unwrap();
1249        assert_eq!(thrift_stats.nan_count, None);
1250    }
1251
1252    #[test]
1253    fn test_nan_count_backwards_compatible() {
1254        // Test that missing nan_count field is handled correctly
1255        let thrift_stats = PageStatistics {
1256            min: None,
1257            max: None,
1258            min_value: Some(vec![0, 0, 0, 0]), // 0.0_f32 in bytes
1259            max_value: Some(vec![0, 0, 128, 63]), // 1.0_f32 in bytes
1260            null_count: Some(0),
1261            distinct_count: None,
1262            nan_count: None, // Not set
1263            is_min_value_exact: None,
1264            is_max_value_exact: None,
1265        };
1266
1267        let stats = from_thrift_page_stats(Type::FLOAT, Some(thrift_stats))
1268            .unwrap()
1269            .unwrap();
1270
1271        // nan_count should be None when not provided
1272        assert_eq!(stats.nan_count_opt(), None);
1273    }
1274
1275    #[test]
1276    fn test_statistics_with_nan_min_max() {
1277        // Test that when there are only NaN values, min/max are NaN
1278        let stats = Statistics::Float(
1279            ValueStatistics::new(
1280                Some(f32::NAN), // min and max should have NaN values
1281                Some(f32::NAN),
1282                None,
1283                Some(0),
1284                false,
1285            )
1286            .with_nan_count(Some(10)), // All values are NaN
1287        );
1288
1289        assert_eq!(stats.min_bytes_opt(), Some(f32::NAN.as_bytes()));
1290        assert_eq!(stats.max_bytes_opt(), Some(f32::NAN.as_bytes()));
1291        assert_eq!(stats.nan_count_opt(), Some(10));
1292
1293        // Verify serialization handles this case
1294        let thrift_stats = page_stats_to_thrift(Some(&stats)).unwrap();
1295        assert_eq!(thrift_stats.min_value, Some(f32::NAN.as_bytes().to_vec()));
1296        assert_eq!(thrift_stats.max_value, Some(f32::NAN.as_bytes().to_vec()));
1297        assert_eq!(thrift_stats.nan_count, Some(10));
1298    }
1299
1300    #[test]
1301    fn test_nan_count_too_large() {
1302        // Test that nan_count larger than i64::MAX is not serialized
1303        let stats = Statistics::Float(
1304            ValueStatistics::new(Some(1.0_f32), Some(2.0_f32), None, Some(0), false)
1305                .with_nan_count(Some(u64::MAX)),
1306        );
1307
1308        let thrift_stats = page_stats_to_thrift(Some(&stats)).unwrap();
1309        // u64::MAX can't fit in i64, so it should be None
1310        assert_eq!(thrift_stats.nan_count, None);
1311    }
1312
1313    #[test]
1314    fn test_nan_counts_in_column_index() {
1315        // Test that nan_counts are properly collected in page index
1316        use crate::file::metadata::ColumnIndexBuilder;
1317
1318        // Test for floating-point column - all pages must have Some(n)
1319        let mut float_builder = ColumnIndexBuilder::new(Type::FLOAT);
1320        float_builder.append(false, vec![0u8; 4], vec![255u8; 4], 0, Some(5));
1321        float_builder.append(false, vec![0u8; 4], vec![255u8; 4], 2, Some(3));
1322        float_builder.append(false, vec![0u8; 4], vec![255u8; 4], 0, Some(0)); // No NaN but still Some(0)
1323
1324        let float_column_index = float_builder.build().unwrap();
1325        // Verify nan_counts field is properly set for float column
1326        assert_eq!(float_column_index.nan_counts(), Some(&vec![5, 3, 0]));
1327
1328        // Test for non-floating-point column - all pages must have None
1329        let mut int_builder = ColumnIndexBuilder::new(Type::INT32);
1330        int_builder.append(false, vec![0u8; 4], vec![255u8; 4], 0, None);
1331        int_builder.append(false, vec![0u8; 4], vec![255u8; 4], 2, None);
1332        int_builder.append(false, vec![0u8; 4], vec![255u8; 4], 0, None);
1333
1334        let int_column_index = int_builder.build().unwrap();
1335        // Verify nan_counts field is None for non-float column
1336        assert_eq!(int_column_index.nan_counts(), None);
1337    }
1338}