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