This is an automated email from the ASF dual-hosted git repository.
alamb pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/datafusion.git
The following commit(s) were added to refs/heads/main by this push:
new 2f43476471 Initial Extract parquet data page statistics API (#10852)
2f43476471 is described below
commit 2f4347647172f6997448b2e24d322b50c856f3a0
Author: Marvin Lanhenke <[email protected]>
AuthorDate: Sat Jun 15 17:58:44 2024 +0200
Initial Extract parquet data page statistics API (#10852)
* feat: enable page statistics
* feat: prototype int64 data_page_min
* feat: prototype MinInt64DataPageStatsIterator
* feat: add make_data_page_stats_iterator macro
* feat: add get_data_page_statistics macro
* feat: add MaxInt64DataPageStatsIterator
* feat: add test_data_page_stats param
* chore: add testcase int64_with_nulls
* feat: add data page null_counts
* fix: clippy
* chore: rename column_page_index
* feat: add data page row counts
* feat: add num_data_pages to iterator
* chore: update docs
* fix: use colum_offset len in data_page_null_counts
* fix: docs
* tweak comments
* update test helper
* Add explicit multi-data page tests to statistics test
* Add explicit data page test
* remove duplicate test
* update coverage
---------
Co-authored-by: Andrew Lamb <[email protected]>
---
.../datasource/physical_plan/parquet/statistics.rs | 315 +++++++++++++-
datafusion/core/tests/parquet/arrow_statistics.rs | 479 +++++++++++++++------
datafusion/core/tests/parquet/mod.rs | 3 +-
3 files changed, 657 insertions(+), 140 deletions(-)
diff --git a/datafusion/core/src/datasource/physical_plan/parquet/statistics.rs
b/datafusion/core/src/datasource/physical_plan/parquet/statistics.rs
index c0d36f1fc4..a2e0d8fa66 100644
--- a/datafusion/core/src/datasource/physical_plan/parquet/statistics.rs
+++ b/datafusion/core/src/datasource/physical_plan/parquet/statistics.rs
@@ -33,7 +33,8 @@ use arrow_array::{
use arrow_schema::{Field, FieldRef, Schema, TimeUnit};
use datafusion_common::{internal_datafusion_err, internal_err, plan_err,
Result};
use half::f16;
-use parquet::file::metadata::RowGroupMetaData;
+use parquet::file::metadata::{ParquetColumnIndex, ParquetOffsetIndex,
RowGroupMetaData};
+use parquet::file::page_index::index::Index;
use parquet::file::statistics::Statistics as ParquetStatistics;
use parquet::schema::types::SchemaDescriptor;
use paste::paste;
@@ -517,6 +518,74 @@ macro_rules! get_statistics {
}}}
}
+macro_rules! make_data_page_stats_iterator {
+ ($iterator_type: ident, $func: ident, $index_type: path, $stat_value_type:
ty) => {
+ struct $iterator_type<'a, I>
+ where
+ I: Iterator<Item = (usize, &'a Index)>,
+ {
+ iter: I,
+ }
+
+ impl<'a, I> $iterator_type<'a, I>
+ where
+ I: Iterator<Item = (usize, &'a Index)>,
+ {
+ fn new(iter: I) -> Self {
+ Self { iter }
+ }
+ }
+
+ impl<'a, I> Iterator for $iterator_type<'a, I>
+ where
+ I: Iterator<Item = (usize, &'a Index)>,
+ {
+ type Item = Vec<Option<$stat_value_type>>;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ let next = self.iter.next();
+ match next {
+ Some((len, index)) => match index {
+ $index_type(native_index) => Some(
+ native_index
+ .indexes
+ .iter()
+ .map(|x| x.$func)
+ .collect::<Vec<_>>(),
+ ),
+ // No matching `Index` found;
+ // thus no statistics that can be extracted.
+ // We return vec![None; len] to effectively
+ // create an arrow null-array with the length
+ // corresponding to the number of entries in
+ // `ParquetOffsetIndex` per row group per column.
+ _ => Some(vec![None; len]),
+ },
+ _ => None,
+ }
+ }
+
+ fn size_hint(&self) -> (usize, Option<usize>) {
+ self.iter.size_hint()
+ }
+ }
+ };
+}
+
+make_data_page_stats_iterator!(MinInt64DataPageStatsIterator, min,
Index::INT64, i64);
+make_data_page_stats_iterator!(MaxInt64DataPageStatsIterator, max,
Index::INT64, i64);
+
+macro_rules! get_data_page_statistics {
+ ($stat_type_prefix: ident, $data_type: ident, $iterator: ident) => {
+ paste! {
+ match $data_type {
+ Some(DataType::Int64) =>
Ok(Arc::new(Int64Array::from_iter([<$stat_type_prefix
Int64DataPageStatsIterator>]::new($iterator).flatten()))),
+ _ => unimplemented!()
+ }
+ }
+ }
+}
+
/// Lookups up the parquet column by name
///
/// Returns the parquet column index and the corresponding arrow field
@@ -563,6 +632,51 @@ fn max_statistics<'a, I: Iterator<Item = Option<&'a
ParquetStatistics>>>(
get_statistics!(Max, data_type, iterator)
}
+/// Extracts the min statistics from an iterator
+/// of parquet page [`Index`]'es to an [`ArrayRef`]
+pub(crate) fn min_page_statistics<'a, I>(
+ data_type: Option<&DataType>,
+ iterator: I,
+) -> Result<ArrayRef>
+where
+ I: Iterator<Item = (usize, &'a Index)>,
+{
+ get_data_page_statistics!(Min, data_type, iterator)
+}
+
+/// Extracts the max statistics from an iterator
+/// of parquet page [`Index`]'es to an [`ArrayRef`]
+pub(crate) fn max_page_statistics<'a, I>(
+ data_type: Option<&DataType>,
+ iterator: I,
+) -> Result<ArrayRef>
+where
+ I: Iterator<Item = (usize, &'a Index)>,
+{
+ get_data_page_statistics!(Max, data_type, iterator)
+}
+
+/// Extracts the null count statistics from an iterator
+/// of parquet page [`Index`]'es to an [`ArrayRef`]
+///
+/// The returned Array is an [`UInt64Array`]
+pub(crate) fn null_counts_page_statistics<'a, I>(iterator: I) ->
Result<ArrayRef>
+where
+ I: Iterator<Item = (usize, &'a Index)>,
+{
+ let iter = iterator.flat_map(|(len, index)| match index {
+ Index::NONE => vec![None; len],
+ Index::INT64(native_index) => native_index
+ .indexes
+ .iter()
+ .map(|x| x.null_count.map(|x| x as u64))
+ .collect::<Vec<_>>(),
+ _ => unimplemented!(),
+ });
+
+ Ok(Arc::new(UInt64Array::from_iter(iter)))
+}
+
/// Extracts Parquet statistics as Arrow arrays
///
/// This is used to convert Parquet statistics to Arrow arrays, with proper
type
@@ -771,10 +885,205 @@ impl<'a> StatisticsConverter<'a> {
Ok(Arc::new(UInt64Array::from_iter(null_counts)))
}
+ /// Extract the minimum values from Data Page statistics.
+ ///
+ /// In Parquet files, in addition to the Column Chunk level statistics
+ /// (stored for each column for each row group) there are also
+ /// optional statistics stored for each data page, as part of
+ /// the [`ParquetColumnIndex`].
+ ///
+ /// Since a single Column Chunk is stored as one or more pages,
+ /// page level statistics can prune at a finer granularity.
+ ///
+ /// However since they are stored in a separate metadata
+ /// structure ([`Index`]) there is different code to extract them as
+ /// compared to arrow statistics.
+ ///
+ /// # Parameters:
+ ///
+ /// * `column_page_index`: The parquet column page indices, read from
+ /// `ParquetMetaData` column_index
+ ///
+ /// * `column_offset_index`: The parquet column offset indices, read from
+ /// `ParquetMetaData` offset_index
+ ///
+ /// * `row_group_indices`: The indices of the row groups, that are used to
+ /// extract the column page index and offset index on a per row group
+ /// per column basis.
+ ///
+ /// # Return Value
+ ///
+ /// The returned array contains 1 value for each `NativeIndex`
+ /// in the underlying `Index`es, in the same order as they appear
+ /// in `metadatas`.
+ ///
+ /// For example, if there are two `Index`es in `metadatas`:
+ /// 1. the first having `3` `PageIndex` entries
+ /// 2. the second having `2` `PageIndex` entries
+ ///
+ /// The returned array would have 5 rows.
+ ///
+ /// Each value is either:
+ /// * the minimum value for the page
+ /// * a null value, if the statistics can not be extracted
+ ///
+ /// Note that a null value does NOT mean the min value was actually
+ /// `null` it means it the requested statistic is unknown
+ ///
+ /// # Errors
+ ///
+ /// Reasons for not being able to extract the statistics include:
+ /// * the column is not present in the parquet file
+ /// * statistics for the pages are not present in the row group
+ /// * the stored statistic value can not be converted to the requested type
+ pub fn data_page_mins<I>(
+ &self,
+ column_page_index: &ParquetColumnIndex,
+ column_offset_index: &ParquetOffsetIndex,
+ row_group_indices: I,
+ ) -> Result<ArrayRef>
+ where
+ I: IntoIterator<Item = &'a usize>,
+ {
+ let data_type = self.arrow_field.data_type();
+
+ let Some(parquet_index) = self.parquet_index else {
+ return Ok(self.make_null_array(data_type, row_group_indices));
+ };
+
+ let iter = row_group_indices.into_iter().map(|rg_index| {
+ let column_page_index_per_row_group_per_column =
+ &column_page_index[*rg_index][parquet_index];
+ let num_data_pages =
&column_offset_index[*rg_index][parquet_index].len();
+
+ (*num_data_pages, column_page_index_per_row_group_per_column)
+ });
+
+ min_page_statistics(Some(data_type), iter)
+ }
+
+ /// Extract the maximum values from Data Page statistics.
+ ///
+ /// See docs on [`Self::data_page_mins`] for details.
+ pub fn data_page_maxes<I>(
+ &self,
+ column_page_index: &ParquetColumnIndex,
+ column_offset_index: &ParquetOffsetIndex,
+ row_group_indices: I,
+ ) -> Result<ArrayRef>
+ where
+ I: IntoIterator<Item = &'a usize>,
+ {
+ let data_type = self.arrow_field.data_type();
+
+ let Some(parquet_index) = self.parquet_index else {
+ return Ok(self.make_null_array(data_type, row_group_indices));
+ };
+
+ let iter = row_group_indices.into_iter().map(|rg_index| {
+ let column_page_index_per_row_group_per_column =
+ &column_page_index[*rg_index][parquet_index];
+ let num_data_pages =
&column_offset_index[*rg_index][parquet_index].len();
+
+ (*num_data_pages, column_page_index_per_row_group_per_column)
+ });
+
+ max_page_statistics(Some(data_type), iter)
+ }
+
+ /// Extract the null counts from Data Page statistics.
+ ///
+ /// The returned Array is an [`UInt64Array`]
+ ///
+ /// See docs on [`Self::data_page_mins`] for details.
+ pub fn data_page_null_counts<I>(
+ &self,
+ column_page_index: &ParquetColumnIndex,
+ column_offset_index: &ParquetOffsetIndex,
+ row_group_indices: I,
+ ) -> Result<ArrayRef>
+ where
+ I: IntoIterator<Item = &'a usize>,
+ {
+ let data_type = self.arrow_field.data_type();
+
+ let Some(parquet_index) = self.parquet_index else {
+ return Ok(self.make_null_array(data_type, row_group_indices));
+ };
+
+ let iter = row_group_indices.into_iter().map(|rg_index| {
+ let column_page_index_per_row_group_per_column =
+ &column_page_index[*rg_index][parquet_index];
+ let num_data_pages =
&column_offset_index[*rg_index][parquet_index].len();
+
+ (*num_data_pages, column_page_index_per_row_group_per_column)
+ });
+ null_counts_page_statistics(iter)
+ }
+
+ /// Returns an [`ArrayRef`] with row counts for each row group.
+ ///
+ /// This function iterates over the given row group indexes and computes
+ /// the row count for each page in the specified column.
+ ///
+ /// # Parameters:
+ ///
+ /// * `column_offset_index`: The parquet column offset indices, read from
+ /// `ParquetMetaData` offset_index
+ ///
+ /// * `row_group_metadatas`: The metadata slice of the row groups, read
+ /// from `ParquetMetaData` row_groups
+ ///
+ /// * `row_group_indices`: The indices of the row groups, that are used to
+ /// extract the column offset index on a per row group per column basis.
+ ///
+ /// See docs on [`Self::data_page_mins`] for details.
+ pub fn data_page_row_counts<I>(
+ &self,
+ column_offset_index: &ParquetOffsetIndex,
+ row_group_metadatas: &[RowGroupMetaData],
+ row_group_indices: I,
+ ) -> Result<ArrayRef>
+ where
+ I: IntoIterator<Item = &'a usize>,
+ {
+ let data_type = self.arrow_field.data_type();
+
+ let Some(parquet_index) = self.parquet_index else {
+ return Ok(self.make_null_array(data_type, row_group_indices));
+ };
+
+ // `offset_index[row_group_number][column_number][page_number]` holds
+ // the [`PageLocation`] corresponding to page `page_number` of column
+ // `column_number`of row group `row_group_number`.
+ let mut row_count_total = Vec::new();
+ for rg_idx in row_group_indices {
+ let page_locations = &column_offset_index[*rg_idx][parquet_index];
+
+ let row_count_per_page = page_locations.windows(2).map(|loc| {
+ Some(loc[1].first_row_index as u64 - loc[0].first_row_index as
u64)
+ });
+
+ let num_rows_in_row_group =
&row_group_metadatas[*rg_idx].num_rows();
+
+ // append the last page row count
+ let row_count_per_page = row_count_per_page
+ .chain(std::iter::once(Some(
+ *num_rows_in_row_group as u64
+ - page_locations.last().unwrap().first_row_index as
u64,
+ )))
+ .collect::<Vec<_>>();
+
+ row_count_total.extend(row_count_per_page);
+ }
+
+ Ok(Arc::new(UInt64Array::from_iter(row_count_total)))
+ }
+
/// Returns a null array of data_type with one element per row group
- fn make_null_array<I>(&self, data_type: &DataType, metadatas: I) ->
ArrayRef
+ fn make_null_array<I, A>(&self, data_type: &DataType, metadatas: I) ->
ArrayRef
where
- I: IntoIterator<Item = &'a RowGroupMetaData>,
+ I: IntoIterator<Item = A>,
{
// column was in the arrow schema but not in the parquet schema, so
return a null array
let num_row_groups = metadatas.into_iter().count();
diff --git a/datafusion/core/tests/parquet/arrow_statistics.rs
b/datafusion/core/tests/parquet/arrow_statistics.rs
index 2ea18d7cf8..3c812800e2 100644
--- a/datafusion/core/tests/parquet/arrow_statistics.rs
+++ b/datafusion/core/tests/parquet/arrow_statistics.rs
@@ -18,6 +18,7 @@
//! This file contains an end to end test of extracting statitics from parquet
files.
//! It writes data into a parquet file, reads statistics and verifies they are
correct
+use std::default::Default;
use std::fs::File;
use std::sync::Arc;
@@ -39,102 +40,102 @@ use arrow_array::{
use arrow_schema::{DataType, Field, Schema};
use datafusion::datasource::physical_plan::parquet::StatisticsConverter;
use half::f16;
-use parquet::arrow::arrow_reader::{ArrowReaderBuilder,
ParquetRecordBatchReaderBuilder};
+use parquet::arrow::arrow_reader::{
+ ArrowReaderBuilder, ArrowReaderOptions, ParquetRecordBatchReaderBuilder,
+};
use parquet::arrow::ArrowWriter;
use parquet::file::properties::{EnabledStatistics, WriterProperties};
use super::make_test_file_rg;
-// TEST HELPERS
-
-/// Return a record batch with i64 with Null values
-fn make_int64_batches_with_null(
+#[derive(Debug, Default, Clone)]
+struct Int64Case {
+ /// Number of nulls in the column
null_values: usize,
+ /// Non null values in the range `[no_null_values_start,
+ /// no_null_values_end]`, one value for each row
no_null_values_start: i64,
no_null_values_end: i64,
-) -> RecordBatch {
- let schema = Arc::new(Schema::new(vec![Field::new("i64", DataType::Int64,
true)]));
-
- let v64: Vec<i64> = (no_null_values_start as _..no_null_values_end as
_).collect();
-
- RecordBatch::try_new(
- schema,
- vec![make_array(
- Int64Array::from_iter(
- v64.into_iter()
- .map(Some)
- .chain(std::iter::repeat(None).take(null_values)),
- )
- .to_data(),
- )],
- )
- .unwrap()
-}
-
-// Create a parquet file with one column for data type i64
-// Data of the file include
-// . Number of null rows is the given num_null
-// . There are non-null values in the range [no_null_values_start,
no_null_values_end], one value each row
-// . The file is divided into row groups of size row_per_group
-pub fn parquet_file_one_column(
- num_null: usize,
- no_null_values_start: i64,
- no_null_values_end: i64,
+ /// Number of rows per row group
row_per_group: usize,
-) -> ParquetRecordBatchReaderBuilder<File> {
- parquet_file_one_column_stats(
- num_null,
- no_null_values_start,
- no_null_values_end,
- row_per_group,
- EnabledStatistics::Chunk,
- )
+ /// if specified, overrides default statistics settings
+ enable_stats: Option<EnabledStatistics>,
+ /// If specified, the number of values in each page
+ data_page_row_count_limit: Option<usize>,
}
-// Create a parquet file with one column for data type i64
-// Data of the file include
-// . Number of null rows is the given num_null
-// . There are non-null values in the range [no_null_values_start,
no_null_values_end], one value each row
-// . The file is divided into row groups of size row_per_group
-// . Statistics are enabled/disabled based on the given enable_stats
-pub fn parquet_file_one_column_stats(
- num_null: usize,
- no_null_values_start: i64,
- no_null_values_end: i64,
- row_per_group: usize,
- enable_stats: EnabledStatistics,
-) -> ParquetRecordBatchReaderBuilder<File> {
- let mut output_file = tempfile::Builder::new()
- .prefix("parquert_statistics_test")
- .suffix(".parquet")
- .tempfile()
- .expect("tempfile creation");
-
- let props = WriterProperties::builder()
- .set_max_row_group_size(row_per_group)
- .set_statistics_enabled(enable_stats)
- .build();
-
- let batches = vec![make_int64_batches_with_null(
- num_null,
- no_null_values_start,
- no_null_values_end,
- )];
-
- let schema = batches[0].schema();
-
- let mut writer = ArrowWriter::try_new(&mut output_file, schema,
Some(props)).unwrap();
+impl Int64Case {
+ /// Return a record batch with i64 with Null values
+ /// The first no_null_values_end - no_null_values_start values
+ /// are non-null with the specified range, the rest are null
+ fn make_int64_batches_with_null(&self) -> RecordBatch {
+ let schema =
+ Arc::new(Schema::new(vec![Field::new("i64", DataType::Int64,
true)]));
+
+ let v64: Vec<i64> =
+ (self.no_null_values_start as _..self.no_null_values_end as
_).collect();
+
+ RecordBatch::try_new(
+ schema,
+ vec![make_array(
+ Int64Array::from_iter(
+ v64.into_iter()
+ .map(Some)
+ .chain(std::iter::repeat(None).take(self.null_values)),
+ )
+ .to_data(),
+ )],
+ )
+ .unwrap()
+ }
+
+ // Create a parquet file with the specified settings
+ pub fn build(&self) -> ParquetRecordBatchReaderBuilder<File> {
+ let mut output_file = tempfile::Builder::new()
+ .prefix("parquert_statistics_test")
+ .suffix(".parquet")
+ .tempfile()
+ .expect("tempfile creation");
+
+ let mut builder =
+
WriterProperties::builder().set_max_row_group_size(self.row_per_group);
+ if let Some(enable_stats) = self.enable_stats {
+ builder = builder.set_statistics_enabled(enable_stats);
+ }
+ if let Some(data_page_row_count_limit) =
self.data_page_row_count_limit {
+ builder =
builder.set_data_page_row_count_limit(data_page_row_count_limit);
+ }
+ let props = builder.build();
+
+ let batches = vec![self.make_int64_batches_with_null()];
+
+ let schema = batches[0].schema();
+
+ let mut writer =
+ ArrowWriter::try_new(&mut output_file, schema,
Some(props)).unwrap();
+
+ // if we have a datapage limit send the batches in one at a time to
give
+ // the writer a chance to be split into multiple pages
+ if self.data_page_row_count_limit.is_some() {
+ for batch in batches {
+ for i in 0..batch.num_rows() {
+ writer.write(&batch.slice(i, 1)).expect("writing batch");
+ }
+ }
+ } else {
+ for batch in batches {
+ writer.write(&batch).expect("writing batch");
+ }
+ }
+
+ // close file
+ let _file_meta = writer.close().unwrap();
- for batch in batches {
- writer.write(&batch).expect("writing batch");
+ // open the file & get the reader
+ let file = output_file.reopen().unwrap();
+ let options = ArrowReaderOptions::new().with_page_index(true);
+ ArrowReaderBuilder::try_new_with_options(file, options).unwrap()
}
-
- // close file
- let _file_meta = writer.close().unwrap();
-
- // open the file & get the reader
- let file = output_file.reopen().unwrap();
- ArrowReaderBuilder::try_new(file).unwrap()
}
/// Defines what data to create in a parquet file
@@ -158,7 +159,8 @@ impl TestReader {
// open the file & get the reader
let file = file.reopen().unwrap();
- ArrowReaderBuilder::try_new(file).unwrap()
+ let options = ArrowReaderOptions::new().with_page_index(true);
+ ArrowReaderBuilder::try_new_with_options(file, options).unwrap()
}
}
@@ -172,6 +174,9 @@ struct Test<'a> {
expected_row_counts: UInt64Array,
/// Which column to extract statistics from
column_name: &'static str,
+ /// If true, extracts and compares data page statistics rather than row
+ /// group statistics
+ test_data_page_statistics: bool,
}
impl<'a> Test<'a> {
@@ -183,6 +188,7 @@ impl<'a> Test<'a> {
expected_null_counts,
expected_row_counts,
column_name,
+ test_data_page_statistics,
} = self;
let converter = StatisticsConverter::try_new(
@@ -193,36 +199,103 @@ impl<'a> Test<'a> {
.unwrap();
let row_groups = reader.metadata().row_groups();
- let min = converter.row_group_mins(row_groups).unwrap();
-
- assert_eq!(
- &min, &expected_min,
- "{column_name}: Mismatch with expected minimums"
- );
- let max = converter.row_group_maxes(row_groups).unwrap();
- assert_eq!(
- &max, &expected_max,
- "{column_name}: Mismatch with expected maximum"
- );
-
- let null_counts = converter.row_group_null_counts(row_groups).unwrap();
- let expected_null_counts = Arc::new(expected_null_counts) as ArrayRef;
- assert_eq!(
- &null_counts, &expected_null_counts,
- "{column_name}: Mismatch with expected null counts. \
- Actual: {null_counts:?}. Expected: {expected_null_counts:?}"
- );
-
- let row_counts = StatisticsConverter::row_group_row_counts(
- reader.metadata().row_groups().iter(),
- )
- .unwrap();
- assert_eq!(
- row_counts, expected_row_counts,
- "{column_name}: Mismatch with expected row counts. \
- Actual: {row_counts:?}. Expected: {expected_row_counts:?}"
- );
+ if test_data_page_statistics {
+ let column_page_index = reader
+ .metadata()
+ .column_index()
+ .expect("File should have column page indices");
+
+ let column_offset_index = reader
+ .metadata()
+ .offset_index()
+ .expect("File should have column offset indices");
+
+ let row_group_indices = row_groups
+ .iter()
+ .enumerate()
+ .map(|(i, _)| i)
+ .collect::<Vec<_>>();
+
+ let min = converter
+ .data_page_mins(
+ column_page_index,
+ column_offset_index,
+ &row_group_indices,
+ )
+ .unwrap();
+ assert_eq!(
+ &min, &expected_min,
+ "{column_name}: Mismatch with expected data page minimums"
+ );
+
+ let max = converter
+ .data_page_maxes(
+ column_page_index,
+ column_offset_index,
+ &row_group_indices,
+ )
+ .unwrap();
+ assert_eq!(
+ &max, &expected_max,
+ "{column_name}: Mismatch with expected data page maximum"
+ );
+
+ let null_counts = converter
+ .data_page_null_counts(
+ column_page_index,
+ column_offset_index,
+ &row_group_indices,
+ )
+ .unwrap();
+
+ let expected_null_counts = Arc::new(expected_null_counts) as
ArrayRef;
+ assert_eq!(
+ &null_counts, &expected_null_counts,
+ "{column_name}: Mismatch with expected data page null counts. \
+ Actual: {null_counts:?}. Expected: {expected_null_counts:?}"
+ );
+
+ let row_counts = converter
+ .data_page_row_counts(column_offset_index, row_groups,
&row_group_indices)
+ .unwrap();
+ let expected_row_counts = Arc::new(expected_row_counts) as
ArrayRef;
+ assert_eq!(
+ &row_counts, &expected_row_counts,
+ "{column_name}: Mismatch with expected row counts. \
+ Actual: {row_counts:?}. Expected: {expected_row_counts:?}"
+ );
+ } else {
+ let min = converter.row_group_mins(row_groups).unwrap();
+ assert_eq!(
+ &min, &expected_min,
+ "{column_name}: Mismatch with expected minimums"
+ );
+
+ let max = converter.row_group_maxes(row_groups).unwrap();
+ assert_eq!(
+ &max, &expected_max,
+ "{column_name}: Mismatch with expected maximum"
+ );
+
+ let null_counts =
converter.row_group_null_counts(row_groups).unwrap();
+ let expected_null_counts = Arc::new(expected_null_counts) as
ArrayRef;
+ assert_eq!(
+ &null_counts, &expected_null_counts,
+ "{column_name}: Mismatch with expected null counts. \
+ Actual: {null_counts:?}. Expected: {expected_null_counts:?}"
+ );
+
+ let row_counts = StatisticsConverter::row_group_row_counts(
+ reader.metadata().row_groups().iter(),
+ )
+ .unwrap();
+ assert_eq!(
+ row_counts, expected_row_counts,
+ "{column_name}: Mismatch with expected row counts. \
+ Actual: {row_counts:?}. Expected: {expected_row_counts:?}"
+ );
+ }
}
/// Run the test and expect a column not found error
@@ -234,6 +307,7 @@ impl<'a> Test<'a> {
expected_null_counts: _,
expected_row_counts: _,
column_name,
+ ..
} = self;
let converter = StatisticsConverter::try_new(
@@ -254,8 +328,15 @@ impl<'a> Test<'a> {
#[tokio::test]
async fn test_one_row_group_without_null() {
- let row_per_group = 20;
- let reader = parquet_file_one_column(0, 4, 7, row_per_group);
+ let reader = Int64Case {
+ null_values: 0,
+ no_null_values_start: 4,
+ no_null_values_end: 7,
+ row_per_group: 20,
+ ..Default::default()
+ }
+ .build();
+
Test {
reader: &reader,
// min is 4
@@ -267,14 +348,21 @@ async fn test_one_row_group_without_null() {
// 3 rows
expected_row_counts: UInt64Array::from(vec![3]),
column_name: "i64",
+ test_data_page_statistics: false,
}
.run()
}
#[tokio::test]
async fn test_one_row_group_with_null_and_negative() {
- let row_per_group = 20;
- let reader = parquet_file_one_column(2, -1, 5, row_per_group);
+ let reader = Int64Case {
+ null_values: 2,
+ no_null_values_start: -1,
+ no_null_values_end: 5,
+ row_per_group: 20,
+ ..Default::default()
+ }
+ .build();
Test {
reader: &reader,
@@ -287,14 +375,21 @@ async fn test_one_row_group_with_null_and_negative() {
// 8 rows
expected_row_counts: UInt64Array::from(vec![8]),
column_name: "i64",
+ test_data_page_statistics: false,
}
.run()
}
#[tokio::test]
async fn test_two_row_group_with_null() {
- let row_per_group = 10;
- let reader = parquet_file_one_column(2, 4, 17, row_per_group);
+ let reader = Int64Case {
+ null_values: 2,
+ no_null_values_start: 4,
+ no_null_values_end: 17,
+ row_per_group: 10,
+ ..Default::default()
+ }
+ .build();
Test {
reader: &reader,
@@ -307,14 +402,21 @@ async fn test_two_row_group_with_null() {
// row counts are [10, 5]
expected_row_counts: UInt64Array::from(vec![10, 5]),
column_name: "i64",
+ test_data_page_statistics: false,
}
.run()
}
#[tokio::test]
async fn test_two_row_groups_with_all_nulls_in_one() {
- let row_per_group = 5;
- let reader = parquet_file_one_column(4, -2, 2, row_per_group);
+ let reader = Int64Case {
+ null_values: 4,
+ no_null_values_start: -2,
+ no_null_values_end: 2,
+ row_per_group: 5,
+ ..Default::default()
+ }
+ .build();
Test {
reader: &reader,
@@ -327,6 +429,38 @@ async fn test_two_row_groups_with_all_nulls_in_one() {
// row counts are [5, 3]
expected_row_counts: UInt64Array::from(vec![5, 3]),
column_name: "i64",
+ test_data_page_statistics: false,
+ }
+ .run()
+}
+
+#[tokio::test]
+async fn test_multiple_data_pages_nulls_and_negatives() {
+ let reader = Int64Case {
+ null_values: 3,
+ no_null_values_start: -1,
+ no_null_values_end: 10,
+ row_per_group: 20,
+ // limit page row count to 4
+ data_page_row_count_limit: Some(4),
+ enable_stats: Some(EnabledStatistics::Page),
+ }
+ .build();
+
+ // Data layout looks like this:
+ //
+ // page 0: [-1, 0, 1, 2]
+ // page 1: [3, 4, 5, 6]
+ // page 2: [7, 8, 9, null]
+ // page 3: [null, null]
+ Test {
+ reader: &reader,
+ expected_min: Arc::new(Int64Array::from(vec![Some(-1), Some(3),
Some(7), None])),
+ expected_max: Arc::new(Int64Array::from(vec![Some(2), Some(6),
Some(9), None])),
+ expected_null_counts: UInt64Array::from(vec![0, 0, 1, 2]),
+ expected_row_counts: UInt64Array::from(vec![4, 4, 4, 2]),
+ column_name: "i64",
+ test_data_page_statistics: true,
}
.run()
}
@@ -347,19 +481,23 @@ async fn test_int_64() {
.build()
.await;
- Test {
- reader: &reader,
- // mins are [-5, -4, 0, 5]
- expected_min: Arc::new(Int64Array::from(vec![-5, -4, 0, 5])),
- // maxes are [-1, 0, 4, 9]
- expected_max: Arc::new(Int64Array::from(vec![-1, 0, 4, 9])),
- // nulls are [0, 0, 0, 0]
- expected_null_counts: UInt64Array::from(vec![0, 0, 0, 0]),
- // row counts are [5, 5, 5, 5]
- expected_row_counts: UInt64Array::from(vec![5, 5, 5, 5]),
- column_name: "i64",
+ // since each row has only one data page, the statistics are the same
+ for test_data_page_statistics in [true, false] {
+ Test {
+ reader: &reader,
+ // mins are [-5, -4, 0, 5]
+ expected_min: Arc::new(Int64Array::from(vec![-5, -4, 0, 5])),
+ // maxes are [-1, 0, 4, 9]
+ expected_max: Arc::new(Int64Array::from(vec![-1, 0, 4, 9])),
+ // nulls are [0, 0, 0, 0]
+ expected_null_counts: UInt64Array::from(vec![0, 0, 0, 0]),
+ // row counts are [5, 5, 5, 5]
+ expected_row_counts: UInt64Array::from(vec![5, 5, 5, 5]),
+ column_name: "i64",
+ test_data_page_statistics,
+ }
+ .run();
}
- .run();
}
#[tokio::test]
@@ -383,6 +521,7 @@ async fn test_int_32() {
// row counts are [5, 5, 5, 5]
expected_row_counts: UInt64Array::from(vec![5, 5, 5, 5]),
column_name: "i32",
+ test_data_page_statistics: false,
}
.run();
}
@@ -423,6 +562,7 @@ async fn test_int_16() {
// row counts are [5, 5, 5, 5]
expected_row_counts: UInt64Array::from(vec![5, 5, 5, 5]),
column_name: "i16",
+ test_data_page_statistics: false,
}
.run();
}
@@ -451,6 +591,7 @@ async fn test_int_8() {
// row counts are [5, 5, 5, 5]
expected_row_counts: UInt64Array::from(vec![5, 5, 5, 5]),
column_name: "i8",
+ test_data_page_statistics: false,
}
.run();
}
@@ -500,6 +641,7 @@ async fn test_timestamp() {
// row counts are [5, 5, 5, 5]
expected_row_counts: UInt64Array::from(vec![5, 5, 5, 5]),
column_name: "nanos",
+ test_data_page_statistics: false,
}
.run();
@@ -528,6 +670,7 @@ async fn test_timestamp() {
// row counts are [5, 5, 5, 5]
expected_row_counts: UInt64Array::from(vec![5, 5, 5, 5]),
column_name: "nanos_timezoned",
+ test_data_page_statistics: false,
}
.run();
@@ -549,6 +692,7 @@ async fn test_timestamp() {
expected_null_counts: UInt64Array::from(vec![1, 1, 1, 1]),
expected_row_counts: UInt64Array::from(vec![5, 5, 5, 5]),
column_name: "micros",
+ test_data_page_statistics: false,
}
.run();
@@ -577,6 +721,7 @@ async fn test_timestamp() {
// row counts are [5, 5, 5, 5]
expected_row_counts: UInt64Array::from(vec![5, 5, 5, 5]),
column_name: "micros_timezoned",
+ test_data_page_statistics: false,
}
.run();
@@ -598,6 +743,7 @@ async fn test_timestamp() {
expected_null_counts: UInt64Array::from(vec![1, 1, 1, 1]),
expected_row_counts: UInt64Array::from(vec![5, 5, 5, 5]),
column_name: "millis",
+ test_data_page_statistics: false,
}
.run();
@@ -626,6 +772,7 @@ async fn test_timestamp() {
// row counts are [5, 5, 5, 5]
expected_row_counts: UInt64Array::from(vec![5, 5, 5, 5]),
column_name: "millis_timezoned",
+ test_data_page_statistics: false,
}
.run();
@@ -647,6 +794,7 @@ async fn test_timestamp() {
expected_null_counts: UInt64Array::from(vec![1, 1, 1, 1]),
expected_row_counts: UInt64Array::from(vec![5, 5, 5, 5]),
column_name: "seconds",
+ test_data_page_statistics: false,
}
.run();
@@ -675,6 +823,7 @@ async fn test_timestamp() {
// row counts are [5, 5, 5, 5]
expected_row_counts: UInt64Array::from(vec![5, 5, 5, 5]),
column_name: "seconds_timezoned",
+ test_data_page_statistics: false,
}
.run();
}
@@ -720,6 +869,7 @@ async fn test_timestamp_diff_rg_sizes() {
// row counts are [8, 8, 4]
expected_row_counts: UInt64Array::from(vec![8, 8, 4]),
column_name: "nanos",
+ test_data_page_statistics: false,
}
.run();
@@ -746,6 +896,7 @@ async fn test_timestamp_diff_rg_sizes() {
// row counts are [8, 8, 4]
expected_row_counts: UInt64Array::from(vec![8, 8, 4]),
column_name: "nanos_timezoned",
+ test_data_page_statistics: false,
}
.run();
@@ -765,6 +916,7 @@ async fn test_timestamp_diff_rg_sizes() {
expected_null_counts: UInt64Array::from(vec![1, 2, 1]),
expected_row_counts: UInt64Array::from(vec![8, 8, 4]),
column_name: "micros",
+ test_data_page_statistics: false,
}
.run();
@@ -791,6 +943,7 @@ async fn test_timestamp_diff_rg_sizes() {
// row counts are [8, 8, 4]
expected_row_counts: UInt64Array::from(vec![8, 8, 4]),
column_name: "micros_timezoned",
+ test_data_page_statistics: false,
}
.run();
@@ -810,6 +963,7 @@ async fn test_timestamp_diff_rg_sizes() {
expected_null_counts: UInt64Array::from(vec![1, 2, 1]),
expected_row_counts: UInt64Array::from(vec![8, 8, 4]),
column_name: "millis",
+ test_data_page_statistics: false,
}
.run();
@@ -836,6 +990,7 @@ async fn test_timestamp_diff_rg_sizes() {
// row counts are [8, 8, 4]
expected_row_counts: UInt64Array::from(vec![8, 8, 4]),
column_name: "millis_timezoned",
+ test_data_page_statistics: false,
}
.run();
@@ -855,6 +1010,7 @@ async fn test_timestamp_diff_rg_sizes() {
expected_null_counts: UInt64Array::from(vec![1, 2, 1]),
expected_row_counts: UInt64Array::from(vec![8, 8, 4]),
column_name: "seconds",
+ test_data_page_statistics: false,
}
.run();
@@ -881,6 +1037,7 @@ async fn test_timestamp_diff_rg_sizes() {
// row counts are [8, 8, 4]
expected_row_counts: UInt64Array::from(vec![8, 8, 4]),
column_name: "seconds_timezoned",
+ test_data_page_statistics: false,
}
.run();
}
@@ -918,6 +1075,7 @@ async fn test_dates_32_diff_rg_sizes() {
// row counts are [13, 7]
expected_row_counts: UInt64Array::from(vec![13, 7]),
column_name: "date32",
+ test_data_page_statistics: false,
}
.run();
}
@@ -940,6 +1098,7 @@ async fn test_time32_second_diff_rg_sizes() {
expected_null_counts: UInt64Array::from(vec![0, 0, 0, 0]), // Assuming
1 null per row group for simplicity
expected_row_counts: UInt64Array::from(vec![4, 4, 4, 4]),
column_name: "second",
+ test_data_page_statistics: false,
}
.run();
}
@@ -966,6 +1125,7 @@ async fn test_time32_millisecond_diff_rg_sizes() {
expected_null_counts: UInt64Array::from(vec![0, 0, 0, 0]), // Assuming
1 null per row group for simplicity
expected_row_counts: UInt64Array::from(vec![4, 4, 4, 4]),
column_name: "millisecond",
+ test_data_page_statistics: false,
}
.run();
}
@@ -998,6 +1158,7 @@ async fn test_time64_microsecond_diff_rg_sizes() {
expected_null_counts: UInt64Array::from(vec![0, 0, 0, 0]), // Assuming
1 null per row group for simplicity
expected_row_counts: UInt64Array::from(vec![4, 4, 4, 4]),
column_name: "microsecond",
+ test_data_page_statistics: false,
}
.run();
}
@@ -1030,6 +1191,7 @@ async fn test_time64_nanosecond_diff_rg_sizes() {
expected_null_counts: UInt64Array::from(vec![0, 0, 0, 0]), // Assuming
1 null per row group for simplicity
expected_row_counts: UInt64Array::from(vec![4, 4, 4, 4]),
column_name: "nanosecond",
+ test_data_page_statistics: false,
}
.run();
}
@@ -1056,6 +1218,7 @@ async fn test_dates_64_diff_rg_sizes() {
expected_null_counts: UInt64Array::from(vec![2, 2]),
expected_row_counts: UInt64Array::from(vec![13, 7]),
column_name: "date64",
+ test_data_page_statistics: false,
}
.run();
}
@@ -1083,6 +1246,7 @@ async fn test_uint() {
expected_null_counts: UInt64Array::from(vec![0, 0, 0, 0, 0]),
expected_row_counts: UInt64Array::from(vec![4, 4, 4, 4, 4]),
column_name: "u8",
+ test_data_page_statistics: false,
}
.run();
@@ -1093,6 +1257,7 @@ async fn test_uint() {
expected_null_counts: UInt64Array::from(vec![0, 0, 0, 0, 0]),
expected_row_counts: UInt64Array::from(vec![4, 4, 4, 4, 4]),
column_name: "u16",
+ test_data_page_statistics: false,
}
.run();
@@ -1103,6 +1268,7 @@ async fn test_uint() {
expected_null_counts: UInt64Array::from(vec![0, 0, 0, 0, 0]),
expected_row_counts: UInt64Array::from(vec![4, 4, 4, 4, 4]),
column_name: "u32",
+ test_data_page_statistics: false,
}
.run();
@@ -1113,6 +1279,7 @@ async fn test_uint() {
expected_null_counts: UInt64Array::from(vec![0, 0, 0, 0, 0]),
expected_row_counts: UInt64Array::from(vec![4, 4, 4, 4, 4]),
column_name: "u64",
+ test_data_page_statistics: false,
}
.run();
}
@@ -1135,6 +1302,7 @@ async fn test_int32_range() {
expected_null_counts: UInt64Array::from(vec![0]),
expected_row_counts: UInt64Array::from(vec![4]),
column_name: "i",
+ test_data_page_statistics: false,
}
.run();
}
@@ -1157,6 +1325,7 @@ async fn test_uint32_range() {
expected_null_counts: UInt64Array::from(vec![0]),
expected_row_counts: UInt64Array::from(vec![4]),
column_name: "u",
+ test_data_page_statistics: false,
}
.run();
}
@@ -1178,6 +1347,7 @@ async fn test_numeric_limits_unsigned() {
expected_null_counts: UInt64Array::from(vec![0, 0]),
expected_row_counts: UInt64Array::from(vec![5, 2]),
column_name: "u8",
+ test_data_page_statistics: false,
}
.run();
@@ -1188,6 +1358,7 @@ async fn test_numeric_limits_unsigned() {
expected_null_counts: UInt64Array::from(vec![0, 0]),
expected_row_counts: UInt64Array::from(vec![5, 2]),
column_name: "u16",
+ test_data_page_statistics: false,
}
.run();
@@ -1198,6 +1369,7 @@ async fn test_numeric_limits_unsigned() {
expected_null_counts: UInt64Array::from(vec![0, 0]),
expected_row_counts: UInt64Array::from(vec![5, 2]),
column_name: "u32",
+ test_data_page_statistics: false,
}
.run();
@@ -1208,6 +1380,7 @@ async fn test_numeric_limits_unsigned() {
expected_null_counts: UInt64Array::from(vec![0, 0]),
expected_row_counts: UInt64Array::from(vec![5, 2]),
column_name: "u64",
+ test_data_page_statistics: false,
}
.run();
}
@@ -1229,6 +1402,7 @@ async fn test_numeric_limits_signed() {
expected_null_counts: UInt64Array::from(vec![0, 0]),
expected_row_counts: UInt64Array::from(vec![5, 2]),
column_name: "i8",
+ test_data_page_statistics: false,
}
.run();
@@ -1239,6 +1413,7 @@ async fn test_numeric_limits_signed() {
expected_null_counts: UInt64Array::from(vec![0, 0]),
expected_row_counts: UInt64Array::from(vec![5, 2]),
column_name: "i16",
+ test_data_page_statistics: false,
}
.run();
@@ -1249,6 +1424,7 @@ async fn test_numeric_limits_signed() {
expected_null_counts: UInt64Array::from(vec![0, 0]),
expected_row_counts: UInt64Array::from(vec![5, 2]),
column_name: "i32",
+ test_data_page_statistics: false,
}
.run();
@@ -1259,6 +1435,7 @@ async fn test_numeric_limits_signed() {
expected_null_counts: UInt64Array::from(vec![0, 0]),
expected_row_counts: UInt64Array::from(vec![5, 2]),
column_name: "i64",
+ test_data_page_statistics: false,
}
.run();
}
@@ -1280,6 +1457,7 @@ async fn test_numeric_limits_float() {
expected_null_counts: UInt64Array::from(vec![0, 0]),
expected_row_counts: UInt64Array::from(vec![5, 2]),
column_name: "f32",
+ test_data_page_statistics: false,
}
.run();
@@ -1290,6 +1468,7 @@ async fn test_numeric_limits_float() {
expected_null_counts: UInt64Array::from(vec![0, 0]),
expected_row_counts: UInt64Array::from(vec![5, 2]),
column_name: "f64",
+ test_data_page_statistics: false,
}
.run();
@@ -1300,6 +1479,7 @@ async fn test_numeric_limits_float() {
expected_null_counts: UInt64Array::from(vec![0, 0]),
expected_row_counts: UInt64Array::from(vec![5, 2]),
column_name: "f32_nan",
+ test_data_page_statistics: false,
}
.run();
@@ -1310,6 +1490,7 @@ async fn test_numeric_limits_float() {
expected_null_counts: UInt64Array::from(vec![0, 0]),
expected_row_counts: UInt64Array::from(vec![5, 2]),
column_name: "f64_nan",
+ test_data_page_statistics: false,
}
.run();
}
@@ -1332,6 +1513,7 @@ async fn test_float64() {
expected_null_counts: UInt64Array::from(vec![0, 0, 0, 0]),
expected_row_counts: UInt64Array::from(vec![5, 5, 5, 5]),
column_name: "f",
+ test_data_page_statistics: false,
}
.run();
}
@@ -1364,6 +1546,7 @@ async fn test_float16() {
expected_null_counts: UInt64Array::from(vec![0, 0, 0, 0]),
expected_row_counts: UInt64Array::from(vec![5, 5, 5, 5]),
column_name: "f",
+ test_data_page_statistics: false,
}
.run();
}
@@ -1394,6 +1577,7 @@ async fn test_decimal() {
expected_null_counts: UInt64Array::from(vec![0, 0, 0]),
expected_row_counts: UInt64Array::from(vec![5, 5, 5]),
column_name: "decimal_col",
+ test_data_page_statistics: false,
}
.run();
}
@@ -1431,6 +1615,7 @@ async fn test_decimal_256() {
expected_null_counts: UInt64Array::from(vec![0, 0, 0]),
expected_row_counts: UInt64Array::from(vec![5, 5, 5]),
column_name: "decimal256_col",
+ test_data_page_statistics: false,
}
.run();
}
@@ -1450,6 +1635,7 @@ async fn test_dictionary() {
expected_null_counts: UInt64Array::from(vec![1, 0]),
expected_row_counts: UInt64Array::from(vec![5, 2]),
column_name: "string_dict_i8",
+ test_data_page_statistics: false,
}
.run();
@@ -1460,6 +1646,7 @@ async fn test_dictionary() {
expected_null_counts: UInt64Array::from(vec![1, 0]),
expected_row_counts: UInt64Array::from(vec![5, 2]),
column_name: "string_dict_i32",
+ test_data_page_statistics: false,
}
.run();
@@ -1470,6 +1657,7 @@ async fn test_dictionary() {
expected_null_counts: UInt64Array::from(vec![1, 0]),
expected_row_counts: UInt64Array::from(vec![5, 2]),
column_name: "int_dict_i8",
+ test_data_page_statistics: false,
}
.run();
}
@@ -1507,6 +1695,7 @@ async fn test_byte() {
expected_null_counts: UInt64Array::from(vec![0, 0, 0]),
expected_row_counts: UInt64Array::from(vec![5, 5, 5]),
column_name: "name",
+ test_data_page_statistics: false,
}
.run();
@@ -1526,6 +1715,7 @@ async fn test_byte() {
expected_null_counts: UInt64Array::from(vec![0, 0, 0]),
expected_row_counts: UInt64Array::from(vec![5, 5, 5]),
column_name: "service_string",
+ test_data_page_statistics: false,
}
.run();
@@ -1544,6 +1734,7 @@ async fn test_byte() {
expected_null_counts: UInt64Array::from(vec![0, 0, 0]),
expected_row_counts: UInt64Array::from(vec![5, 5, 5]),
column_name: "service_binary",
+ test_data_page_statistics: false,
}
.run();
@@ -1564,6 +1755,7 @@ async fn test_byte() {
expected_null_counts: UInt64Array::from(vec![0, 0, 0]),
expected_row_counts: UInt64Array::from(vec![5, 5, 5]),
column_name: "service_fixedsize",
+ test_data_page_statistics: false,
}
.run();
@@ -1584,6 +1776,7 @@ async fn test_byte() {
expected_null_counts: UInt64Array::from(vec![0, 0, 0]),
expected_row_counts: UInt64Array::from(vec![5, 5, 5]),
column_name: "service_large_binary",
+ test_data_page_statistics: false,
}
.run();
}
@@ -1616,6 +1809,7 @@ async fn test_period_in_column_names() {
expected_null_counts: UInt64Array::from(vec![0, 0, 0]),
expected_row_counts: UInt64Array::from(vec![5, 5, 5]),
column_name: "name",
+ test_data_page_statistics: false,
}
.run();
@@ -1629,6 +1823,7 @@ async fn test_period_in_column_names() {
expected_null_counts: UInt64Array::from(vec![0, 0, 0]),
expected_row_counts: UInt64Array::from(vec![5, 5, 5]),
column_name: "service.name",
+ test_data_page_statistics: false,
}
.run();
}
@@ -1652,6 +1847,7 @@ async fn test_boolean() {
expected_null_counts: UInt64Array::from(vec![1, 0]),
expected_row_counts: UInt64Array::from(vec![5, 5]),
column_name: "bool",
+ test_data_page_statistics: false,
}
.run();
}
@@ -1678,6 +1874,7 @@ async fn test_struct() {
expected_null_counts: UInt64Array::from(vec![0]),
expected_row_counts: UInt64Array::from(vec![3]),
column_name: "struct",
+ test_data_page_statistics: false,
}
.run();
}
@@ -1700,6 +1897,7 @@ async fn test_utf8() {
expected_null_counts: UInt64Array::from(vec![1, 0]),
expected_row_counts: UInt64Array::from(vec![5, 5]),
column_name: "utf8",
+ test_data_page_statistics: false,
}
.run();
@@ -1711,6 +1909,7 @@ async fn test_utf8() {
expected_null_counts: UInt64Array::from(vec![1, 0]),
expected_row_counts: UInt64Array::from(vec![5, 5]),
column_name: "large_utf8",
+ test_data_page_statistics: false,
}
.run();
}
@@ -1719,9 +1918,15 @@ async fn test_utf8() {
#[tokio::test]
async fn test_missing_statistics() {
- let row_per_group = 5;
- let reader =
- parquet_file_one_column_stats(0, 4, 7, row_per_group,
EnabledStatistics::None);
+ let reader = Int64Case {
+ null_values: 0,
+ no_null_values_start: 4,
+ no_null_values_end: 7,
+ row_per_group: 5,
+ enable_stats: Some(EnabledStatistics::None),
+ ..Default::default()
+ }
+ .build();
Test {
reader: &reader,
@@ -1730,6 +1935,7 @@ async fn test_missing_statistics() {
expected_null_counts: UInt64Array::from(vec![None]),
expected_row_counts: UInt64Array::from(vec![3]), // stil has row count
statistics
column_name: "i64",
+ test_data_page_statistics: false,
}
.run();
}
@@ -1751,6 +1957,7 @@ async fn test_column_not_found() {
expected_null_counts: UInt64Array::from(vec![2, 2]),
expected_row_counts: UInt64Array::from(vec![13, 7]),
column_name: "not_a_column",
+ test_data_page_statistics: false,
}
.run_col_not_found();
}
diff --git a/datafusion/core/tests/parquet/mod.rs
b/datafusion/core/tests/parquet/mod.rs
index 9546ab30c9..0434a271c3 100644
--- a/datafusion/core/tests/parquet/mod.rs
+++ b/datafusion/core/tests/parquet/mod.rs
@@ -43,7 +43,7 @@ use datafusion::{
use datafusion_expr::{Expr, LogicalPlan, LogicalPlanBuilder};
use half::f16;
use parquet::arrow::ArrowWriter;
-use parquet::file::properties::WriterProperties;
+use parquet::file::properties::{EnabledStatistics, WriterProperties};
use std::sync::Arc;
use tempfile::NamedTempFile;
@@ -1349,6 +1349,7 @@ async fn make_test_file_rg(scenario: Scenario,
row_per_group: usize) -> NamedTem
let props = WriterProperties::builder()
.set_max_row_group_size(row_per_group)
.set_bloom_filter_enabled(true)
+ .set_statistics_enabled(EnabledStatistics::Page)
.build();
let batches = create_data_batch(scenario);
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]