1use crate::arrow::ProjectionMask;
19use crate::arrow::arrow_reader::{RowSelection, RowSelectionPolicy};
20use crate::schema::types::SchemaDescriptor;
21use arrow_array::{Array, BooleanArray, RecordBatch};
22use arrow_buffer::BooleanBuffer;
23use arrow_schema::ArrowError;
24use arrow_select::filter::{SlicesIterator, filter_record_batch, prep_null_mask_filter};
25use std::fmt::{Debug, Formatter};
26
27pub trait ArrowPredicate: Send + 'static {
34 fn projection(&self) -> &ProjectionMask;
42
43 fn evaluate(&mut self, batch: RecordBatch) -> Result<BooleanArray, ArrowError>;
51}
52
53pub struct ArrowPredicateFn<F> {
111 f: F,
112 projection: ProjectionMask,
113}
114
115impl<F> ArrowPredicateFn<F>
116where
117 F: FnMut(RecordBatch) -> Result<BooleanArray, ArrowError> + Send + 'static,
118{
119 pub fn new(projection: ProjectionMask, f: F) -> Self {
122 Self { f, projection }
123 }
124}
125
126impl<F> ArrowPredicate for ArrowPredicateFn<F>
127where
128 F: FnMut(RecordBatch) -> Result<BooleanArray, ArrowError> + Send + 'static,
129{
130 fn projection(&self) -> &ProjectionMask {
131 &self.projection
132 }
133
134 fn evaluate(&mut self, batch: RecordBatch) -> Result<BooleanArray, ArrowError> {
135 (self.f)(batch)
136 }
137}
138
139pub struct RowFilter {
185 pub(crate) predicates: Vec<Box<dyn ArrowPredicate>>,
187}
188
189impl Debug for RowFilter {
190 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
191 write!(f, "RowFilter {{ {} predicates: }}", self.predicates.len())
192 }
193}
194
195impl RowFilter {
196 pub fn new(predicates: Vec<Box<dyn ArrowPredicate>>) -> Self {
198 Self { predicates }
199 }
200 pub fn predicates(&self) -> &Vec<Box<dyn ArrowPredicate>> {
202 &self.predicates
203 }
204 pub fn into_predicates(self) -> Vec<Box<dyn ArrowPredicate>> {
206 self.predicates
207 }
208
209 pub(crate) fn fuse_same_projection(self, parquet_schema: &SchemaDescriptor) -> Self {
212 let mut predicates: Vec<Box<dyn ArrowPredicate>> =
213 Vec::with_capacity(self.predicates.len());
214 let mut group: Vec<Box<dyn ArrowPredicate>> = Vec::new();
215 let mut flush_group = |group: &mut Vec<Box<dyn ArrowPredicate>>| {
216 if group.len() > 1 && can_fuse_projection(group[0].projection(), parquet_schema) {
217 let group = std::mem::take(group);
218 predicates.push(Box::new(FusedPredicate::new(group)));
219 } else {
220 predicates.append(group);
221 }
222 };
223
224 for predicate in self.predicates {
225 if group
226 .last()
227 .is_some_and(|last| last.projection() != predicate.projection())
228 {
229 flush_group(&mut group);
230 }
231 group.push(predicate);
232 }
233 flush_group(&mut group);
234
235 Self { predicates }
236 }
237}
238
239fn can_fuse_projection(projection: &ProjectionMask, parquet_schema: &SchemaDescriptor) -> bool {
241 let mut leaf_indices =
242 (0..parquet_schema.num_columns()).filter(|idx| projection.leaf_included(*idx));
243 let Some(leaf_idx) = leaf_indices.next() else {
244 return false;
245 };
246 if leaf_indices.next().is_some() {
247 return false;
248 }
249
250 let column = parquet_schema.column(leaf_idx);
251 column.path().parts().len() == 1 && column.max_rep_level() == 0
252}
253
254struct FusedPredicate {
258 predicates: Vec<Box<dyn ArrowPredicate>>,
260 composition_policy: RowSelectionPolicy,
264}
265
266impl FusedPredicate {
267 fn new(predicates: Vec<Box<dyn ArrowPredicate>>) -> Self {
270 debug_assert!(predicates.len() > 1);
271 debug_assert!(
272 predicates
273 .windows(2)
274 .all(|pair| pair[0].projection() == pair[1].projection())
275 );
276 Self {
277 predicates,
278 composition_policy: RowSelectionPolicy::default(),
279 }
280 }
281}
282
283impl Debug for FusedPredicate {
284 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
285 write!(
286 f,
287 "FusedPredicate {{ {} predicates }}",
288 self.predicates.len()
289 )
290 }
291}
292
293impl ArrowPredicate for FusedPredicate {
294 fn projection(&self) -> &ProjectionMask {
295 self.predicates[0].projection()
296 }
297
298 fn evaluate(&mut self, batch: RecordBatch) -> Result<BooleanArray, ArrowError> {
299 let num_rows = batch.num_rows();
300 let mut selection: Option<RowSelection> = None;
302 let mut filtered_batch = batch;
303 let last_predicate_idx = self.predicates.len() - 1;
304
305 for (idx, predicate) in self.predicates.iter_mut().enumerate() {
306 let filter = evaluate_predicate(predicate.as_mut(), filtered_batch.clone())?;
307 if idx == last_predicate_idx && selection.is_none() {
309 return Ok(filter);
310 }
311 let true_count = filter.true_count();
312 if true_count == 0 {
313 return Ok(BooleanArray::new(BooleanBuffer::new_unset(num_rows), None));
314 }
315 if true_count == filter.len() {
316 continue;
317 }
318
319 let predicate_selection = RowSelection::from_boolean_buffer(filter.values().clone());
320 selection = Some(match selection.take() {
321 Some(prev) => self
324 .composition_policy
325 .apply(prev)
326 .and_then(&predicate_selection),
327 None => predicate_selection,
328 });
329 if idx != last_predicate_idx {
330 filtered_batch = narrow_batch(&filtered_batch, &filter, true_count)?;
331 }
332 }
333
334 let mask = match selection {
335 Some(selection) => selection.into_boolean_buffer(),
336 None => BooleanBuffer::new_set(num_rows),
337 };
338 debug_assert_eq!(mask.len(), num_rows);
339 Ok(BooleanArray::new(mask, None))
340 }
341}
342
343fn evaluate_predicate(
345 predicate: &mut dyn ArrowPredicate,
346 batch: RecordBatch,
347) -> Result<BooleanArray, ArrowError> {
348 let input_rows = batch.num_rows();
349 let filter = predicate.evaluate(batch)?;
350 if filter.len() != input_rows {
351 return Err(ArrowError::InvalidArgumentError(format!(
352 "ArrowPredicate predicate returned {} rows, expected {input_rows}",
353 filter.len()
354 )));
355 }
356 Ok(match filter.null_count() {
357 0 => filter,
358 _ => prep_null_mask_filter(&filter),
359 })
360}
361
362fn narrow_batch(
367 batch: &RecordBatch,
368 filter: &BooleanArray,
369 true_count: usize,
370) -> Result<RecordBatch, ArrowError> {
371 let mut slices = SlicesIterator::new(filter);
372 let (start, end) = slices
373 .next()
374 .expect("a partially selected filter has a true slice");
375 if end - start == true_count {
376 Ok(batch.slice(start, end - start))
377 } else {
378 filter_record_batch(batch, filter)
379 }
380}
381
382#[cfg(test)]
383mod tests {
384 use super::*;
385 use crate::arrow::array_reader::ArrayReader;
386 use crate::arrow::array_reader::StructArrayReader;
387 use crate::arrow::array_reader::test_util::make_int32_page_reader;
388 use crate::arrow::arrow_reader::ReadPlanBuilder;
389 use crate::schema::parser::parse_message_type;
390 use arrow_array::Int32Array;
391 use arrow_array::cast::AsArray;
392 use arrow_array::types::Int32Type;
393 use arrow_schema::{DataType, Field, Fields, Schema};
394 use std::sync::Arc;
395
396 fn test_schema() -> SchemaDescriptor {
397 let schema = parse_message_type(
398 "message schema {
399 REQUIRED INT32 a;
400 REQUIRED INT32 b;
401 REQUIRED INT32 c;
402 REQUIRED GROUP nested { REQUIRED INT32 d; }
403 REPEATED INT32 e;
404 }",
405 )
406 .unwrap();
407 SchemaDescriptor::new(Arc::new(schema))
408 }
409
410 #[test]
411 fn can_fuse_projection_requires_one_top_level_leaf() {
412 let schema = test_schema();
413
414 let one_leaf = ProjectionMask::leaves(&schema, [1]);
415 let two_leaves = ProjectionMask::leaves(&schema, [0, 2]);
416 let nested_leaf = ProjectionMask::leaves(&schema, [3]);
417 let repeated_leaf = ProjectionMask::leaves(&schema, [4]);
418 assert!(can_fuse_projection(&one_leaf, &schema));
419 assert!(!can_fuse_projection(&two_leaves, &schema));
420 assert!(!can_fuse_projection(&nested_leaf, &schema));
421 assert!(!can_fuse_projection(&repeated_leaf, &schema));
422 assert!(!can_fuse_projection(
423 &ProjectionMask::none(schema.num_columns()),
424 &schema
425 ));
426 }
427
428 fn accept_all(projection: ProjectionMask) -> Box<dyn ArrowPredicate> {
429 Box::new(ArrowPredicateFn::new(projection, |batch| {
430 Ok(BooleanArray::from(vec![true; batch.num_rows()]))
431 }))
432 }
433
434 #[test]
435 fn fuse_same_projection_groups_consecutive_eligible_predicates() {
436 let schema = test_schema();
437 let a = ProjectionMask::leaves(&schema, [0]);
438 let b = ProjectionMask::leaves(&schema, [1]);
439 let ac = ProjectionMask::leaves(&schema, [0, 2]);
440 let nested = ProjectionMask::leaves(&schema, [3]);
441
442 let filter = RowFilter::new(vec![
443 accept_all(a.clone()),
445 accept_all(a.clone()),
446 accept_all(b.clone()),
448 accept_all(ac.clone()),
450 accept_all(ac.clone()),
451 accept_all(nested.clone()),
453 accept_all(nested.clone()),
454 accept_all(a.clone()),
456 accept_all(a.clone()),
457 accept_all(a.clone()),
458 ])
459 .fuse_same_projection(&schema);
460
461 let projections: Vec<_> = filter
462 .predicates()
463 .iter()
464 .map(|predicate| predicate.projection().clone())
465 .collect();
466 assert_eq!(
467 projections,
468 vec![a.clone(), b, ac.clone(), ac, nested.clone(), nested, a]
469 );
470 assert_eq!(format!("{filter:?}"), "RowFilter { 7 predicates: }");
471 }
472
473 #[test]
474 fn fuse_same_projection_keeps_single_predicates_and_empty_filters() {
475 let schema = test_schema();
476 let a = ProjectionMask::leaves(&schema, [0]);
477 let b = ProjectionMask::leaves(&schema, [1]);
478
479 let filter =
480 RowFilter::new(vec![accept_all(a), accept_all(b)]).fuse_same_projection(&schema);
481 assert_eq!(filter.predicates().len(), 2);
482
483 let filter = RowFilter::new(vec![]).fuse_same_projection(&schema);
484 assert!(filter.predicates().is_empty());
485 }
486
487 fn int_batch(values: impl IntoIterator<Item = i32>) -> RecordBatch {
488 let schema = Arc::new(Schema::new(vec![Field::new("v", DataType::Int32, false)]));
489 let values = Int32Array::from_iter_values(values);
490 RecordBatch::try_new(schema, vec![Arc::new(values)]).unwrap()
491 }
492
493 fn int_predicate(
496 precondition: impl Fn(i32) -> bool + Send + 'static,
497 f: impl Fn(i32) -> Option<bool> + Send + 'static,
498 ) -> Box<dyn ArrowPredicate> {
499 Box::new(ArrowPredicateFn::new(ProjectionMask::all(), move |batch| {
500 let values = batch.column(0).as_primitive::<Int32Type>();
501 assert!(
502 values.values().iter().all(|v| precondition(*v)),
503 "predicate saw rows rejected by a predecessor"
504 );
505 Ok(values.values().iter().map(|v| f(*v)).collect())
506 }))
507 }
508
509 fn even() -> Box<dyn ArrowPredicate> {
511 int_predicate(|_| true, |v| Some(v % 2 == 0))
512 }
513
514 fn in_range() -> Box<dyn ArrowPredicate> {
516 int_predicate(|_| true, |v| Some((10..40).contains(&v)))
517 }
518
519 fn divisible_by_three_after_even() -> Box<dyn ArrowPredicate> {
521 int_predicate(
522 |v| v % 2 == 0,
523 |v| if v % 7 == 0 { None } else { Some(v % 3 == 0) },
524 )
525 }
526
527 fn all_true() -> Box<dyn ArrowPredicate> {
528 int_predicate(|_| true, |_| Some(true))
529 }
530
531 fn all_false() -> Box<dyn ArrowPredicate> {
532 int_predicate(|_| true, |_| Some(false))
533 }
534
535 fn never_called() -> Box<dyn ArrowPredicate> {
536 Box::new(ArrowPredicateFn::new(ProjectionMask::all(), |_| {
537 panic!("predicate evaluated after every row was rejected")
538 }))
539 }
540
541 fn sequential(batch: &RecordBatch, predicates: &mut [Box<dyn ArrowPredicate>]) -> Vec<bool> {
544 let mut kept = vec![true; batch.num_rows()];
545 for predicate in predicates {
546 if !kept.contains(&true) {
548 break;
549 }
550 let survivors = filter_record_batch(batch, &BooleanArray::from(kept.clone())).unwrap();
551 let filter = predicate.evaluate(survivors).unwrap();
552 for (filter_idx, keep) in kept.iter_mut().filter(|keep| **keep).enumerate() {
553 *keep = filter.is_valid(filter_idx) && filter.value(filter_idx);
554 }
555 }
556 kept
557 }
558
559 const POLICIES: [RowSelectionPolicy; 4] = [
560 RowSelectionPolicy::Selectors,
561 RowSelectionPolicy::Mask,
562 RowSelectionPolicy::Auto { threshold: 32 },
563 RowSelectionPolicy::Auto { threshold: 2 },
564 ];
565
566 type PredicateChain = fn() -> Vec<Box<dyn ArrowPredicate>>;
567
568 #[test]
569 fn fused_predicate_matches_sequential_evaluation() {
570 let cases: Vec<PredicateChain> = vec![
571 || vec![even(), divisible_by_three_after_even()],
572 || vec![even(), in_range(), divisible_by_three_after_even()],
573 || vec![in_range(), even()],
574 || vec![even(), all_true(), divisible_by_three_after_even()],
575 || vec![all_true(), even()],
576 || vec![all_true(), all_true()],
577 || vec![even(), all_false(), never_called()],
578 || vec![all_false(), never_called()],
579 ];
580
581 for num_rows in [0, 1, 7, 97, 200] {
582 let batch = int_batch(0..num_rows);
583 for make_predicates in &cases {
584 let expected = sequential(&batch, &mut make_predicates());
585 for policy in POLICIES {
586 let mut fused = FusedPredicate {
587 predicates: make_predicates(),
588 composition_policy: policy,
589 };
590 let actual = fused.evaluate(batch.clone()).unwrap();
591 assert_eq!(actual.null_count(), 0);
592 assert_eq!(
593 actual.values().iter().collect::<Vec<_>>(),
594 expected,
595 "{num_rows} rows, {policy:?}"
596 );
597 }
598 }
599 }
600 }
601
602 #[test]
603 fn fused_predicate_rejects_wrong_result_length() {
604 let short = Box::new(ArrowPredicateFn::new(ProjectionMask::all(), |_| {
605 Ok(BooleanArray::from(vec![true]))
606 }));
607 let mut fused = FusedPredicate::new(vec![all_true(), short]);
608 let err = fused.evaluate(int_batch(0..4)).unwrap_err();
609 assert!(
610 err.to_string()
611 .contains("ArrowPredicate predicate returned 1 rows, expected 4"),
612 "{err}"
613 );
614 }
615
616 #[test]
617 fn narrow_batch_slices_contiguous_survivors() {
618 let batch = int_batch(0..6);
619 let filter = BooleanArray::from(vec![false, false, true, true, true, false]);
620 let narrowed = narrow_batch(&batch, &filter, 3).unwrap();
621 assert_eq!(
622 narrowed.column(0).as_primitive::<Int32Type>().values(),
623 &[2, 3, 4]
624 );
625 assert_eq!(narrowed.column(0).to_data().buffers()[0].as_ptr(), unsafe {
627 batch.column(0).to_data().buffers()[0].as_ptr().add(2 * 4)
628 });
629
630 let filter = BooleanArray::from(vec![false, true, false, true, false, false]);
631 let narrowed = narrow_batch(&batch, &filter, 2).unwrap();
632 assert_eq!(
633 narrowed.column(0).as_primitive::<Int32Type>().values(),
634 &[1, 3]
635 );
636 }
637
638 #[test]
641 fn fused_predicate_matches_predicate_major_read_plan() {
642 let data: Vec<i32> = (0..97).collect();
643 let make_reader = || {
644 let levels = vec![0; data.len()];
645 let leaf = make_int32_page_reader(&data, &levels, &levels, 0, 0, None);
646 let struct_type =
647 DataType::Struct(Fields::from(vec![Field::new("c0", DataType::Int32, false)]));
648 Box::new(StructArrayReader::new(
649 struct_type,
650 vec![leaf],
651 0,
652 0,
653 false,
654 None,
655 )) as Box<dyn ArrayReader>
656 };
657 let make_predicates = || vec![even(), divisible_by_three_after_even()];
658
659 let prior = RowSelection::from_filters(&[BooleanArray::from(
660 (0..data.len()).map(|idx| idx % 5 != 0).collect::<Vec<_>>(),
661 )]);
662 for initial in [None, Some(prior)] {
663 let mut sequential = ReadPlanBuilder::new(7).with_selection(initial.clone());
664 for predicate in &mut make_predicates() {
665 sequential = sequential
666 .with_predicate(make_reader(), predicate.as_mut())
667 .unwrap();
668 }
669
670 for policy in POLICIES {
671 let mut fused = FusedPredicate::new(make_predicates());
672 let plan = ReadPlanBuilder::new(7)
673 .with_selection(initial.clone())
674 .with_row_selection_policy(policy)
675 .with_predicate(make_reader(), &mut fused)
676 .unwrap();
677 assert_eq!(plan.selection(), sequential.selection(), "{policy:?}");
678 }
679 }
680 }
681}