use super::*;
fn get_expected_batches(
column: &RecordBatch,
selection: &RowSelection,
batch_size: usize,
) -> Vec<RecordBatch> {
let mut expected_batches = vec![];
let mut selection: VecDeque<_> = selection.clone().into();
let mut row_offset = 0;
let mut last_start = None;
while row_offset < column.num_rows() && !selection.is_empty() {
let mut batch_remaining = batch_size.min(column.num_rows() - row_offset);
while batch_remaining > 0 && !selection.is_empty() {
let (to_read, skip) = match selection.front_mut() {
Some(selection) if selection.row_count > batch_remaining => {
selection.row_count -= batch_remaining;
(batch_remaining, selection.skip)
}
Some(_) => {
let select = selection.pop_front().unwrap();
(select.row_count, select.skip)
}
None => break,
};
batch_remaining -= to_read;
match skip {
true => {
if let Some(last_start) = last_start.take() {
expected_batches.push(column.slice(last_start, row_offset - last_start))
}
}
false => {
last_start.get_or_insert(row_offset);
}
}
row_offset += to_read;
}
}
if let Some(last_start) = last_start.take() {
expected_batches.push(column.slice(last_start, row_offset - last_start))
}
for batch in &expected_batches[..expected_batches.len() - 1] {
assert_eq!(batch.num_rows(), batch_size);
}
expected_batches
}
#[test]
#[cfg_attr(miri, ignore)] fn test_scan_row_with_selection() {
let testdata = arrow::util::test_util::parquet_test_data();
let path = format!("{testdata}/alltypes_tiny_pages_plain.parquet");
let test_file = File::open(&path).unwrap();
let mut serial_reader =
ParquetRecordBatchReader::try_new(File::open(&path).unwrap(), 7300).unwrap();
let data = serial_reader.next().unwrap().unwrap();
let do_test = |batch_size: usize, selection_len: usize| {
for skip_first in [false, true] {
let selections = create_test_selection(batch_size, data.num_rows(), skip_first).0;
let expected = get_expected_batches(&data, &selections, batch_size);
let skip_reader = create_skip_reader(&test_file, batch_size, selections);
assert_eq!(
skip_reader.collect::<Result<Vec<_>, _>>().unwrap(),
expected,
"batch_size: {batch_size}, selection_len: {selection_len}, skip_first: {skip_first}"
);
}
};
do_test(1000, 1000);
do_test(20, 20);
do_test(20, 5);
do_test(20, 5);
fn create_skip_reader(
test_file: &File,
batch_size: usize,
selections: RowSelection,
) -> ParquetRecordBatchReader {
let options = ArrowReaderOptions::new().with_page_index_policy(PageIndexPolicy::Required);
let file = test_file.try_clone().unwrap();
ParquetRecordBatchReaderBuilder::try_new_with_options(file, options)
.unwrap()
.with_batch_size(batch_size)
.with_row_selection(selections)
.build()
.unwrap()
}
}
#[test]
#[cfg(feature = "snap")]
fn test_read_nested_lists() {
let testdata = arrow::util::test_util::parquet_test_data();
let path = format!("{testdata}/nested_lists.snappy.parquet");
let file = File::open(path).unwrap();
let f = file.try_clone().unwrap();
let mut reader = ParquetRecordBatchReader::try_new(f, 60).unwrap();
let expected = reader.next().unwrap().unwrap();
assert_eq!(expected.num_rows(), 3);
let selection = RowSelection::from(vec![
RowSelector::skip(1),
RowSelector::select(1),
RowSelector::skip(1),
]);
let mut reader = ParquetRecordBatchReaderBuilder::try_new(file)
.unwrap()
.with_row_selection(selection)
.build()
.unwrap();
let actual = reader.next().unwrap().unwrap();
assert_eq!(actual.num_rows(), 1);
assert_eq!(actual.column(0), &expected.column(0).slice(1, 1));
}
#[test]
fn test_list_skip() {
let mut list = ListBuilder::new(Int32Builder::new());
list.append_value([Some(1), Some(2)]);
list.append_value([Some(3)]);
list.append_value([Some(4)]);
let list = list.finish();
let batch = RecordBatch::try_from_iter([("l", Arc::new(list) as _)]).unwrap();
let props = WriterProperties::builder()
.set_data_page_row_count_limit(1)
.set_write_batch_size(2)
.build();
let mut buffer = Vec::with_capacity(1024);
let mut writer = ArrowWriter::try_new(&mut buffer, batch.schema(), Some(props)).unwrap();
writer.write(&batch).unwrap();
writer.close().unwrap();
let selection = vec![RowSelector::skip(2), RowSelector::select(1)];
let mut reader = ParquetRecordBatchReaderBuilder::try_new(Bytes::from(buffer))
.unwrap()
.with_row_selection(selection.into())
.build()
.unwrap();
let out = reader.next().unwrap().unwrap();
assert_eq!(out.num_rows(), 1);
assert_eq!(out, batch.slice(2, 1));
}
#[test]
#[cfg_attr(miri, ignore)] fn test_list_selection() {
let schema = Arc::new(Schema::new(vec![Field::new_list(
"list",
Field::new_list_field(ArrowDataType::Utf8, true),
false,
)]));
let mut buf = Vec::with_capacity(1024);
let mut writer = ArrowWriter::try_new(&mut buf, schema.clone(), None).unwrap();
for i in 0..2 {
let mut list_a_builder = ListBuilder::new(StringBuilder::new());
for j in 0..1024 {
list_a_builder.values().append_value(format!("{i} {j}"));
list_a_builder.append(true);
}
let batch =
RecordBatch::try_new(schema.clone(), vec![Arc::new(list_a_builder.finish())]).unwrap();
writer.write(&batch).unwrap();
}
let _metadata = writer.close().unwrap();
let buf = Bytes::from(buf);
let reader = ParquetRecordBatchReaderBuilder::try_new(buf)
.unwrap()
.with_row_selection(RowSelection::from(vec![
RowSelector::skip(100),
RowSelector::select(924),
RowSelector::skip(100),
RowSelector::select(924),
]))
.build()
.unwrap();
let batches = reader.collect::<Result<Vec<_>, _>>().unwrap();
let batch = concat_batches(&schema, &batches).unwrap();
assert_eq!(batch.num_rows(), 924 * 2);
let list = batch.column(0).as_list::<i32>();
for w in list.value_offsets().windows(2) {
assert_eq!(w[0] + 1, w[1])
}
let mut values = list.values().as_string::<i32>().iter();
for i in 0..2 {
for j in 100..1024 {
let expected = format!("{i} {j}");
assert_eq!(values.next().unwrap().unwrap(), &expected);
}
}
}
#[test]
#[cfg_attr(miri, ignore)] fn test_list_selection_fuzz() {
let mut rng = rng();
let schema = Arc::new(Schema::new(vec![Field::new_list(
"list",
Field::new_list(
Field::LIST_FIELD_DEFAULT_NAME,
Field::new_list_field(ArrowDataType::Int32, true),
true,
),
true,
)]));
let mut buf = Vec::with_capacity(1024);
let mut writer = ArrowWriter::try_new(&mut buf, schema.clone(), None).unwrap();
let mut list_a_builder = ListBuilder::new(ListBuilder::new(Int32Builder::new()));
for _ in 0..2048 {
if rng.random_bool(0.2) {
list_a_builder.append(false);
continue;
}
let list_a_len = rng.random_range(0..10);
let list_b_builder = list_a_builder.values();
for _ in 0..list_a_len {
if rng.random_bool(0.2) {
list_b_builder.append(false);
continue;
}
let list_b_len = rng.random_range(0..10);
let int_builder = list_b_builder.values();
for _ in 0..list_b_len {
match rng.random_bool(0.2) {
true => int_builder.append_null(),
false => int_builder.append_value(rng.random()),
}
}
list_b_builder.append(true)
}
list_a_builder.append(true);
}
let array = Arc::new(list_a_builder.finish());
let batch = RecordBatch::try_new(schema, vec![array]).unwrap();
writer.write(&batch).unwrap();
let _metadata = writer.close().unwrap();
let buf = Bytes::from(buf);
let cases = [
vec![
RowSelector::skip(100),
RowSelector::select(924),
RowSelector::skip(100),
RowSelector::select(924),
],
vec![
RowSelector::select(924),
RowSelector::skip(100),
RowSelector::select(924),
RowSelector::skip(100),
],
vec![
RowSelector::skip(1023),
RowSelector::select(1),
RowSelector::skip(1023),
RowSelector::select(1),
],
vec![
RowSelector::select(1),
RowSelector::skip(1023),
RowSelector::select(1),
RowSelector::skip(1023),
],
];
for batch_size in [100, 1024, 2048] {
for selection in &cases {
let selection = RowSelection::from(selection.clone());
let reader = ParquetRecordBatchReaderBuilder::try_new(buf.clone())
.unwrap()
.with_row_selection(selection.clone())
.with_batch_size(batch_size)
.build()
.unwrap();
let batches = reader.collect::<Result<Vec<_>, _>>().unwrap();
let actual = concat_batches(batch.schema_ref(), &batches).unwrap();
assert_eq!(actual.num_rows(), selection.row_count());
let mut batch_offset = 0;
let mut actual_offset = 0;
for selector in selection.iter() {
if selector.skip {
batch_offset += selector.row_count;
continue;
}
assert_eq!(
batch.slice(batch_offset, selector.row_count),
actual.slice(actual_offset, selector.row_count)
);
batch_offset += selector.row_count;
actual_offset += selector.row_count;
}
}
}
}