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/arrow-rs.git
The following commit(s) were added to refs/heads/main by this push:
new 84688d6d81 test(arrow-select): additional tests for inline-view filter
fast path (tests for #9755) (#10054)
84688d6d81 is described below
commit 84688d6d81a9adac624c8470cc3359308c9c6c57
Author: Andrew Lamb <[email protected]>
AuthorDate: Tue Jun 23 14:13:26 2026 -0400
test(arrow-select): additional tests for inline-view filter fast path
(tests for #9755) (#10054)
# Which issue does this PR close?
- Follow-up / test coverage for #9755
# Rationale for this change
While reviewing #9755 I found, via `cargo llvm-cov`, there are some
uncovered code paths (for very sparse filters as well as for outputting
intermediate blocks)
# What changes are included in this PR?
* Add tests with 5% selectivity filters
* Add test case whose buffered rows cross `target_batch_size` forcing a
flush
# Are there any user-facing changes?
No. This is test only
---
arrow-select/src/coalesce.rs | 131 +++++++++++++++++++++++++++++++++++++++----
1 file changed, 119 insertions(+), 12 deletions(-)
diff --git a/arrow-select/src/coalesce.rs b/arrow-select/src/coalesce.rs
index 0d5199941f..0f1086be82 100644
--- a/arrow-select/src/coalesce.rs
+++ b/arrow-select/src/coalesce.rs
@@ -1458,28 +1458,101 @@ mod tests {
}
#[test]
- fn test_mixed_boolean_inline_string_view_filtered() {
- let bool_values = BooleanArray::from_iter((0..1000).map(|v| Some(v % 3
== 0)));
+ fn test_inline_binary_view_sparse() {
+ // All-inline binary views (<=12 bytes) + 5% selectivity
+ let values: Vec<Option<&[u8]>> = vec![Some(b"foo"), None,
Some(b"barbaz")];
+ let binary_view =
+
BinaryViewArray::from_iter(std::iter::repeat(values.iter()).flatten().take(1000));
+ let batch =
+ RecordBatch::try_from_iter(vec![("c0", Arc::new(binary_view) as
ArrayRef)]).unwrap();
+ let filter = very_sparse_filter(1000);
+
+ Test::new("inline_binary_view_sparse")
+ .with_batch(batch.clone())
+ .with_filter(filter.clone())
+ .with_batch(batch)
+ .with_filter(filter)
+ .with_batch_size(1024)
+ .with_expected_output_sizes(vec![100])
+ .run();
+ }
+
+ #[test]
+ fn test_inline_string_view_sparse() {
+ let values: Vec<Option<&str>> = vec![Some("foo"), None,
Some("barbaz")];
+ let string_view =
+
StringViewArray::from_iter(std::iter::repeat(values.iter()).flatten().take(1000));
+ let batch =
+ RecordBatch::try_from_iter(vec![("c0", Arc::new(string_view) as
ArrayRef)]).unwrap();
+ let filter = very_sparse_filter(1000);
+
+ Test::new("inline_string_view_sparse")
+ .with_batch(batch.clone())
+ .with_filter(filter.clone())
+ .with_batch(batch)
+ .with_filter(filter)
+ .with_batch_size(1024)
+ .with_expected_output_sizes(vec![100])
+ .run();
+ }
+
+ #[test]
+ fn test_inline_mixed_sparse() {
+ // Mixing primitives with inline views exercises materialized `Indices`
+ // selection for both the primitive and the byte-view.
+ let int_values =
+ Int32Array::from_iter((0..1000).map(|v| if v % 5 == 0 { None }
else { Some(v) }));
+ let float_values =
arrow_array::Float64Array::from_iter((0..1000).map(|v| Some(v as f64)));
let string_values: Vec<Option<&str>> = vec![Some("foo"), None,
Some("barbaz")];
let string_view = StringViewArray::from_iter(
std::iter::repeat(string_values.iter()).flatten().take(1000),
);
+ let binary_values: Vec<Option<&[u8]>> = vec![Some(b"x"), None,
Some(b"abcdef")];
+ let binary_view = BinaryViewArray::from_iter(
+ std::iter::repeat(binary_values.iter()).flatten().take(1000),
+ );
let batch = RecordBatch::try_from_iter(vec![
- ("b", Arc::new(bool_values) as ArrayRef),
+ ("i", Arc::new(int_values) as ArrayRef),
+ ("f", Arc::new(float_values) as ArrayRef),
("s", Arc::new(string_view) as ArrayRef),
+ ("b", Arc::new(binary_view) as ArrayRef),
])
.unwrap();
+ let filter = very_sparse_filter(1000);
- let filter = sparse_filter(1000);
-
- Test::new("coalesce_mixed_boolean_inline_string_view_filtered")
+ Test::new("inline_mixed_sparse")
.with_batch(batch.clone())
.with_filter(filter.clone())
.with_batch(batch)
.with_filter(filter)
- .with_batch_size(300)
- .with_expected_output_sizes(vec![250])
+ .with_batch_size(1024)
+ .with_expected_output_sizes(vec![100])
+ .run();
+ }
+
+ #[test]
+ fn test_inline_crosses_target_batch_size() {
+ // Each 1000-row batch selects 50 rows; target_batch_size 100
+ // exercises intermediate batch flushing batch (not just the final
+ // flush).
+ let values: Vec<Option<&[u8]>> = vec![Some(b"foo"), None,
Some(b"barbaz")];
+ let make_batch = || {
+ let binary_view =
+
BinaryViewArray::from_iter(std::iter::repeat(values.iter()).flatten().take(1000));
+ RecordBatch::try_from_iter(vec![("c0", Arc::new(binary_view) as
ArrayRef)]).unwrap()
+ };
+ let filter = very_sparse_filter(1000);
+
+ Test::new("inline_crosses_target_batch_size")
+ .with_batch(make_batch())
+ .with_filter(filter.clone())
+ .with_batch(make_batch())
+ .with_filter(filter.clone())
+ .with_batch(make_batch())
+ .with_filter(filter)
+ .with_batch_size(100)
+ .with_expected_output_sizes(vec![100, 50])
.run();
}
@@ -1501,6 +1574,32 @@ mod tests {
);
}
+ #[test]
+ fn test_mixed_boolean_inline_string_view_filtered() {
+ let bool_values = BooleanArray::from_iter((0..1000).map(|v| Some(v % 3
== 0)));
+ let string_values: Vec<Option<&str>> = vec![Some("foo"), None,
Some("barbaz")];
+ let string_view = StringViewArray::from_iter(
+ std::iter::repeat(string_values.iter()).flatten().take(1000),
+ );
+
+ let batch = RecordBatch::try_from_iter(vec![
+ ("b", Arc::new(bool_values) as ArrayRef),
+ ("s", Arc::new(string_view) as ArrayRef),
+ ])
+ .unwrap();
+
+ let filter = sparse_filter(1000);
+
+ Test::new("coalesce_mixed_boolean_inline_string_view_filtered")
+ .with_batch(batch.clone())
+ .with_filter(filter.clone())
+ .with_batch(batch)
+ .with_filter(filter)
+ .with_batch_size(300)
+ .with_expected_output_sizes(vec![250])
+ .run();
+ }
+
#[test]
fn test_filter_fast_path_schema_capability() {
let supported = Arc::new(Schema::new(vec![
@@ -2012,10 +2111,6 @@ mod tests {
}
}
- fn sparse_filter(len: usize) -> BooleanArray {
- BooleanArray::from_iter((0..len).map(|idx| Some(idx % 8 == 0)))
- }
-
/// Returns the named column as a StringViewArray
fn col_as_string_view<'b>(name: &str, batch: &'b RecordBatch) -> &'b
StringViewArray {
batch
@@ -2025,6 +2120,18 @@ mod tests {
.expect("column is not a string view")
}
+ /// Filter that selects 12.5% of the rows (`idx % 8 == 0`)
+ fn sparse_filter(len: usize) -> BooleanArray {
+ BooleanArray::from_iter((0..len).map(|idx| Some(idx % 8 == 0)))
+ }
+
+ /// Filter that selects 5% of the rows (`idx % 20 == 0`)
+ ///
+ /// Different code paths are taken for very sparse filters
+ fn very_sparse_filter(len: usize) -> BooleanArray {
+ BooleanArray::from_iter((0..len).map(|idx| Some(idx % 20 == 0)))
+ }
+
/// Normalize the `RecordBatch` so that the memory layout is consistent
/// (e.g. StringArray is compacted).
fn normalize_batch(batch: RecordBatch) -> RecordBatch {