1use std::fmt;
30
31use crate::basic::Type;
32use crate::data_type::private::ParquetValueType;
33use crate::data_type::*;
34use crate::errors::{ParquetError, Result};
35use crate::file::metadata::thrift::PageStatistics;
36use crate::util::bit_util::FromBytes;
37
38pub(crate) mod private {
39 use super::*;
40
41 pub trait MakeStatistics {
42 fn make_statistics(statistics: ValueStatistics<Self>) -> Statistics
43 where
44 Self: Sized;
45 }
46
47 macro_rules! gen_make_statistics {
48 ($value_ty:ty, $stat:ident) => {
49 impl MakeStatistics for $value_ty {
50 fn make_statistics(statistics: ValueStatistics<Self>) -> Statistics
51 where
52 Self: Sized,
53 {
54 Statistics::$stat(statistics)
55 }
56 }
57 };
58 }
59
60 gen_make_statistics!(bool, Boolean);
61 gen_make_statistics!(i32, Int32);
62 gen_make_statistics!(i64, Int64);
63 gen_make_statistics!(Int96, Int96);
64 gen_make_statistics!(f32, Float);
65 gen_make_statistics!(f64, Double);
66 gen_make_statistics!(ByteArray, ByteArray);
67 gen_make_statistics!(FixedLenByteArray, FixedLenByteArray);
68}
69
70macro_rules! statistics_new_func {
72 ($func:ident, $vtype:ty, $stat:ident) => {
73 #[doc = concat!("Creates new statistics for `", stringify!($stat), "` column type.")]
74 pub fn $func(
75 min: $vtype,
76 max: $vtype,
77 distinct: Option<u64>,
78 nulls: Option<u64>,
79 is_deprecated: bool,
80 ) -> Self {
81 Statistics::$stat(ValueStatistics::new(
82 min,
83 max,
84 distinct,
85 nulls,
86 is_deprecated,
87 ))
88 }
89 };
90}
91
92macro_rules! statistics_enum_func {
94 ($self:ident, $func:ident) => {{
95 match *$self {
96 Statistics::Boolean(ref typed) => typed.$func(),
97 Statistics::Int32(ref typed) => typed.$func(),
98 Statistics::Int64(ref typed) => typed.$func(),
99 Statistics::Int96(ref typed) => typed.$func(),
100 Statistics::Float(ref typed) => typed.$func(),
101 Statistics::Double(ref typed) => typed.$func(),
102 Statistics::ByteArray(ref typed) => typed.$func(),
103 Statistics::FixedLenByteArray(ref typed) => typed.$func(),
104 }
105 }};
106}
107
108pub(crate) fn from_thrift_page_stats(
110 physical_type: Type,
111 thrift_stats: Option<PageStatistics>,
112) -> Result<Option<Statistics>> {
113 Ok(match thrift_stats {
114 Some(stats) => {
115 let null_count = stats
117 .null_count
118 .map(|null_count| {
119 if null_count < 0 {
120 return Err(ParquetError::General(format!(
121 "Statistics null count is negative {null_count}",
122 )));
123 }
124 Ok(null_count as u64)
125 })
126 .transpose()?;
127 let distinct_count = stats.distinct_count.map(|value| value as u64);
129 let nan_count = stats
131 .nan_count
132 .map(|nan_count| {
133 if nan_count < 0 {
134 return Err(ParquetError::General(format!(
135 "Statistics NaN count is negative {nan_count}",
136 )));
137 }
138 Ok(nan_count as u64)
139 })
140 .transpose()?;
141 let old_format = stats.min_value.is_none() && stats.max_value.is_none();
143 let min = if old_format {
145 stats.min
146 } else {
147 stats.min_value
148 };
149 let max = if old_format {
151 stats.max
152 } else {
153 stats.max_value
154 };
155
156 fn check_len(min: Option<&[u8]>, max: Option<&[u8]>, len: usize) -> Result<()> {
157 if let Some(min) = min
158 && min.len() < len
159 {
160 return Err(ParquetError::General(
161 "Insufficient bytes to parse min statistic".to_string(),
162 ));
163 }
164 if let Some(max) = max
165 && max.len() < len
166 {
167 return Err(ParquetError::General(
168 "Insufficient bytes to parse max statistic".to_string(),
169 ));
170 }
171 Ok(())
172 }
173
174 {
175 let (min, max) = (min.as_deref(), max.as_deref());
176 match physical_type {
177 Type::BOOLEAN => check_len(min, max, 1),
178 Type::INT32 | Type::FLOAT => check_len(min, max, 4),
179 Type::INT64 | Type::DOUBLE => check_len(min, max, 8),
180 Type::INT96 => check_len(min, max, 12),
181 _ => Ok(()),
182 }?;
183 }
184
185 let res = match physical_type {
190 Type::BOOLEAN => Statistics::boolean(
191 min.map(|data| data[0] != 0),
192 max.map(|data| data[0] != 0),
193 distinct_count,
194 null_count,
195 old_format,
196 ),
197 Type::INT32 => Statistics::int32(
198 min.map(|data| i32::from_le_bytes(data[..4].try_into().unwrap())),
199 max.map(|data| i32::from_le_bytes(data[..4].try_into().unwrap())),
200 distinct_count,
201 null_count,
202 old_format,
203 ),
204 Type::INT64 => Statistics::int64(
205 min.map(|data| i64::from_le_bytes(data[..8].try_into().unwrap())),
206 max.map(|data| i64::from_le_bytes(data[..8].try_into().unwrap())),
207 distinct_count,
208 null_count,
209 old_format,
210 ),
211 Type::INT96 => {
212 let min = if let Some(data) = min {
214 if data.len() != 12 {
215 return Err(ParquetError::General(
216 "Incorrect Int96 min statistics".to_string(),
217 ));
218 }
219 Some(Int96::try_from_le_slice(&data)?)
220 } else {
221 None
222 };
223 let max = if let Some(data) = max {
224 if data.len() != 12 {
225 return Err(ParquetError::General(
226 "Incorrect Int96 max statistics".to_string(),
227 ));
228 }
229 Some(Int96::try_from_le_slice(&data)?)
230 } else {
231 None
232 };
233 Statistics::int96(min, max, distinct_count, null_count, old_format)
234 }
235 Type::FLOAT => Statistics::Float(
236 ValueStatistics::new(
237 min.map(|data| f32::from_le_bytes(data[..4].try_into().unwrap())),
238 max.map(|data| f32::from_le_bytes(data[..4].try_into().unwrap())),
239 distinct_count,
240 null_count,
241 old_format,
242 )
243 .with_nan_count(nan_count)
244 .with_max_is_exact(stats.is_max_value_exact.unwrap_or(false))
245 .with_min_is_exact(stats.is_min_value_exact.unwrap_or(false)),
246 ),
247 Type::DOUBLE => Statistics::Double(
248 ValueStatistics::new(
249 min.map(|data| f64::from_le_bytes(data[..8].try_into().unwrap())),
250 max.map(|data| f64::from_le_bytes(data[..8].try_into().unwrap())),
251 distinct_count,
252 null_count,
253 old_format,
254 )
255 .with_nan_count(nan_count)
256 .with_max_is_exact(stats.is_max_value_exact.unwrap_or(false))
257 .with_min_is_exact(stats.is_min_value_exact.unwrap_or(false)),
258 ),
259 Type::BYTE_ARRAY => Statistics::ByteArray(
260 ValueStatistics::new(
261 min.map(ByteArray::from),
262 max.map(ByteArray::from),
263 distinct_count,
264 null_count,
265 old_format,
266 )
267 .with_max_is_exact(stats.is_max_value_exact.unwrap_or(false))
268 .with_min_is_exact(stats.is_min_value_exact.unwrap_or(false)),
269 ),
270 Type::FIXED_LEN_BYTE_ARRAY => Statistics::FixedLenByteArray(
271 ValueStatistics::new(
272 min.map(ByteArray::from).map(FixedLenByteArray::from),
273 max.map(ByteArray::from).map(FixedLenByteArray::from),
274 distinct_count,
275 null_count,
276 old_format,
277 )
278 .with_nan_count(nan_count)
284 .with_max_is_exact(stats.is_max_value_exact.unwrap_or(false))
285 .with_min_is_exact(stats.is_min_value_exact.unwrap_or(false)),
286 ),
287 };
288
289 Some(res)
290 }
291 None => None,
292 })
293}
294
295pub(crate) fn page_stats_to_thrift(stats: Option<&Statistics>) -> Option<PageStatistics> {
297 let stats = stats?;
298
299 let null_count = stats
301 .null_count_opt()
302 .and_then(|value| i64::try_from(value).ok());
303
304 let distinct_count = stats
306 .distinct_count_opt()
307 .and_then(|value| i64::try_from(value).ok());
308
309 let nan_count = stats
311 .nan_count_opt()
312 .and_then(|value| i64::try_from(value).ok());
313
314 let mut thrift_stats = PageStatistics {
315 max: None,
316 min: None,
317 null_count,
318 distinct_count,
319 max_value: None,
320 min_value: None,
321 is_max_value_exact: None,
322 is_min_value_exact: None,
323 nan_count,
324 };
325
326 let (min, max, min_exact, max_exact) = (
328 stats.min_bytes_opt().map(|x| x.to_vec()),
329 stats.max_bytes_opt().map(|x| x.to_vec()),
330 Some(stats.min_is_exact()),
331 Some(stats.max_is_exact()),
332 );
333 if stats.is_min_max_backwards_compatible() {
334 thrift_stats.min.clone_from(&min);
336 thrift_stats.max.clone_from(&max);
337 }
338
339 if !stats.is_min_max_deprecated() {
340 thrift_stats.min_value = min;
341 thrift_stats.max_value = max;
342 }
343
344 thrift_stats.is_min_value_exact = min_exact;
345 thrift_stats.is_max_value_exact = max_exact;
346
347 Some(thrift_stats)
348}
349
350#[derive(Debug, Clone, PartialEq)]
361pub enum Statistics {
362 Boolean(ValueStatistics<bool>),
364 Int32(ValueStatistics<i32>),
366 Int64(ValueStatistics<i64>),
368 Int96(ValueStatistics<Int96>),
370 Float(ValueStatistics<f32>),
372 Double(ValueStatistics<f64>),
374 ByteArray(ValueStatistics<ByteArray>),
376 FixedLenByteArray(ValueStatistics<FixedLenByteArray>),
378}
379
380impl<T: ParquetValueType> From<ValueStatistics<T>> for Statistics {
381 fn from(t: ValueStatistics<T>) -> Self {
382 T::make_statistics(t)
383 }
384}
385
386impl Statistics {
387 pub fn new<T: ParquetValueType>(
389 min: Option<T>,
390 max: Option<T>,
391 distinct_count: Option<u64>,
392 null_count: Option<u64>,
393 is_deprecated: bool,
394 ) -> Self {
395 Self::from(ValueStatistics::new(
396 min,
397 max,
398 distinct_count,
399 null_count,
400 is_deprecated,
401 ))
402 }
403
404 statistics_new_func![boolean, Option<bool>, Boolean];
405
406 statistics_new_func![int32, Option<i32>, Int32];
407
408 statistics_new_func![int64, Option<i64>, Int64];
409
410 statistics_new_func![int96, Option<Int96>, Int96];
411
412 statistics_new_func![float, Option<f32>, Float];
413
414 statistics_new_func![double, Option<f64>, Double];
415
416 statistics_new_func![byte_array, Option<ByteArray>, ByteArray];
417
418 statistics_new_func![
419 fixed_len_byte_array,
420 Option<FixedLenByteArray>,
421 FixedLenByteArray
422 ];
423
424 pub fn is_min_max_deprecated(&self) -> bool {
431 statistics_enum_func![self, is_min_max_deprecated]
432 }
433
434 pub fn is_min_max_backwards_compatible(&self) -> bool {
445 statistics_enum_func![self, is_min_max_backwards_compatible]
446 }
447
448 pub fn distinct_count_opt(&self) -> Option<u64> {
451 statistics_enum_func![self, distinct_count]
452 }
453
454 pub fn null_count_opt(&self) -> Option<u64> {
472 statistics_enum_func![self, null_count_opt]
473 }
474
475 pub fn nan_count_opt(&self) -> Option<u64> {
477 statistics_enum_func![self, nan_count_opt]
478 }
479
480 pub fn min_is_exact(&self) -> bool {
482 statistics_enum_func![self, min_is_exact]
483 }
484
485 pub fn max_is_exact(&self) -> bool {
487 statistics_enum_func![self, max_is_exact]
488 }
489
490 pub fn min_bytes_opt(&self) -> Option<&[u8]> {
492 statistics_enum_func![self, min_bytes_opt]
493 }
494
495 pub fn max_bytes_opt(&self) -> Option<&[u8]> {
497 statistics_enum_func![self, max_bytes_opt]
498 }
499
500 pub fn physical_type(&self) -> Type {
502 match self {
503 Statistics::Boolean(_) => Type::BOOLEAN,
504 Statistics::Int32(_) => Type::INT32,
505 Statistics::Int64(_) => Type::INT64,
506 Statistics::Int96(_) => Type::INT96,
507 Statistics::Float(_) => Type::FLOAT,
508 Statistics::Double(_) => Type::DOUBLE,
509 Statistics::ByteArray(_) => Type::BYTE_ARRAY,
510 Statistics::FixedLenByteArray(_) => Type::FIXED_LEN_BYTE_ARRAY,
511 }
512 }
513}
514
515impl fmt::Display for Statistics {
516 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
517 match self {
518 Statistics::Boolean(typed) => write!(f, "{typed}"),
519 Statistics::Int32(typed) => write!(f, "{typed}"),
520 Statistics::Int64(typed) => write!(f, "{typed}"),
521 Statistics::Int96(typed) => write!(f, "{typed}"),
522 Statistics::Float(typed) => write!(f, "{typed}"),
523 Statistics::Double(typed) => write!(f, "{typed}"),
524 Statistics::ByteArray(typed) => write!(f, "{typed}"),
525 Statistics::FixedLenByteArray(typed) => write!(f, "{typed}"),
526 }
527 }
528}
529
530pub type TypedStatistics<T> = ValueStatistics<<T as DataType>::T>;
532
533#[derive(Clone, Eq, PartialEq)]
537pub struct ValueStatistics<T> {
538 min: Option<T>,
539 max: Option<T>,
540 distinct_count: Option<u64>,
542 null_count: Option<u64>,
543 nan_count: Option<u64>,
545
546 is_max_value_exact: bool,
548 is_min_value_exact: bool,
549
550 is_min_max_deprecated: bool,
553
554 is_min_max_backwards_compatible: bool,
557}
558
559impl<T> ValueStatistics<T> {
560 pub fn new(
562 min: Option<T>,
563 max: Option<T>,
564 distinct_count: Option<u64>,
565 null_count: Option<u64>,
566 is_min_max_deprecated: bool,
567 ) -> Self {
568 Self {
569 is_max_value_exact: max.is_some(),
570 is_min_value_exact: min.is_some(),
571 min,
572 max,
573 distinct_count,
574 null_count,
575 nan_count: None,
576 is_min_max_deprecated,
577 is_min_max_backwards_compatible: is_min_max_deprecated,
578 }
579 }
580
581 pub(crate) fn with_min_max(self, min: Option<T>, max: Option<T>) -> Self {
583 Self { min, max, ..self }
584 }
585
586 pub fn with_min_is_exact(self, is_min_value_exact: bool) -> Self {
591 Self {
592 is_min_value_exact,
593 ..self
594 }
595 }
596
597 pub fn with_max_is_exact(self, is_max_value_exact: bool) -> Self {
602 Self {
603 is_max_value_exact,
604 ..self
605 }
606 }
607
608 pub fn with_backwards_compatible_min_max(self, backwards_compatible: bool) -> Self {
614 Self {
615 is_min_max_backwards_compatible: backwards_compatible,
616 ..self
617 }
618 }
619
620 pub fn nan_count_opt(&self) -> Option<u64> {
622 self.nan_count
623 }
624
625 pub fn with_nan_count(self, nan_count: Option<u64>) -> Self {
627 Self { nan_count, ..self }
628 }
629
630 pub fn min_opt(&self) -> Option<&T> {
632 self.min.as_ref()
633 }
634
635 pub fn max_opt(&self) -> Option<&T> {
637 self.max.as_ref()
638 }
639
640 pub(crate) fn _internal_has_min_max_set(&self) -> bool {
643 self.min.is_some() && self.max.is_some()
644 }
645
646 pub fn max_is_exact(&self) -> bool {
648 self.max.is_some() && self.is_max_value_exact
649 }
650
651 pub fn min_is_exact(&self) -> bool {
653 self.min.is_some() && self.is_min_value_exact
654 }
655
656 pub fn distinct_count(&self) -> Option<u64> {
658 self.distinct_count
659 }
660
661 pub fn null_count_opt(&self) -> Option<u64> {
663 self.null_count
664 }
665
666 fn is_min_max_deprecated(&self) -> bool {
668 self.is_min_max_deprecated
669 }
670
671 pub fn is_min_max_backwards_compatible(&self) -> bool {
682 self.is_min_max_backwards_compatible
683 }
684}
685
686impl<T: AsBytes> ValueStatistics<T> {
687 pub fn min_bytes_opt(&self) -> Option<&[u8]> {
689 self.min_opt().map(AsBytes::as_bytes)
690 }
691
692 pub fn max_bytes_opt(&self) -> Option<&[u8]> {
694 self.max_opt().map(AsBytes::as_bytes)
695 }
696}
697
698impl<T: ParquetValueType> fmt::Display for ValueStatistics<T> {
699 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
700 write!(f, "{{")?;
701 write!(f, "min: ")?;
702 match self.min {
703 Some(ref value) => write!(f, "{value}")?,
704 None => write!(f, "N/A")?,
705 }
706 write!(f, ", max: ")?;
707 match self.max {
708 Some(ref value) => write!(f, "{value}")?,
709 None => write!(f, "N/A")?,
710 }
711 write!(f, ", distinct_count: ")?;
712 match self.distinct_count {
713 Some(value) => write!(f, "{value}")?,
714 None => write!(f, "N/A")?,
715 }
716 write!(f, ", null_count: ")?;
717 match self.null_count {
718 Some(value) => write!(f, "{value}")?,
719 None => write!(f, "N/A")?,
720 }
721 write!(f, ", min_max_deprecated: {}", self.is_min_max_deprecated)?;
722 write!(f, ", max_value_exact: {}", self.is_max_value_exact)?;
723 write!(f, ", min_value_exact: {}", self.is_min_value_exact)?;
724 write!(f, "}}")
725 }
726}
727
728impl<T: ParquetValueType> fmt::Debug for ValueStatistics<T> {
729 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
730 write!(
731 f,
732 "{{min: {:?}, max: {:?}, distinct_count: {:?}, null_count: {:?}, \
733 min_max_deprecated: {}, min_max_backwards_compatible: {}, max_value_exact: {}, min_value_exact: {}}}",
734 self.min,
735 self.max,
736 self.distinct_count,
737 self.null_count,
738 self.is_min_max_deprecated,
739 self.is_min_max_backwards_compatible,
740 self.is_max_value_exact,
741 self.is_min_value_exact
742 )
743 }
744}
745
746#[cfg(test)]
747mod tests {
748 use super::*;
749
750 #[test]
751 fn test_statistics_min_max_bytes() {
752 let stats = Statistics::int32(Some(-123), Some(234), None, Some(1), false);
753 assert_eq!(stats.min_bytes_opt(), Some((-123).as_bytes()));
754 assert_eq!(stats.max_bytes_opt(), Some(234.as_bytes()));
755
756 let stats = Statistics::byte_array(
757 Some(ByteArray::from(vec![1, 2, 3])),
758 Some(ByteArray::from(vec![3, 4, 5])),
759 None,
760 Some(1),
761 true,
762 );
763 assert_eq!(stats.min_bytes_opt().unwrap(), &[1, 2, 3]);
764 assert_eq!(stats.max_bytes_opt().unwrap(), &[3, 4, 5]);
765 }
766
767 #[test]
768 #[should_panic(expected = "General(\"Statistics null count is negative -10\")")]
769 fn test_statistics_negative_null_count() {
770 let thrift_stats = PageStatistics {
771 max: None,
772 min: None,
773 null_count: Some(-10),
774 distinct_count: None,
775 max_value: None,
776 min_value: None,
777 is_max_value_exact: None,
778 is_min_value_exact: None,
779 nan_count: None,
780 };
781
782 from_thrift_page_stats(Type::INT32, Some(thrift_stats)).unwrap();
783 }
784
785 #[test]
786 fn test_statistics_thrift_none() {
787 assert_eq!(from_thrift_page_stats(Type::INT32, None).unwrap(), None);
788 assert_eq!(
789 from_thrift_page_stats(Type::BYTE_ARRAY, None).unwrap(),
790 None
791 );
792 }
793
794 #[test]
795 fn test_statistics_debug() {
796 let stats = Statistics::int32(Some(1), Some(12), None, Some(12), true);
797 assert_eq!(
798 format!("{stats:?}"),
799 "Int32({min: Some(1), max: Some(12), distinct_count: None, null_count: Some(12), \
800 min_max_deprecated: true, min_max_backwards_compatible: true, max_value_exact: true, min_value_exact: true})"
801 );
802
803 let stats = Statistics::int32(None, None, None, Some(7), false);
804 assert_eq!(
805 format!("{stats:?}"),
806 "Int32({min: None, max: None, distinct_count: None, null_count: Some(7), \
807 min_max_deprecated: false, min_max_backwards_compatible: false, max_value_exact: false, min_value_exact: false})"
808 )
809 }
810
811 #[test]
812 fn test_statistics_display() {
813 let stats = Statistics::int32(Some(1), Some(12), None, Some(12), true);
814 assert_eq!(
815 format!("{stats}"),
816 "{min: 1, max: 12, distinct_count: N/A, null_count: 12, min_max_deprecated: true, max_value_exact: true, min_value_exact: true}"
817 );
818
819 let stats = Statistics::int64(None, None, None, Some(7), false);
820 assert_eq!(
821 format!("{stats}"),
822 "{min: N/A, max: N/A, distinct_count: N/A, null_count: 7, min_max_deprecated: \
823 false, max_value_exact: false, min_value_exact: false}"
824 );
825
826 let stats = Statistics::int96(
827 Some(Int96::from(vec![1, 0, 0])),
828 Some(Int96::from(vec![2, 3, 4])),
829 None,
830 Some(3),
831 true,
832 );
833 assert_eq!(
834 format!("{stats}"),
835 "{min: [1, 0, 0], max: [2, 3, 4], distinct_count: N/A, null_count: 3, \
836 min_max_deprecated: true, max_value_exact: true, min_value_exact: true}"
837 );
838
839 let stats = Statistics::ByteArray(
840 ValueStatistics::new(
841 Some(ByteArray::from(vec![1u8])),
842 Some(ByteArray::from(vec![2u8])),
843 Some(5),
844 Some(7),
845 false,
846 )
847 .with_max_is_exact(false)
848 .with_min_is_exact(false),
849 );
850 assert_eq!(
851 format!("{stats}"),
852 "{min: [1], max: [2], distinct_count: 5, null_count: 7, min_max_deprecated: false, max_value_exact: false, min_value_exact: false}"
853 );
854 }
855
856 #[test]
857 fn test_statistics_partial_eq() {
858 let expected = Statistics::int32(Some(12), Some(45), None, Some(11), true);
859
860 assert_eq!(
861 Statistics::int32(Some(12), Some(45), None, Some(11), true),
862 expected
863 );
864 assert_ne!(
865 Statistics::int32(Some(11), Some(45), None, Some(11), true),
866 expected
867 );
868 assert_ne!(
869 Statistics::int32(Some(12), Some(44), None, Some(11), true),
870 expected
871 );
872 assert_ne!(
873 Statistics::int32(Some(12), Some(45), None, Some(23), true),
874 expected
875 );
876 assert_ne!(
877 Statistics::int32(Some(12), Some(45), None, Some(11), false),
878 expected
879 );
880
881 assert_ne!(
882 Statistics::int32(Some(12), Some(45), None, Some(11), false),
883 Statistics::int64(Some(12), Some(45), None, Some(11), false)
884 );
885
886 assert_ne!(
887 Statistics::boolean(Some(false), Some(true), None, None, true),
888 Statistics::double(Some(1.2), Some(4.5), None, None, true)
889 );
890
891 assert_ne!(
892 Statistics::byte_array(
893 Some(ByteArray::from(vec![1, 2, 3])),
894 Some(ByteArray::from(vec![1, 2, 3])),
895 None,
896 None,
897 true
898 ),
899 Statistics::fixed_len_byte_array(
900 Some(ByteArray::from(vec![1, 2, 3]).into()),
901 Some(ByteArray::from(vec![1, 2, 3]).into()),
902 None,
903 None,
904 true,
905 )
906 );
907
908 assert_ne!(
909 Statistics::byte_array(
910 Some(ByteArray::from(vec![1, 2, 3])),
911 Some(ByteArray::from(vec![1, 2, 3])),
912 None,
913 None,
914 true,
915 ),
916 Statistics::ByteArray(
917 ValueStatistics::new(
918 Some(ByteArray::from(vec![1, 2, 3])),
919 Some(ByteArray::from(vec![1, 2, 3])),
920 None,
921 None,
922 true,
923 )
924 .with_max_is_exact(false)
925 )
926 );
927
928 assert_ne!(
929 Statistics::fixed_len_byte_array(
930 Some(FixedLenByteArray::from(vec![1, 2, 3])),
931 Some(FixedLenByteArray::from(vec![1, 2, 3])),
932 None,
933 None,
934 true,
935 ),
936 Statistics::FixedLenByteArray(
937 ValueStatistics::new(
938 Some(FixedLenByteArray::from(vec![1, 2, 3])),
939 Some(FixedLenByteArray::from(vec![1, 2, 3])),
940 None,
941 None,
942 true,
943 )
944 .with_min_is_exact(false)
945 )
946 );
947 }
948
949 #[test]
950 fn test_statistics_from_thrift() {
951 fn check_stats(stats: Statistics) {
953 let type_ = stats.physical_type();
954 let thrift_stats = page_stats_to_thrift(Some(&stats));
955 assert_eq!(
956 from_thrift_page_stats(type_, thrift_stats).unwrap(),
957 Some(stats)
958 );
959 }
960
961 check_stats(Statistics::boolean(
962 Some(false),
963 Some(true),
964 None,
965 Some(7),
966 true,
967 ));
968 check_stats(Statistics::boolean(
969 Some(false),
970 Some(true),
971 None,
972 Some(7),
973 true,
974 ));
975 check_stats(Statistics::boolean(
976 Some(false),
977 Some(true),
978 None,
979 Some(0),
980 false,
981 ));
982 check_stats(Statistics::boolean(
983 Some(true),
984 Some(true),
985 None,
986 Some(7),
987 true,
988 ));
989 check_stats(Statistics::boolean(
990 Some(false),
991 Some(false),
992 None,
993 Some(7),
994 true,
995 ));
996 check_stats(Statistics::boolean(None, None, None, Some(7), true));
997
998 check_stats(Statistics::int32(
999 Some(-100),
1000 Some(500),
1001 None,
1002 Some(7),
1003 true,
1004 ));
1005 check_stats(Statistics::int32(
1006 Some(-100),
1007 Some(500),
1008 None,
1009 Some(0),
1010 false,
1011 ));
1012 check_stats(Statistics::int32(None, None, None, Some(7), true));
1013
1014 check_stats(Statistics::int64(
1015 Some(-100),
1016 Some(200),
1017 None,
1018 Some(7),
1019 true,
1020 ));
1021 check_stats(Statistics::int64(
1022 Some(-100),
1023 Some(200),
1024 None,
1025 Some(0),
1026 false,
1027 ));
1028 check_stats(Statistics::int64(None, None, None, Some(7), true));
1029
1030 check_stats(Statistics::float(Some(1.2), Some(3.4), None, Some(7), true));
1031 check_stats(Statistics::float(
1032 Some(1.2),
1033 Some(3.4),
1034 None,
1035 Some(0),
1036 false,
1037 ));
1038 check_stats(Statistics::float(None, None, None, Some(7), true));
1039
1040 check_stats(Statistics::double(
1041 Some(1.2),
1042 Some(3.4),
1043 None,
1044 Some(7),
1045 true,
1046 ));
1047 check_stats(Statistics::double(
1048 Some(1.2),
1049 Some(3.4),
1050 None,
1051 Some(0),
1052 false,
1053 ));
1054 check_stats(Statistics::double(None, None, None, Some(7), true));
1055
1056 check_stats(Statistics::byte_array(
1057 Some(ByteArray::from(vec![1, 2, 3])),
1058 Some(ByteArray::from(vec![3, 4, 5])),
1059 None,
1060 Some(7),
1061 true,
1062 ));
1063 check_stats(Statistics::byte_array(None, None, None, Some(7), true));
1064
1065 check_stats(Statistics::fixed_len_byte_array(
1066 Some(ByteArray::from(vec![1, 2, 3]).into()),
1067 Some(ByteArray::from(vec![3, 4, 5]).into()),
1068 None,
1069 Some(7),
1070 true,
1071 ));
1072 check_stats(Statistics::fixed_len_byte_array(
1073 None,
1074 None,
1075 None,
1076 Some(7),
1077 true,
1078 ));
1079 }
1080
1081 #[test]
1082 fn test_count_encoding() {
1083 statistics_count_test(None, None);
1084 statistics_count_test(Some(0), Some(0));
1085 statistics_count_test(Some(100), Some(2000));
1086 statistics_count_test(Some(1), None);
1087 statistics_count_test(None, Some(1));
1088 }
1089
1090 #[test]
1091 fn test_count_encoding_distinct_too_large() {
1092 let statistics = make_bool_stats(Some(u64::MAX), Some(100));
1094 let thrift_stats = page_stats_to_thrift(Some(&statistics)).unwrap();
1095 assert_eq!(thrift_stats.distinct_count, None); assert_eq!(thrift_stats.null_count, Some(100));
1097 }
1098
1099 #[test]
1100 fn test_count_encoding_null_too_large() {
1101 let statistics = make_bool_stats(Some(100), Some(u64::MAX));
1103 let thrift_stats = page_stats_to_thrift(Some(&statistics)).unwrap();
1104 assert_eq!(thrift_stats.distinct_count, Some(100));
1105 assert_eq!(thrift_stats.null_count, None); }
1107
1108 #[test]
1109 fn test_count_decoding_null_invalid() {
1110 let tstatistics = PageStatistics {
1111 null_count: Some(-42),
1112 max: None,
1113 min: None,
1114 distinct_count: None,
1115 max_value: None,
1116 min_value: None,
1117 is_max_value_exact: None,
1118 is_min_value_exact: None,
1119 nan_count: None,
1120 };
1121 let err = from_thrift_page_stats(Type::BOOLEAN, Some(tstatistics)).unwrap_err();
1122 assert_eq!(
1123 err.to_string(),
1124 "Parquet error: Statistics null count is negative -42"
1125 );
1126 }
1127
1128 fn statistics_count_test(distinct_count: Option<u64>, null_count: Option<u64>) {
1132 let statistics = make_bool_stats(distinct_count, null_count);
1133
1134 let thrift_stats = page_stats_to_thrift(Some(&statistics)).unwrap();
1135 assert_eq!(thrift_stats.null_count.map(|c| c as u64), null_count);
1136 assert_eq!(
1137 thrift_stats.distinct_count.map(|c| c as u64),
1138 distinct_count
1139 );
1140
1141 let round_tripped = from_thrift_page_stats(Type::BOOLEAN, Some(thrift_stats))
1142 .unwrap()
1143 .unwrap();
1144 assert_eq!(round_tripped, statistics);
1145 }
1146
1147 fn make_bool_stats(distinct_count: Option<u64>, null_count: Option<u64>) -> Statistics {
1148 let min = Some(true);
1149 let max = Some(false);
1150 let is_min_max_deprecated = false;
1151
1152 Statistics::Boolean(ValueStatistics::new(
1154 min,
1155 max,
1156 distinct_count,
1157 null_count,
1158 is_min_max_deprecated,
1159 ))
1160 }
1161
1162 #[test]
1163 fn test_int96_invalid_statistics() {
1164 let mut thrift_stats = PageStatistics {
1165 max: None,
1166 min: Some((0..13).collect()),
1167 null_count: Some(0),
1168 distinct_count: None,
1169 max_value: None,
1170 min_value: None,
1171 is_max_value_exact: None,
1172 is_min_value_exact: None,
1173 nan_count: None,
1174 };
1175
1176 let err = from_thrift_page_stats(Type::INT96, Some(thrift_stats.clone())).unwrap_err();
1177 assert_eq!(
1178 err.to_string(),
1179 "Parquet error: Incorrect Int96 min statistics"
1180 );
1181
1182 thrift_stats.min = None;
1183 thrift_stats.max = Some((0..13).collect());
1184 let err = from_thrift_page_stats(Type::INT96, Some(thrift_stats)).unwrap_err();
1185 assert_eq!(
1186 err.to_string(),
1187 "Parquet error: Incorrect Int96 max statistics"
1188 );
1189 }
1190
1191 fn generic_statistics_handler<T: std::fmt::Display>(stats: ValueStatistics<T>) -> String {
1194 match stats.min_opt() {
1195 Some(s) => format!("min: {s}"),
1196 None => "min: NA".to_string(),
1197 }
1198 }
1199
1200 #[test]
1201 fn test_generic_access() {
1202 let stats = Statistics::int32(Some(12), Some(45), None, Some(11), false);
1203
1204 match stats {
1205 Statistics::Int32(v) => {
1206 let stats_string = generic_statistics_handler(v);
1207 assert_eq!(&stats_string, "min: 12");
1208 }
1209 _ => unreachable!(),
1210 }
1211 }
1212
1213 #[test]
1214 fn test_nan_count_float() {
1215 let stats = Statistics::Float(
1217 ValueStatistics::new(Some(1.0_f32), Some(5.0_f32), None, Some(0), false)
1218 .with_nan_count(Some(3)),
1219 );
1220
1221 assert_eq!(stats.nan_count_opt(), Some(3));
1222
1223 let thrift_stats = page_stats_to_thrift(Some(&stats)).unwrap();
1225 assert_eq!(thrift_stats.nan_count, Some(3));
1226
1227 let round_tripped = from_thrift_page_stats(Type::FLOAT, Some(thrift_stats))
1228 .unwrap()
1229 .unwrap();
1230 assert_eq!(round_tripped.nan_count_opt(), Some(3));
1231 }
1232
1233 #[test]
1234 fn test_nan_count_double() {
1235 let stats = Statistics::Double(
1237 ValueStatistics::new(Some(1.0_f64), Some(5.0_f64), None, Some(0), false)
1238 .with_nan_count(Some(5)),
1239 );
1240
1241 assert_eq!(stats.nan_count_opt(), Some(5));
1242
1243 let thrift_stats = page_stats_to_thrift(Some(&stats)).unwrap();
1245 assert_eq!(thrift_stats.nan_count, Some(5));
1246
1247 let round_tripped = from_thrift_page_stats(Type::DOUBLE, Some(thrift_stats))
1248 .unwrap()
1249 .unwrap();
1250 assert_eq!(round_tripped.nan_count_opt(), Some(5));
1251 }
1252
1253 #[test]
1254 fn test_nan_count_none_for_non_float() {
1255 let stats = Statistics::int32(Some(1), Some(100), None, Some(0), false);
1257 assert_eq!(stats.nan_count_opt(), None);
1258
1259 let thrift_stats = page_stats_to_thrift(Some(&stats)).unwrap();
1260 assert_eq!(thrift_stats.nan_count, None);
1261 }
1262
1263 #[test]
1264 fn test_nan_count_backwards_compatible() {
1265 let thrift_stats = PageStatistics {
1267 min: None,
1268 max: None,
1269 min_value: Some(vec![0, 0, 0, 0]), max_value: Some(vec![0, 0, 128, 63]), null_count: Some(0),
1272 distinct_count: None,
1273 nan_count: None, is_min_value_exact: None,
1275 is_max_value_exact: None,
1276 };
1277
1278 let stats = from_thrift_page_stats(Type::FLOAT, Some(thrift_stats))
1279 .unwrap()
1280 .unwrap();
1281
1282 assert_eq!(stats.nan_count_opt(), None);
1284 }
1285
1286 #[test]
1287 fn test_statistics_with_nan_min_max() {
1288 let stats = Statistics::Float(
1290 ValueStatistics::new(
1291 Some(f32::NAN), Some(f32::NAN),
1293 None,
1294 Some(0),
1295 false,
1296 )
1297 .with_nan_count(Some(10)), );
1299
1300 assert_eq!(stats.min_bytes_opt(), Some(f32::NAN.as_bytes()));
1301 assert_eq!(stats.max_bytes_opt(), Some(f32::NAN.as_bytes()));
1302 assert_eq!(stats.nan_count_opt(), Some(10));
1303
1304 let thrift_stats = page_stats_to_thrift(Some(&stats)).unwrap();
1306 assert_eq!(thrift_stats.min_value, Some(f32::NAN.as_bytes().to_vec()));
1307 assert_eq!(thrift_stats.max_value, Some(f32::NAN.as_bytes().to_vec()));
1308 assert_eq!(thrift_stats.nan_count, Some(10));
1309 }
1310
1311 #[test]
1312 fn test_nan_count_too_large() {
1313 let stats = Statistics::Float(
1315 ValueStatistics::new(Some(1.0_f32), Some(2.0_f32), None, Some(0), false)
1316 .with_nan_count(Some(u64::MAX)),
1317 );
1318
1319 let thrift_stats = page_stats_to_thrift(Some(&stats)).unwrap();
1320 assert_eq!(thrift_stats.nan_count, None);
1322 }
1323
1324 #[test]
1325 fn test_nan_counts_in_column_index() {
1326 use crate::file::metadata::ColumnIndexBuilder;
1328
1329 let mut float_builder = ColumnIndexBuilder::new(Type::FLOAT);
1331 float_builder.append(false, vec![0u8; 4], vec![255u8; 4], 0, Some(5));
1332 float_builder.append(false, vec![0u8; 4], vec![255u8; 4], 2, Some(3));
1333 float_builder.append(false, vec![0u8; 4], vec![255u8; 4], 0, Some(0)); let float_column_index = float_builder.build().unwrap();
1336 assert_eq!(float_column_index.nan_counts(), Some(&vec![5, 3, 0]));
1338
1339 let mut int_builder = ColumnIndexBuilder::new(Type::INT32);
1341 int_builder.append(false, vec![0u8; 4], vec![255u8; 4], 0, None);
1342 int_builder.append(false, vec![0u8; 4], vec![255u8; 4], 2, None);
1343 int_builder.append(false, vec![0u8; 4], vec![255u8; 4], 0, None);
1344
1345 let int_column_index = int_builder.build().unwrap();
1346 assert_eq!(int_column_index.nan_counts(), None);
1348 }
1349}