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