Skip to content

Commit d3cad6e

Browse files
authored
[Parquet] Do not panic when trying to skip records in delta encoded files using non-standard block sizes (apache#9794)
# Which issue does this PR close? - Closes apache#9793. # Rationale for this change `DeltaBitPackDecoder::skip` uses some magic numbers when sizing the buffer used for skipping. Files that use non-standard miniblock sizes will cause `skip` to panic. # What changes are included in this PR? Check for non-standard miniblock sizes and return an error rather than a panic. Note: allocating a vec sized with `values_per_mini_block` resulted in a significant performance regression. # Are these changes tested? Yes, test with file with non-standard sizes is added to the arrow_reader tests. # Are there any user-facing changes? No.
1 parent 7ffcf0d commit d3cad6e

3 files changed

Lines changed: 46 additions & 5 deletions

File tree

parquet/src/encodings/decoding.rs

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -847,10 +847,21 @@ where
847847
self.values_left -= 1;
848848
}
849849

850-
let mini_block_batch_size = match T::T::PHYSICAL_TYPE {
851-
Type::INT32 => 32,
852-
Type::INT64 => 64,
853-
_ => unreachable!(),
850+
// See https://github.com/apache/arrow-rs/pull/9794.
851+
// The parquet spec actually allows for miniblock sizes other than 32 or 64, but
852+
// no current writers use anything else. Using values_per_mini_block directly
853+
// for the skip_buffer doesn't allow stack allocation and leads to a significant
854+
// drop in performance. We'll settle for erroring out here and come up with a
855+
// better fix if writers ever start getting creative with block sizes.
856+
let mini_block_batch_size = match self.values_per_mini_block {
857+
32 => 32,
858+
64 => 64,
859+
_ => {
860+
return Err(general_err!(
861+
"cannot skip miniblock of size {}",
862+
self.values_per_mini_block
863+
));
864+
}
854865
};
855866

856867
let mut skip_buffer = vec![T::T::default(); mini_block_batch_size];
@@ -863,10 +874,13 @@ where
863874
self.check_bit_width(bit_width)?;
864875
let mini_block_to_skip = self.mini_block_remaining.min(to_skip - skip);
865876

877+
// see commentary in self.get() above regarding optimizations
866878
let min_delta = self.min_delta.as_i64()?;
867879
if bit_width == 0 {
868880
// All remainders are zero: every delta equals min_delta exactly.
869881
// Advance last_value by n * min_delta with no bit reads.
882+
// When min_delta == 0 there is nothing to do: last_value is
883+
// unchanged and no bytes are consumed from the bit reader.
870884
if min_delta != 0 {
871885
let total = min_delta.wrapping_mul(mini_block_to_skip as i64);
872886
let step = T::T::from_i64(total)

parquet/tests/arrow_reader/bad_data.rs

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
//! Tests that reading invalid parquet files returns an error
1919
2020
use arrow::util::test_util::parquet_test_data;
21+
use bytes::Bytes;
2122
use parquet::arrow::arrow_reader::ArrowReaderBuilder;
2223
use parquet::errors::ParquetError;
2324
use std::collections::HashSet;
@@ -147,11 +148,37 @@ fn read_file(name: &str) -> Result<usize, ParquetError> {
147148
Ok(num_rows)
148149
}
149150

151+
#[test]
152+
fn non_standard_delta_blocks() {
153+
let file = Bytes::from_static(include_bytes!("bigdelta.parquet"));
154+
use parquet::arrow::arrow_reader::{RowSelection, RowSelector};
155+
156+
let selectors = vec![RowSelector::skip(1000), RowSelector::select(5)];
157+
158+
let selection: RowSelection = selectors.into();
159+
let reader = ArrowReaderBuilder::try_new(file)
160+
.unwrap()
161+
.with_row_selection(selection)
162+
.build()
163+
.unwrap();
164+
165+
if let Some(maybe_batch) = reader.into_iter().next() {
166+
// TODO: uncomment if we ever allow skipping miniblocks > 64 elements
167+
//let batch = maybe_batch.expect("skip should succeed");
168+
//assert_eq!(batch.num_rows(), 5);
169+
assert!(
170+
maybe_batch
171+
.unwrap_err()
172+
.to_string()
173+
.contains("cannot skip miniblock of size 128")
174+
);
175+
}
176+
}
177+
150178
#[cfg(feature = "async")]
151179
#[tokio::test]
152180
#[allow(deprecated)]
153181
async fn bad_metadata_err() {
154-
use bytes::Bytes;
155182
use parquet::file::metadata::ParquetMetaDataReader;
156183

157184
let metadata_buffer = Bytes::from_static(include_bytes!("bad_raw_metadata.bin"));
3.57 KB
Binary file not shown.

0 commit comments

Comments
 (0)