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 core::f32;
757
758    use super::*;
759
760    #[test]
761    fn test_statistics_min_max_bytes() {
762        let stats = Statistics::int32(Some(-123), Some(234), None, Some(1), false);
763        assert_eq!(stats.min_bytes_opt(), Some((-123).as_bytes()));
764        assert_eq!(stats.max_bytes_opt(), Some(234.as_bytes()));
765
766        let stats = Statistics::byte_array(
767            Some(ByteArray::from(vec![1, 2, 3])),
768            Some(ByteArray::from(vec![3, 4, 5])),
769            None,
770            Some(1),
771            true,
772        );
773        assert_eq!(stats.min_bytes_opt().unwrap(), &[1, 2, 3]);
774        assert_eq!(stats.max_bytes_opt().unwrap(), &[3, 4, 5]);
775    }
776
777    #[test]
778    #[should_panic(expected = "General(\"Statistics null count is negative -10\")")]
779    fn test_statistics_negative_null_count() {
780        let thrift_stats = PageStatistics {
781            max: None,
782            min: None,
783            null_count: Some(-10),
784            distinct_count: None,
785            max_value: None,
786            min_value: None,
787            is_max_value_exact: None,
788            is_min_value_exact: None,
789            nan_count: None,
790        };
791
792        from_thrift_page_stats(Type::INT32, Some(thrift_stats)).unwrap();
793    }
794
795    #[test]
796    fn test_statistics_thrift_none() {
797        assert_eq!(from_thrift_page_stats(Type::INT32, None).unwrap(), None);
798        assert_eq!(
799            from_thrift_page_stats(Type::BYTE_ARRAY, None).unwrap(),
800            None
801        );
802    }
803
804    #[test]
805    fn test_statistics_debug() {
806        let stats = Statistics::int32(Some(1), Some(12), None, Some(12), true);
807        assert_eq!(
808            format!("{stats:?}"),
809            "Int32({min: Some(1), max: Some(12), distinct_count: None, null_count: Some(12), \
810             min_max_deprecated: true, min_max_backwards_compatible: true, max_value_exact: true, min_value_exact: true})"
811        );
812
813        let stats = Statistics::int32(None, None, None, Some(7), false);
814        assert_eq!(
815            format!("{stats:?}"),
816            "Int32({min: None, max: None, distinct_count: None, null_count: Some(7), \
817             min_max_deprecated: false, min_max_backwards_compatible: false, max_value_exact: false, min_value_exact: false})"
818        )
819    }
820
821    #[test]
822    fn test_statistics_display() {
823        let stats = Statistics::int32(Some(1), Some(12), None, Some(12), true);
824        assert_eq!(
825            format!("{stats}"),
826            "{min: 1, max: 12, distinct_count: N/A, null_count: 12, min_max_deprecated: true, max_value_exact: true, min_value_exact: true}"
827        );
828
829        let stats = Statistics::int64(None, None, None, Some(7), false);
830        assert_eq!(
831            format!("{stats}"),
832            "{min: N/A, max: N/A, distinct_count: N/A, null_count: 7, min_max_deprecated: \
833             false, max_value_exact: false, min_value_exact: false}"
834        );
835
836        let stats = Statistics::int96(
837            Some(Int96::from(vec![1, 0, 0])),
838            Some(Int96::from(vec![2, 3, 4])),
839            None,
840            Some(3),
841            true,
842        );
843        assert_eq!(
844            format!("{stats}"),
845            "{min: [1, 0, 0], max: [2, 3, 4], distinct_count: N/A, null_count: 3, \
846             min_max_deprecated: true, max_value_exact: true, min_value_exact: true}"
847        );
848
849        let stats = Statistics::ByteArray(
850            ValueStatistics::new(
851                Some(ByteArray::from(vec![1u8])),
852                Some(ByteArray::from(vec![2u8])),
853                Some(5),
854                Some(7),
855                false,
856            )
857            .with_max_is_exact(false)
858            .with_min_is_exact(false),
859        );
860        assert_eq!(
861            format!("{stats}"),
862            "{min: [1], max: [2], distinct_count: 5, null_count: 7, min_max_deprecated: false, max_value_exact: false, min_value_exact: false}"
863        );
864    }
865
866    #[test]
867    fn test_statistics_partial_eq() {
868        let expected = Statistics::int32(Some(12), Some(45), None, Some(11), true);
869
870        assert_eq!(
871            Statistics::int32(Some(12), Some(45), None, Some(11), true),
872            expected
873        );
874        assert_ne!(
875            Statistics::int32(Some(11), Some(45), None, Some(11), true),
876            expected
877        );
878        assert_ne!(
879            Statistics::int32(Some(12), Some(44), None, Some(11), true),
880            expected
881        );
882        assert_ne!(
883            Statistics::int32(Some(12), Some(45), None, Some(23), true),
884            expected
885        );
886        assert_ne!(
887            Statistics::int32(Some(12), Some(45), None, Some(11), false),
888            expected
889        );
890
891        assert_ne!(
892            Statistics::int32(Some(12), Some(45), None, Some(11), false),
893            Statistics::int64(Some(12), Some(45), None, Some(11), false)
894        );
895
896        assert_ne!(
897            Statistics::boolean(Some(false), Some(true), None, None, true),
898            Statistics::double(Some(1.2), Some(4.5), None, None, true)
899        );
900
901        assert_ne!(
902            Statistics::byte_array(
903                Some(ByteArray::from(vec![1, 2, 3])),
904                Some(ByteArray::from(vec![1, 2, 3])),
905                None,
906                None,
907                true
908            ),
909            Statistics::fixed_len_byte_array(
910                Some(ByteArray::from(vec![1, 2, 3]).into()),
911                Some(ByteArray::from(vec![1, 2, 3]).into()),
912                None,
913                None,
914                true,
915            )
916        );
917
918        assert_ne!(
919            Statistics::byte_array(
920                Some(ByteArray::from(vec![1, 2, 3])),
921                Some(ByteArray::from(vec![1, 2, 3])),
922                None,
923                None,
924                true,
925            ),
926            Statistics::ByteArray(
927                ValueStatistics::new(
928                    Some(ByteArray::from(vec![1, 2, 3])),
929                    Some(ByteArray::from(vec![1, 2, 3])),
930                    None,
931                    None,
932                    true,
933                )
934                .with_max_is_exact(false)
935            )
936        );
937
938        assert_ne!(
939            Statistics::fixed_len_byte_array(
940                Some(FixedLenByteArray::from(vec![1, 2, 3])),
941                Some(FixedLenByteArray::from(vec![1, 2, 3])),
942                None,
943                None,
944                true,
945            ),
946            Statistics::FixedLenByteArray(
947                ValueStatistics::new(
948                    Some(FixedLenByteArray::from(vec![1, 2, 3])),
949                    Some(FixedLenByteArray::from(vec![1, 2, 3])),
950                    None,
951                    None,
952                    true,
953                )
954                .with_min_is_exact(false)
955            )
956        );
957    }
958
959    #[test]
960    fn test_statistics_from_thrift() {
961        // Helper method to check statistics conversion.
962        fn check_stats(stats: Statistics) {
963            let type_ = stats.physical_type();
964            let thrift_stats = page_stats_to_thrift(Some(&stats));
965            assert_eq!(
966                from_thrift_page_stats(type_, thrift_stats).unwrap(),
967                Some(stats)
968            );
969        }
970
971        check_stats(Statistics::boolean(
972            Some(false),
973            Some(true),
974            None,
975            Some(7),
976            true,
977        ));
978        check_stats(Statistics::boolean(
979            Some(false),
980            Some(true),
981            None,
982            Some(7),
983            true,
984        ));
985        check_stats(Statistics::boolean(
986            Some(false),
987            Some(true),
988            None,
989            Some(0),
990            false,
991        ));
992        check_stats(Statistics::boolean(
993            Some(true),
994            Some(true),
995            None,
996            Some(7),
997            true,
998        ));
999        check_stats(Statistics::boolean(
1000            Some(false),
1001            Some(false),
1002            None,
1003            Some(7),
1004            true,
1005        ));
1006        check_stats(Statistics::boolean(None, None, None, Some(7), true));
1007
1008        check_stats(Statistics::int32(
1009            Some(-100),
1010            Some(500),
1011            None,
1012            Some(7),
1013            true,
1014        ));
1015        check_stats(Statistics::int32(
1016            Some(-100),
1017            Some(500),
1018            None,
1019            Some(0),
1020            false,
1021        ));
1022        check_stats(Statistics::int32(None, None, None, Some(7), true));
1023
1024        check_stats(Statistics::int64(
1025            Some(-100),
1026            Some(200),
1027            None,
1028            Some(7),
1029            true,
1030        ));
1031        check_stats(Statistics::int64(
1032            Some(-100),
1033            Some(200),
1034            None,
1035            Some(0),
1036            false,
1037        ));
1038        check_stats(Statistics::int64(None, None, None, Some(7), true));
1039
1040        check_stats(Statistics::float(Some(1.2), Some(3.4), None, Some(7), true));
1041        check_stats(Statistics::float(
1042            Some(1.2),
1043            Some(3.4),
1044            None,
1045            Some(0),
1046            false,
1047        ));
1048        check_stats(Statistics::float(None, None, None, Some(7), true));
1049
1050        check_stats(Statistics::double(
1051            Some(1.2),
1052            Some(3.4),
1053            None,
1054            Some(7),
1055            true,
1056        ));
1057        check_stats(Statistics::double(
1058            Some(1.2),
1059            Some(3.4),
1060            None,
1061            Some(0),
1062            false,
1063        ));
1064        check_stats(Statistics::double(None, None, None, Some(7), true));
1065
1066        check_stats(Statistics::byte_array(
1067            Some(ByteArray::from(vec![1, 2, 3])),
1068            Some(ByteArray::from(vec![3, 4, 5])),
1069            None,
1070            Some(7),
1071            true,
1072        ));
1073        check_stats(Statistics::byte_array(None, None, None, Some(7), true));
1074
1075        check_stats(Statistics::fixed_len_byte_array(
1076            Some(ByteArray::from(vec![1, 2, 3]).into()),
1077            Some(ByteArray::from(vec![3, 4, 5]).into()),
1078            None,
1079            Some(7),
1080            true,
1081        ));
1082        check_stats(Statistics::fixed_len_byte_array(
1083            None,
1084            None,
1085            None,
1086            Some(7),
1087            true,
1088        ));
1089    }
1090
1091    #[test]
1092    fn test_count_encoding() {
1093        statistics_count_test(None, None);
1094        statistics_count_test(Some(0), Some(0));
1095        statistics_count_test(Some(100), Some(2000));
1096        statistics_count_test(Some(1), None);
1097        statistics_count_test(None, Some(1));
1098    }
1099
1100    #[test]
1101    fn test_count_encoding_distinct_too_large() {
1102        // statistics are stored using i64, so test trying to store larger values
1103        let statistics = make_bool_stats(Some(u64::MAX), Some(100));
1104        let thrift_stats = page_stats_to_thrift(Some(&statistics)).unwrap();
1105        assert_eq!(thrift_stats.distinct_count, None); // can't store u64 max --> null
1106        assert_eq!(thrift_stats.null_count, Some(100));
1107    }
1108
1109    #[test]
1110    fn test_count_encoding_null_too_large() {
1111        // statistics are stored using i64, so test trying to store larger values
1112        let statistics = make_bool_stats(Some(100), Some(u64::MAX));
1113        let thrift_stats = page_stats_to_thrift(Some(&statistics)).unwrap();
1114        assert_eq!(thrift_stats.distinct_count, Some(100));
1115        assert_eq!(thrift_stats.null_count, None); // can' store u64 max --> null
1116    }
1117
1118    #[test]
1119    fn test_count_decoding_null_invalid() {
1120        let tstatistics = PageStatistics {
1121            null_count: Some(-42),
1122            max: None,
1123            min: None,
1124            distinct_count: None,
1125            max_value: None,
1126            min_value: None,
1127            is_max_value_exact: None,
1128            is_min_value_exact: None,
1129            nan_count: None,
1130        };
1131        let err = from_thrift_page_stats(Type::BOOLEAN, Some(tstatistics)).unwrap_err();
1132        assert_eq!(
1133            err.to_string(),
1134            "Parquet error: Statistics null count is negative -42"
1135        );
1136    }
1137
1138    /// Writes statistics to thrift and reads them back and ensures:
1139    /// - The statistics are the same
1140    /// - The statistics written to thrift are the same as the original statistics
1141    fn statistics_count_test(distinct_count: Option<u64>, null_count: Option<u64>) {
1142        let statistics = make_bool_stats(distinct_count, null_count);
1143
1144        let thrift_stats = page_stats_to_thrift(Some(&statistics)).unwrap();
1145        assert_eq!(thrift_stats.null_count.map(|c| c as u64), null_count);
1146        assert_eq!(
1147            thrift_stats.distinct_count.map(|c| c as u64),
1148            distinct_count
1149        );
1150
1151        let round_tripped = from_thrift_page_stats(Type::BOOLEAN, Some(thrift_stats))
1152            .unwrap()
1153            .unwrap();
1154        assert_eq!(round_tripped, statistics);
1155    }
1156
1157    fn make_bool_stats(distinct_count: Option<u64>, null_count: Option<u64>) -> Statistics {
1158        let min = Some(true);
1159        let max = Some(false);
1160        let is_min_max_deprecated = false;
1161
1162        // test is about the counts, so we aren't really testing the min/max values
1163        Statistics::Boolean(ValueStatistics::new(
1164            min,
1165            max,
1166            distinct_count,
1167            null_count,
1168            is_min_max_deprecated,
1169        ))
1170    }
1171
1172    #[test]
1173    fn test_int96_invalid_statistics() {
1174        let mut thrift_stats = PageStatistics {
1175            max: None,
1176            min: Some((0..13).collect()),
1177            null_count: Some(0),
1178            distinct_count: None,
1179            max_value: None,
1180            min_value: None,
1181            is_max_value_exact: None,
1182            is_min_value_exact: None,
1183            nan_count: None,
1184        };
1185
1186        let err = from_thrift_page_stats(Type::INT96, Some(thrift_stats.clone())).unwrap_err();
1187        assert_eq!(
1188            err.to_string(),
1189            "Parquet error: Incorrect Int96 min statistics"
1190        );
1191
1192        thrift_stats.min = None;
1193        thrift_stats.max = Some((0..13).collect());
1194        let err = from_thrift_page_stats(Type::INT96, Some(thrift_stats)).unwrap_err();
1195        assert_eq!(
1196            err.to_string(),
1197            "Parquet error: Incorrect Int96 max statistics"
1198        );
1199    }
1200
1201    // Ensures that we can call ValueStatistics::min_opt from a
1202    // generic function without reyling on a bound to a private trait.
1203    fn generic_statistics_handler<T: std::fmt::Display>(stats: ValueStatistics<T>) -> String {
1204        match stats.min_opt() {
1205            Some(s) => format!("min: {s}"),
1206            None => "min: NA".to_string(),
1207        }
1208    }
1209
1210    #[test]
1211    fn test_generic_access() {
1212        let stats = Statistics::int32(Some(12), Some(45), None, Some(11), false);
1213
1214        match stats {
1215            Statistics::Int32(v) => {
1216                let stats_string = generic_statistics_handler(v);
1217                assert_eq!(&stats_string, "min: 12");
1218            }
1219            _ => unreachable!(),
1220        }
1221    }
1222
1223    #[test]
1224    fn test_nan_count_float() {
1225        // Test NaN count for f32
1226        let stats = Statistics::Float(
1227            ValueStatistics::new(Some(1.0_f32), Some(5.0_f32), None, Some(0), false)
1228                .with_nan_count(Some(3)),
1229        );
1230
1231        assert_eq!(stats.nan_count_opt(), Some(3));
1232
1233        // Verify round-trip through thrift
1234        let thrift_stats = page_stats_to_thrift(Some(&stats)).unwrap();
1235        assert_eq!(thrift_stats.nan_count, Some(3));
1236
1237        let round_tripped = from_thrift_page_stats(Type::FLOAT, Some(thrift_stats))
1238            .unwrap()
1239            .unwrap();
1240        assert_eq!(round_tripped.nan_count_opt(), Some(3));
1241    }
1242
1243    #[test]
1244    fn test_nan_count_double() {
1245        // Test NaN count for f64
1246        let stats = Statistics::Double(
1247            ValueStatistics::new(Some(1.0_f64), Some(5.0_f64), None, Some(0), false)
1248                .with_nan_count(Some(5)),
1249        );
1250
1251        assert_eq!(stats.nan_count_opt(), Some(5));
1252
1253        // Verify round-trip through thrift
1254        let thrift_stats = page_stats_to_thrift(Some(&stats)).unwrap();
1255        assert_eq!(thrift_stats.nan_count, Some(5));
1256
1257        let round_tripped = from_thrift_page_stats(Type::DOUBLE, Some(thrift_stats))
1258            .unwrap()
1259            .unwrap();
1260        assert_eq!(round_tripped.nan_count_opt(), Some(5));
1261    }
1262
1263    #[test]
1264    fn test_nan_count_none_for_non_float() {
1265        // NaN count should not be set for non-floating point types
1266        let stats = Statistics::int32(Some(1), Some(100), None, Some(0), false);
1267        assert_eq!(stats.nan_count_opt(), None);
1268
1269        let thrift_stats = page_stats_to_thrift(Some(&stats)).unwrap();
1270        assert_eq!(thrift_stats.nan_count, None);
1271    }
1272
1273    #[test]
1274    fn test_nan_count_backwards_compatible() {
1275        // Test that missing nan_count field is handled correctly
1276        let thrift_stats = PageStatistics {
1277            min: None,
1278            max: None,
1279            min_value: Some(vec![0, 0, 0, 0]), // 0.0_f32 in bytes
1280            max_value: Some(vec![0, 0, 128, 63]), // 1.0_f32 in bytes
1281            null_count: Some(0),
1282            distinct_count: None,
1283            nan_count: None, // Not set
1284            is_min_value_exact: None,
1285            is_max_value_exact: None,
1286        };
1287
1288        let stats = from_thrift_page_stats(Type::FLOAT, Some(thrift_stats))
1289            .unwrap()
1290            .unwrap();
1291
1292        // nan_count should be None when not provided
1293        assert_eq!(stats.nan_count_opt(), None);
1294    }
1295
1296    #[test]
1297    fn test_statistics_with_nan_min_max() {
1298        // Test that when there are only NaN values, min/max are NaN
1299        let stats = Statistics::Float(
1300            ValueStatistics::new(
1301                Some(f32::NAN), // min and max should have NaN values
1302                Some(f32::NAN),
1303                None,
1304                Some(0),
1305                false,
1306            )
1307            .with_nan_count(Some(10)), // All values are NaN
1308        );
1309
1310        assert_eq!(stats.min_bytes_opt(), Some(f32::NAN.as_bytes()));
1311        assert_eq!(stats.max_bytes_opt(), Some(f32::NAN.as_bytes()));
1312        assert_eq!(stats.nan_count_opt(), Some(10));
1313
1314        // Verify serialization handles this case
1315        let thrift_stats = page_stats_to_thrift(Some(&stats)).unwrap();
1316        assert_eq!(thrift_stats.min_value, Some(f32::NAN.as_bytes().to_vec()));
1317        assert_eq!(thrift_stats.max_value, Some(f32::NAN.as_bytes().to_vec()));
1318        assert_eq!(thrift_stats.nan_count, Some(10));
1319    }
1320
1321    #[test]
1322    fn test_nan_count_too_large() {
1323        // Test that nan_count larger than i64::MAX is not serialized
1324        let stats = Statistics::Float(
1325            ValueStatistics::new(Some(1.0_f32), Some(2.0_f32), None, Some(0), false)
1326                .with_nan_count(Some(u64::MAX)),
1327        );
1328
1329        let thrift_stats = page_stats_to_thrift(Some(&stats)).unwrap();
1330        // u64::MAX can't fit in i64, so it should be None
1331        assert_eq!(thrift_stats.nan_count, None);
1332    }
1333
1334    #[test]
1335    fn test_nan_counts_in_column_index() {
1336        // Test that nan_counts are properly collected in page index
1337        use crate::file::metadata::ColumnIndexBuilder;
1338
1339        // Test for floating-point column - all pages must have Some(n)
1340        let mut float_builder = ColumnIndexBuilder::new(Type::FLOAT);
1341        float_builder.append(false, vec![0u8; 4], vec![255u8; 4], 0, Some(5));
1342        float_builder.append(false, vec![0u8; 4], vec![255u8; 4], 2, Some(3));
1343        float_builder.append(false, vec![0u8; 4], vec![255u8; 4], 0, Some(0)); // No NaN but still Some(0)
1344
1345        let float_column_index = float_builder.build().unwrap();
1346        // Verify nan_counts field is properly set for float column
1347        assert_eq!(float_column_index.nan_counts(), Some(&vec![5, 3, 0]));
1348
1349        // Test for non-floating-point column - all pages must have None
1350        let mut int_builder = ColumnIndexBuilder::new(Type::INT32);
1351        int_builder.append(false, vec![0u8; 4], vec![255u8; 4], 0, None);
1352        int_builder.append(false, vec![0u8; 4], vec![255u8; 4], 2, None);
1353        int_builder.append(false, vec![0u8; 4], vec![255u8; 4], 0, None);
1354
1355        let int_column_index = int_builder.build().unwrap();
1356        // Verify nan_counts field is None for non-float column
1357        assert_eq!(int_column_index.nan_counts(), None);
1358    }
1359}