use crate::io::{
LogEntry, OperationLog, TestParquetFile, filter_a_175_b_625, filter_b_575_625, filter_b_false,
test_file, test_options,
};
use bytes::Bytes;
use parquet::arrow::ProjectionMask;
use parquet::arrow::arrow_reader::{
ArrowReaderOptions, ParquetRecordBatchReaderBuilder, RowSelection, RowSelector,
};
use parquet::file::metadata::PageIndexPolicy;
use parquet::file::reader::{ChunkReader, Length};
use std::io::Read;
use std::sync::Arc;
#[test]
fn test_read_entire_file() {
let test_file = test_file();
let builder = sync_builder(&test_file, test_options());
insta::assert_debug_snapshot!(run(&test_file, builder),
@r#"
[
"Footer: 8 bytes",
"Metadata: 1162",
"UNKNOWN: 22230..22877 (maybe Page Index)",
"Event: Builder Configured",
"Event: Reader Built",
"Row Group 0, column 'a': DictionaryPage (1617 bytes, 1 requests) [data]",
"Row Group 0, column 'a': DataPage(0) (113 bytes , 1 requests) [data]",
"Row Group 0, column 'a': DataPage(1) (126 bytes , 1 requests) [data]",
"Row Group 1, column 'a': DictionaryPage (1617 bytes, 1 requests) [data]",
"Row Group 1, column 'a': DataPage(0) (113 bytes , 1 requests) [data]",
"Row Group 1, column 'a': DataPage(1) (126 bytes , 1 requests) [data]",
"Row Group 0, column 'b': DictionaryPage (1617 bytes, 1 requests) [data]",
"Row Group 0, column 'b': DataPage(0) (113 bytes , 1 requests) [data]",
"Row Group 0, column 'b': DataPage(1) (126 bytes , 1 requests) [data]",
"Row Group 1, column 'b': DictionaryPage (1617 bytes, 1 requests) [data]",
"Row Group 1, column 'b': DataPage(0) (113 bytes , 1 requests) [data]",
"Row Group 1, column 'b': DataPage(1) (126 bytes , 1 requests) [data]",
"Row Group 0, column 'c': DictionaryPage (7107 bytes, 1 requests) [data]",
"Row Group 0, column 'c': DataPage(0) (113 bytes , 1 requests) [data]",
"Row Group 0, column 'c': DataPage(1) (126 bytes , 1 requests) [data]",
"Row Group 1, column 'c': DictionaryPage (7217 bytes, 1 requests) [data]",
"Row Group 1, column 'c': DataPage(0) (113 bytes , 1 requests) [data]",
"Row Group 1, column 'c': DataPage(1) (126 bytes , 1 requests) [data]",
]
"#);
}
#[test]
fn test_read_single_group() {
let test_file = test_file();
let builder = sync_builder(&test_file, test_options()).with_row_groups(vec![1]);
insta::assert_debug_snapshot!(run(&test_file, builder),
@r#"
[
"Footer: 8 bytes",
"Metadata: 1162",
"UNKNOWN: 22230..22877 (maybe Page Index)",
"Event: Builder Configured",
"Event: Reader Built",
"Row Group 1, column 'a': DictionaryPage (1617 bytes, 1 requests) [data]",
"Row Group 1, column 'a': DataPage(0) (113 bytes , 1 requests) [data]",
"Row Group 1, column 'a': DataPage(1) (126 bytes , 1 requests) [data]",
"Row Group 1, column 'b': DictionaryPage (1617 bytes, 1 requests) [data]",
"Row Group 1, column 'b': DataPage(0) (113 bytes , 1 requests) [data]",
"Row Group 1, column 'b': DataPage(1) (126 bytes , 1 requests) [data]",
"Row Group 1, column 'c': DictionaryPage (7217 bytes, 1 requests) [data]",
"Row Group 1, column 'c': DataPage(0) (113 bytes , 1 requests) [data]",
"Row Group 1, column 'c': DataPage(1) (126 bytes , 1 requests) [data]",
]
"#);
}
#[test]
fn test_read_single_column() {
let test_file = test_file();
let builder = sync_builder(&test_file, test_options());
let schema_descr = builder.metadata().file_metadata().schema_descr_ptr();
let builder = builder.with_projection(ProjectionMask::columns(&schema_descr, ["b"]));
insta::assert_debug_snapshot!(run(&test_file, builder),
@r#"
[
"Footer: 8 bytes",
"Metadata: 1162",
"UNKNOWN: 22230..22877 (maybe Page Index)",
"Event: Builder Configured",
"Event: Reader Built",
"Row Group 0, column 'b': DictionaryPage (1617 bytes, 1 requests) [data]",
"Row Group 0, column 'b': DataPage(0) (113 bytes , 1 requests) [data]",
"Row Group 0, column 'b': DataPage(1) (126 bytes , 1 requests) [data]",
"Row Group 1, column 'b': DictionaryPage (1617 bytes, 1 requests) [data]",
"Row Group 1, column 'b': DataPage(0) (113 bytes , 1 requests) [data]",
"Row Group 1, column 'b': DataPage(1) (126 bytes , 1 requests) [data]",
]
"#);
}
#[test]
fn test_read_single_column_no_page_index() {
let test_file = test_file();
let options = test_options().with_page_index_policy(PageIndexPolicy::from(false));
let builder = sync_builder(&test_file, options);
let schema_descr = builder.metadata().file_metadata().schema_descr_ptr();
let builder = builder.with_projection(ProjectionMask::columns(&schema_descr, ["b"]));
insta::assert_debug_snapshot!(run(&test_file, builder),
@r#"
[
"Footer: 8 bytes",
"Metadata: 1162",
"Event: Builder Configured",
"Event: Reader Built",
"Row Group 0, column 'b': DictionaryPage (17 bytes , 17 requests) [header]",
"Row Group 0, column 'b': DictionaryPage (1600 bytes, 1 requests) [data]",
"Row Group 0, column 'b': DataPage(0) (20 bytes , 20 requests) [header]",
"Row Group 0, column 'b': DataPage(0) (93 bytes , 1 requests) [data]",
"Row Group 0, column 'b': DataPage(1) (20 bytes , 20 requests) [header]",
"Row Group 0, column 'b': DataPage(1) (106 bytes , 1 requests) [data]",
"Row Group 1, column 'b': DictionaryPage (17 bytes , 17 requests) [header]",
"Row Group 1, column 'b': DictionaryPage (1600 bytes, 1 requests) [data]",
"Row Group 1, column 'b': DataPage(0) (20 bytes , 20 requests) [header]",
"Row Group 1, column 'b': DataPage(0) (93 bytes , 1 requests) [data]",
"Row Group 1, column 'b': DataPage(1) (20 bytes , 20 requests) [header]",
"Row Group 1, column 'b': DataPage(1) (106 bytes , 1 requests) [data]",
]
"#);
}
#[test]
fn test_read_row_selection() {
let test_file = test_file();
let builder = sync_builder(&test_file, test_options());
let schema_descr = builder.metadata().file_metadata().schema_descr_ptr();
let builder = builder
.with_projection(
ProjectionMask::columns(&schema_descr, ["a", "b"]),
)
.with_row_selection(RowSelection::from(vec![
RowSelector::skip(175),
RowSelector::select(50),
]));
insta::assert_debug_snapshot!(run(&test_file, builder),
@r#"
[
"Footer: 8 bytes",
"Metadata: 1162",
"UNKNOWN: 22230..22877 (maybe Page Index)",
"Event: Builder Configured",
"Event: Reader Built",
"Row Group 0, column 'a': DictionaryPage (1617 bytes, 1 requests) [data]",
"Row Group 0, column 'a': DataPage(1) (126 bytes , 1 requests) [data]",
"Row Group 0, column 'b': DictionaryPage (1617 bytes, 1 requests) [data]",
"Row Group 0, column 'b': DataPage(1) (126 bytes , 1 requests) [data]",
"Row Group 1, column 'a': DictionaryPage (1617 bytes, 1 requests) [data]",
"Row Group 1, column 'a': DataPage(0) (113 bytes , 1 requests) [data]",
"Row Group 1, column 'b': DictionaryPage (1617 bytes, 1 requests) [data]",
"Row Group 1, column 'b': DataPage(0) (113 bytes , 1 requests) [data]",
]
"#);
}
#[test]
fn test_read_limit() {
let test_file = test_file();
let builder = sync_builder(&test_file, test_options());
let schema_descr = builder.metadata().file_metadata().schema_descr_ptr();
let builder = builder
.with_projection(ProjectionMask::columns(&schema_descr, ["a"]))
.with_limit(125);
insta::assert_debug_snapshot!(run(&test_file, builder),
@r#"
[
"Footer: 8 bytes",
"Metadata: 1162",
"UNKNOWN: 22230..22877 (maybe Page Index)",
"Event: Builder Configured",
"Event: Reader Built",
"Row Group 0, column 'a': DictionaryPage (1617 bytes, 1 requests) [data]",
"Row Group 0, column 'a': DataPage(0) (113 bytes , 1 requests) [data]",
"Row Group 0, column 'a': DataPage(1) (126 bytes , 1 requests) [data]",
]
"#);
}
#[test]
fn test_read_single_row_filter() {
let test_file = test_file();
let builder = sync_builder(&test_file, test_options());
let schema_descr = builder.metadata().file_metadata().schema_descr_ptr();
let builder = builder
.with_projection(
ProjectionMask::columns(&schema_descr, ["a", "b"]),
)
.with_row_filter(filter_b_575_625(&schema_descr));
insta::assert_debug_snapshot!(run(&test_file, builder),
@r#"
[
"Footer: 8 bytes",
"Metadata: 1162",
"UNKNOWN: 22230..22877 (maybe Page Index)",
"Event: Builder Configured",
"Row Group 0, column 'b': DictionaryPage (1617 bytes, 1 requests) [data]",
"Row Group 0, column 'b': DataPage(0) (113 bytes , 1 requests) [data]",
"Row Group 0, column 'b': DataPage(1) (126 bytes , 1 requests) [data]",
"Row Group 1, column 'b': DictionaryPage (1617 bytes, 1 requests) [data]",
"Row Group 1, column 'b': DataPage(0) (113 bytes , 1 requests) [data]",
"Row Group 1, column 'b': DataPage(1) (126 bytes , 1 requests) [data]",
"Event: Reader Built",
"Row Group 0, column 'a': DictionaryPage (1617 bytes, 1 requests) [data]",
"Row Group 0, column 'a': DataPage(1) (126 bytes , 1 requests) [data]",
"Row Group 0, column 'b': DictionaryPage (1617 bytes, 1 requests) [data]",
"Row Group 0, column 'b': DataPage(1) (126 bytes , 1 requests) [data]",
"Row Group 1, column 'a': DictionaryPage (1617 bytes, 1 requests) [data]",
"Row Group 1, column 'a': DataPage(0) (113 bytes , 1 requests) [data]",
"Row Group 1, column 'b': DictionaryPage (1617 bytes, 1 requests) [data]",
"Row Group 1, column 'b': DataPage(0) (113 bytes , 1 requests) [data]",
]
"#);
}
#[test]
fn test_read_multiple_row_filter() {
let test_file = test_file();
let builder = sync_builder(&test_file, test_options());
let schema_descr = builder.metadata().file_metadata().schema_descr_ptr();
let builder = builder
.with_projection(
ProjectionMask::columns(&schema_descr, ["c"]), )
.with_row_filter(filter_a_175_b_625(&schema_descr));
insta::assert_debug_snapshot!(run(&test_file, builder),
@r#"
[
"Footer: 8 bytes",
"Metadata: 1162",
"UNKNOWN: 22230..22877 (maybe Page Index)",
"Event: Builder Configured",
"Row Group 0, column 'a': DictionaryPage (1617 bytes, 1 requests) [data]",
"Row Group 0, column 'a': DataPage(0) (113 bytes , 1 requests) [data]",
"Row Group 0, column 'a': DataPage(1) (126 bytes , 1 requests) [data]",
"Row Group 1, column 'a': DictionaryPage (1617 bytes, 1 requests) [data]",
"Row Group 1, column 'a': DataPage(0) (113 bytes , 1 requests) [data]",
"Row Group 1, column 'a': DataPage(1) (126 bytes , 1 requests) [data]",
"Row Group 0, column 'b': DictionaryPage (1617 bytes, 1 requests) [data]",
"Row Group 0, column 'b': DataPage(1) (126 bytes , 1 requests) [data]",
"Row Group 1, column 'b': DictionaryPage (1617 bytes, 1 requests) [data]",
"Row Group 1, column 'b': DataPage(0) (113 bytes , 1 requests) [data]",
"Row Group 1, column 'b': DataPage(1) (126 bytes , 1 requests) [data]",
"Event: Reader Built",
"Row Group 0, column 'c': DictionaryPage (7107 bytes, 1 requests) [data]",
"Row Group 0, column 'c': DataPage(1) (126 bytes , 1 requests) [data]",
"Row Group 1, column 'c': DictionaryPage (7217 bytes, 1 requests) [data]",
"Row Group 1, column 'c': DataPage(0) (113 bytes , 1 requests) [data]",
]
"#);
}
#[test]
fn test_read_single_row_filter_all() {
let test_file = test_file();
let builder = sync_builder(&test_file, test_options());
let schema_descr = builder.metadata().file_metadata().schema_descr_ptr();
let builder = builder
.with_projection(ProjectionMask::columns(&schema_descr, ["a", "b"]))
.with_row_filter(filter_b_false(&schema_descr));
insta::assert_debug_snapshot!(run(&test_file, builder),
@r#"
[
"Footer: 8 bytes",
"Metadata: 1162",
"UNKNOWN: 22230..22877 (maybe Page Index)",
"Event: Builder Configured",
"Row Group 0, column 'b': DictionaryPage (1617 bytes, 1 requests) [data]",
"Row Group 0, column 'b': DataPage(0) (113 bytes , 1 requests) [data]",
"Row Group 0, column 'b': DataPage(1) (126 bytes , 1 requests) [data]",
"Row Group 1, column 'b': DictionaryPage (1617 bytes, 1 requests) [data]",
"Row Group 1, column 'b': DataPage(0) (113 bytes , 1 requests) [data]",
"Row Group 1, column 'b': DataPage(1) (126 bytes , 1 requests) [data]",
"Event: Reader Built",
]
"#);
}
fn sync_builder(
test_file: &TestParquetFile,
options: ArrowReaderOptions,
) -> ParquetRecordBatchReaderBuilder<RecordingChunkReader> {
let reader = RecordingChunkReader {
inner: test_file.bytes().clone(),
ops: Arc::clone(test_file.ops()),
};
ParquetRecordBatchReaderBuilder::try_new_with_options(reader, options)
.expect("ParquetRecordBatchReaderBuilder")
}
fn run(
test_file: &TestParquetFile,
builder: ParquetRecordBatchReaderBuilder<RecordingChunkReader>,
) -> Vec<String> {
let ops = test_file.ops();
ops.add_entry(LogEntry::event("Builder Configured"));
let reader = builder.build().unwrap();
ops.add_entry(LogEntry::event("Reader Built"));
for batch in reader {
match batch {
Ok(_) => {}
Err(e) => panic!("Error reading batch: {e}"),
}
}
ops.snapshot()
}
struct RecordingChunkReader {
inner: Bytes,
ops: Arc<OperationLog>,
}
impl Length for RecordingChunkReader {
fn len(&self) -> u64 {
self.inner.len() as u64
}
}
impl ChunkReader for RecordingChunkReader {
type T = RecordingStdIoReader;
fn get_read(&self, start: u64) -> parquet::errors::Result<Self::T> {
let reader = RecordingStdIoReader {
start: start as usize,
inner: self.inner.clone(),
ops: Arc::clone(&self.ops),
};
Ok(reader)
}
fn get_bytes(&self, start: u64, length: usize) -> parquet::errors::Result<Bytes> {
let start = start as usize;
let range = start..start + length;
self.ops.add_entry_for_range(&range);
Ok(self.inner.slice(start..start + length))
}
}
struct RecordingStdIoReader {
start: usize,
inner: Bytes,
ops: Arc<OperationLog>,
}
impl Read for RecordingStdIoReader {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
let remain = self.inner.len() - self.start;
let start = self.start;
let read_length = buf.len().min(remain);
let read_range = start..start + read_length;
self.ops.add_entry_for_range(&read_range);
buf.copy_from_slice(self.inner.slice(read_range).as_ref());
self.start += read_length;
Ok(read_length)
}
}