1use std::sync::Arc;
16
17use arc_swap::ArcSwap;
18use common_telemetry::{debug, warn};
19use common_time::Timestamp;
20use common_time::range::TimestampRange;
21use common_time::timestamp::TimeUnit;
22use datafusion::common::ScalarValue;
23use datafusion::physical_optimizer::pruning::PruningPredicate;
24use datafusion_common::ToDFSchema;
25use datafusion_common::pruning::PruningStatistics;
26use datafusion_common::tree_node::TreeNode;
27use datafusion_expr::expr::{Expr, InList};
28use datafusion_expr::{Between, BinaryExpr, Operator};
29use datafusion_physical_expr::execution_props::ExecutionProps;
30use datafusion_physical_expr::expressions::{
31 BinaryExpr as PhysicalBinaryExpr, DynamicFilterPhysicalExpr, is_null,
32};
33use datafusion_physical_expr::{PhysicalExpr, create_physical_expr};
34use datatypes::arrow;
35use datatypes::value::scalar_value_to_timestamp;
36use snafu::ResultExt;
37
38use crate::error;
39
40#[cfg(test)]
41mod stats;
42
43macro_rules! return_none_if_utf8 {
46 ($lit: ident) => {
47 if is_string_timestamp_literal($lit) {
48 warn!(
49 "Unexpected ScalarValue::Utf8 in time range predicate: {:?}. Maybe it's an implicit bug, please report it to https://github.com/GreptimeTeam/greptimedb/issues",
50 $lit
51 );
52
53 return None;
55 }
56 };
57}
58
59pub fn is_string_timestamp_literal(scalar: &ScalarValue) -> bool {
60 matches!(
61 scalar,
62 ScalarValue::Utf8(_) | ScalarValue::LargeUtf8(_) | ScalarValue::Utf8View(_)
63 )
64}
65
66#[derive(Debug, Clone, Default)]
68pub struct Predicate {
69 exprs: Arc<Vec<Expr>>,
71 dyn_filters: Arc<ArcSwap<Vec<Arc<DynamicFilterPhysicalExpr>>>>,
75}
76
77impl Predicate {
78 pub fn new(exprs: Vec<Expr>) -> Self {
82 Self {
83 exprs: Arc::new(exprs),
84 dyn_filters: Arc::new(ArcSwap::new(Arc::new(vec![]))),
85 }
86 }
87
88 pub fn with_dyn_filters(
89 exprs: Vec<Expr>,
90 dyn_filters: Vec<Arc<DynamicFilterPhysicalExpr>>,
91 ) -> Self {
92 Self {
93 exprs: Arc::new(exprs),
94 dyn_filters: Arc::new(ArcSwap::new(Arc::new(dyn_filters))),
95 }
96 }
97
98 pub fn is_empty(&self) -> bool {
99 self.exprs.is_empty() && self.dyn_filters.load().is_empty()
100 }
101
102 pub fn add_dyn_filters(&self, dyn_filters: Vec<Arc<DynamicFilterPhysicalExpr>>) {
104 self.dyn_filters.rcu(|existing| {
105 let mut new_filters = existing.as_ref().clone();
106 new_filters.extend(dyn_filters.clone());
107 Arc::new(new_filters)
108 });
109 }
110
111 pub fn clear_dyn_filters(&self) {
113 self.dyn_filters.store(Arc::new(vec![]));
114 }
115
116 pub fn exprs(&self) -> &[Expr] {
118 &self.exprs
119 }
120
121 pub fn dyn_filters(&self) -> Arc<Vec<Arc<DynamicFilterPhysicalExpr>>> {
124 self.dyn_filters.load_full()
125 }
126
127 pub fn dyn_filter_phy_exprs(&self) -> error::Result<Vec<Arc<dyn PhysicalExpr>>> {
130 self.dyn_filters
131 .load()
132 .iter()
133 .map(|e| {
134 e.children()
136 .into_iter()
137 .try_fold(e.current()?, |expr, child| {
138 Ok(Arc::new(PhysicalBinaryExpr::new(
139 expr,
140 Operator::Or,
141 is_null(child.clone())?,
142 )) as Arc<dyn PhysicalExpr>)
143 })
144 })
145 .collect::<Result<Vec<_>, _>>()
146 .context(error::DatafusionSnafu)
147 }
148
149 pub fn to_physical_expr(
151 expr: &Expr,
152 schema: &arrow::datatypes::SchemaRef,
153 ) -> error::Result<Arc<dyn PhysicalExpr>> {
154 let df_schema = schema
155 .clone()
156 .to_dfschema_ref()
157 .context(error::DatafusionSnafu)?;
158
159 let execution_props = &ExecutionProps::new();
163
164 create_physical_expr(expr, df_schema.as_ref(), execution_props)
165 .context(error::DatafusionSnafu)
166 }
167
168 pub fn to_physical_exprs(
170 &self,
171 schema: &arrow::datatypes::SchemaRef,
172 ) -> error::Result<Vec<Arc<dyn PhysicalExpr>>> {
173 let dyn_filters = self.dyn_filter_phy_exprs()?;
174
175 Ok(self
176 .exprs
177 .iter()
178 .filter_map(|expr| Self::to_physical_expr(expr, schema).ok())
179 .chain(dyn_filters)
180 .collect::<Vec<_>>())
181 }
182
183 pub fn prune_with_stats<S: PruningStatistics>(
186 &self,
187 stats: &S,
188 schema: &arrow::datatypes::SchemaRef,
189 ) -> Vec<bool> {
190 let mut res = vec![true; stats.num_containers()];
191 let physical_exprs = match self.to_physical_exprs(schema) {
192 Ok(expr) => expr,
193 Err(e) => {
194 warn!(e; "Failed to build physical expr from predicates: {:?}", &self.exprs);
195 return res;
196 }
197 };
198
199 for expr in &physical_exprs {
200 match PruningPredicate::try_new(expr.clone(), schema.clone()) {
201 Ok(p) => match p.prune(stats) {
202 Ok(r) => {
203 for (curr_val, res) in r.into_iter().zip(res.iter_mut()) {
204 *res &= curr_val
205 }
206 }
207 Err(e) => {
208 warn!(e; "Failed to prune row groups");
209 }
210 },
211 Err(e) => {
212 debug!("Failed to create pruning predicate for expr: {e:?}");
214 }
215 }
216 }
217 res
218 }
219}
220
221pub fn build_time_range_predicate(
226 ts_col_name: &str,
227 ts_col_unit: TimeUnit,
228 filters: &[Expr],
229) -> TimestampRange {
230 let mut res = TimestampRange::min_to_max();
231 for expr in filters {
232 if let Some(range) = extract_time_range_from_expr(ts_col_name, ts_col_unit, expr) {
233 res = res.and(&range);
234 }
235 }
236 res
237}
238
239#[derive(Debug, Clone, PartialEq, Eq)]
246pub enum TimeRangeExtraction {
247 Absent,
249 Extracted(TimestampRange),
252 Unsupported,
255}
256
257pub fn extract_time_range_strict(
260 ts_col_name: &str,
261 ts_col_unit: TimeUnit,
262 filters: &[Expr],
263) -> TimeRangeExtraction {
264 let mut range: Option<TimestampRange> = None;
265 for expr in filters {
266 if !expr
267 .column_refs()
268 .iter()
269 .any(|column| column.name == ts_col_name)
270 {
271 continue;
272 }
273 if contains_disjunction_over_column(expr, ts_col_name) {
278 return TimeRangeExtraction::Unsupported;
279 }
280 match extract_time_range_from_expr(ts_col_name, ts_col_unit, expr) {
285 Some(extracted) => {
286 range = Some(match range {
287 Some(acc) => acc.and(&extracted),
288 None => extracted,
289 });
290 }
291 None => return TimeRangeExtraction::Unsupported,
292 }
293 }
294 match range {
295 Some(range) => TimeRangeExtraction::Extracted(range),
296 None => TimeRangeExtraction::Absent,
297 }
298}
299
300fn contains_disjunction_over_column(expr: &Expr, ts_col_name: &str) -> bool {
301 let is_disjunction = |expr: &Expr| {
302 matches!(
303 expr,
304 Expr::BinaryExpr(BinaryExpr {
305 op: Operator::Or,
306 ..
307 }) | Expr::InList(_)
308 ) && expr
309 .column_refs()
310 .iter()
311 .any(|column| column.name == ts_col_name)
312 };
313 expr.exists(|expr| Ok(is_disjunction(expr))).unwrap_or(true)
314}
315
316pub fn extract_time_range_from_expr(
319 ts_col_name: &str,
320 ts_col_unit: TimeUnit,
321 expr: &Expr,
322) -> Option<TimestampRange> {
323 match expr {
324 Expr::BinaryExpr(BinaryExpr { left, op, right }) => {
325 extract_from_binary_expr(ts_col_name, ts_col_unit, left, op, right)
326 }
327 Expr::Between(Between {
328 expr,
329 negated,
330 low,
331 high,
332 }) => extract_from_between_expr(ts_col_name, ts_col_unit, expr, negated, low, high),
333 Expr::InList(InList {
334 expr,
335 list,
336 negated,
337 }) => extract_from_in_list_expr(ts_col_name, expr, *negated, list),
338 _ => None,
339 }
340}
341
342fn extract_from_binary_expr(
343 ts_col_name: &str,
344 ts_col_unit: TimeUnit,
345 left: &Expr,
346 op: &Operator,
347 right: &Expr,
348) -> Option<TimestampRange> {
349 match op {
350 Operator::Eq => get_timestamp_filter(ts_col_name, left, right)
351 .and_then(|(ts, _)| ts.convert_to(ts_col_unit))
352 .map(TimestampRange::single),
353 Operator::Lt => {
354 let (ts, reverse) = get_timestamp_filter(ts_col_name, left, right)?;
355 if reverse {
356 let ts_val = ts.convert_to(ts_col_unit)?.value();
358 Some(TimestampRange::from_start(Timestamp::new(
359 ts_val + 1,
360 ts_col_unit,
361 )))
362 } else {
363 ts.convert_to_ceil(ts_col_unit)
365 .map(|ts| TimestampRange::until_end(ts, false))
366 }
367 }
368 Operator::LtEq => {
369 let (ts, reverse) = get_timestamp_filter(ts_col_name, left, right)?;
370 if reverse {
371 ts.convert_to_ceil(ts_col_unit)
373 .map(TimestampRange::from_start)
374 } else {
375 ts.convert_to(ts_col_unit)
377 .map(|ts| TimestampRange::until_end(ts, true))
378 }
379 }
380 Operator::Gt => {
381 let (ts, reverse) = get_timestamp_filter(ts_col_name, left, right)?;
382 if reverse {
383 ts.convert_to_ceil(ts_col_unit)
385 .map(|t| TimestampRange::until_end(t, false))
386 } else {
387 let ts_val = ts.convert_to(ts_col_unit)?.value();
389 Some(TimestampRange::from_start(Timestamp::new(
390 ts_val + 1,
391 ts_col_unit,
392 )))
393 }
394 }
395 Operator::GtEq => {
396 let (ts, reverse) = get_timestamp_filter(ts_col_name, left, right)?;
397 if reverse {
398 ts.convert_to(ts_col_unit)
400 .map(|t| TimestampRange::until_end(t, true))
401 } else {
402 ts.convert_to_ceil(ts_col_unit)
404 .map(TimestampRange::from_start)
405 }
406 }
407 Operator::And => {
408 let left = extract_time_range_from_expr(ts_col_name, ts_col_unit, left)
411 .unwrap_or_else(TimestampRange::min_to_max);
412 let right = extract_time_range_from_expr(ts_col_name, ts_col_unit, right)
413 .unwrap_or_else(TimestampRange::min_to_max);
414 Some(left.and(&right))
415 }
416 Operator::Or => {
417 let left = extract_time_range_from_expr(ts_col_name, ts_col_unit, left)?;
418 let right = extract_time_range_from_expr(ts_col_name, ts_col_unit, right)?;
419 Some(left.or(&right))
420 }
421 _ => None,
422 }
423}
424
425fn get_timestamp_filter(ts_col_name: &str, left: &Expr, right: &Expr) -> Option<(Timestamp, bool)> {
426 let (col, lit, reverse) = match (left, right) {
427 (Expr::Column(column), Expr::Literal(scalar, _)) => (column, scalar, false),
428 (Expr::Literal(scalar, _), Expr::Column(column)) => (column, scalar, true),
429 _ => {
430 return None;
431 }
432 };
433 if col.name != ts_col_name {
434 return None;
435 }
436
437 return_none_if_utf8!(lit);
438 scalar_value_to_timestamp(lit, None).map(|t| (t, reverse))
439}
440
441fn extract_from_between_expr(
442 ts_col_name: &str,
443 ts_col_unit: TimeUnit,
444 expr: &Expr,
445 negated: &bool,
446 low: &Expr,
447 high: &Expr,
448) -> Option<TimestampRange> {
449 let Expr::Column(col) = expr else {
450 return None;
451 };
452 if col.name != ts_col_name {
453 return None;
454 }
455
456 if *negated {
457 return None;
458 }
459
460 match (low, high) {
461 (Expr::Literal(low, _), Expr::Literal(high, _)) => {
462 return_none_if_utf8!(low);
463 return_none_if_utf8!(high);
464
465 let low_opt =
466 scalar_value_to_timestamp(low, None).and_then(|ts| ts.convert_to(ts_col_unit));
467 let high_opt = scalar_value_to_timestamp(high, None)
468 .and_then(|ts| ts.convert_to_ceil(ts_col_unit));
469 Some(TimestampRange::new_inclusive(low_opt, high_opt))
470 }
471 _ => None,
472 }
473}
474
475fn extract_from_in_list_expr(
477 ts_col_name: &str,
478 expr: &Expr,
479 negated: bool,
480 list: &[Expr],
481) -> Option<TimestampRange> {
482 if negated {
483 return None;
484 }
485 let Expr::Column(col) = expr else {
486 return None;
487 };
488 if col.name != ts_col_name {
489 return None;
490 }
491
492 if list.is_empty() {
493 return Some(TimestampRange::empty());
494 }
495 let mut init_range = TimestampRange::empty();
496 for expr in list {
497 if let Expr::Literal(scalar, _) = expr {
498 return_none_if_utf8!(scalar);
499 if let Some(timestamp) = scalar_value_to_timestamp(scalar, None) {
500 init_range = init_range.or(&TimestampRange::single(timestamp))
501 } else {
502 return None;
505 }
506 }
507 }
508 Some(init_range)
509}
510
511#[cfg(test)]
512mod tests {
513 use std::sync::Arc;
514
515 use common_test_util::temp_dir::{TempDir, create_temp_dir};
516 use datafusion::parquet::arrow::ArrowWriter;
517 use datafusion_common::{Column, ScalarValue};
518 use datafusion_expr::{BinaryExpr, Literal, Operator, col, lit};
519 use datatypes::arrow::array::Int32Array;
520 use datatypes::arrow::datatypes::{DataType, Field, Schema};
521 use datatypes::arrow::record_batch::RecordBatch;
522 use datatypes::arrow_array::StringArray;
523 use parquet::arrow::ParquetRecordBatchStreamBuilder;
524 use parquet::file::properties::WriterProperties;
525
526 use super::*;
527 use crate::predicate::stats::RowGroupPruningStatistics;
528
529 fn check_build_predicate(expr: Expr, expect: TimestampRange) {
530 assert_eq!(
531 expect,
532 build_time_range_predicate("ts", TimeUnit::Millisecond, &[expr])
533 );
534 }
535
536 #[test]
537 fn test_gt() {
538 check_build_predicate(
540 col("ts").gt(lit(ScalarValue::TimestampMillisecond(Some(1), None))),
541 TimestampRange::from_start(Timestamp::new_millisecond(2)),
542 );
543
544 check_build_predicate(
546 lit(ScalarValue::TimestampMillisecond(Some(1), None)).gt(col("ts")),
547 TimestampRange::until_end(Timestamp::new_millisecond(1), false),
548 );
549
550 check_build_predicate(
552 lit(ScalarValue::TimestampMicrosecond(Some(1001), None)).gt(col("ts")),
553 TimestampRange::until_end(Timestamp::new_millisecond(1), true),
554 );
555
556 check_build_predicate(
558 col("ts").gt(lit(ScalarValue::TimestampMicrosecond(Some(1001), None))),
559 TimestampRange::from_start(Timestamp::new_millisecond(2)),
560 );
561
562 check_build_predicate(
564 lit(ScalarValue::TimestampSecond(Some(1), None)).gt(col("ts")),
565 TimestampRange::until_end(Timestamp::new_millisecond(1000), false),
566 );
567
568 check_build_predicate(
570 col("ts").gt(lit(ScalarValue::TimestampSecond(Some(1), None))),
571 TimestampRange::from_start(Timestamp::new_millisecond(1001)),
572 );
573 }
574
575 #[test]
576 fn test_gt_eq() {
577 check_build_predicate(
579 col("ts").gt_eq(lit(ScalarValue::TimestampMillisecond(Some(1), None))),
580 TimestampRange::from_start(Timestamp::new_millisecond(1)),
581 );
582
583 check_build_predicate(
585 lit(ScalarValue::TimestampMillisecond(Some(1), None)).gt_eq(col("ts")),
586 TimestampRange::until_end(Timestamp::new_millisecond(1), true),
587 );
588
589 check_build_predicate(
591 lit(ScalarValue::TimestampMicrosecond(Some(1001), None)).gt_eq(col("ts")),
592 TimestampRange::until_end(Timestamp::new_millisecond(1), true),
593 );
594
595 check_build_predicate(
597 col("ts").gt_eq(lit(ScalarValue::TimestampMicrosecond(Some(1001), None))),
598 TimestampRange::from_start(Timestamp::new_millisecond(2)),
599 );
600
601 check_build_predicate(
603 lit(ScalarValue::TimestampSecond(Some(1), None)).gt_eq(col("ts")),
604 TimestampRange::until_end(Timestamp::new_millisecond(1000), true),
605 );
606
607 check_build_predicate(
609 col("ts").gt_eq(lit(ScalarValue::TimestampSecond(Some(1), None))),
610 TimestampRange::from_start(Timestamp::new_millisecond(1000)),
611 );
612 }
613
614 #[test]
615 fn test_lt() {
616 check_build_predicate(
618 col("ts").lt(lit(ScalarValue::TimestampMillisecond(Some(1), None))),
619 TimestampRange::until_end(Timestamp::new_millisecond(1), false),
620 );
621
622 check_build_predicate(
624 lit(ScalarValue::TimestampMillisecond(Some(1), None)).lt(col("ts")),
625 TimestampRange::from_start(Timestamp::new_millisecond(2)),
626 );
627
628 check_build_predicate(
630 lit(ScalarValue::TimestampMicrosecond(Some(1001), None)).lt(col("ts")),
631 TimestampRange::from_start(Timestamp::new_millisecond(2)),
632 );
633
634 check_build_predicate(
636 col("ts").lt(lit(ScalarValue::TimestampMicrosecond(Some(1001), None))),
637 TimestampRange::until_end(Timestamp::new_millisecond(1), true),
638 );
639
640 check_build_predicate(
642 lit(ScalarValue::TimestampSecond(Some(1), None)).lt(col("ts")),
643 TimestampRange::from_start(Timestamp::new_millisecond(1001)),
644 );
645
646 check_build_predicate(
648 col("ts").lt(lit(ScalarValue::TimestampSecond(Some(1), None))),
649 TimestampRange::until_end(Timestamp::new_millisecond(1000), false),
650 );
651 }
652
653 #[test]
654 fn test_lt_eq() {
655 check_build_predicate(
657 col("ts").lt_eq(lit(ScalarValue::TimestampMillisecond(Some(1), None))),
658 TimestampRange::until_end(Timestamp::new_millisecond(1), true),
659 );
660
661 check_build_predicate(
663 lit(ScalarValue::TimestampMillisecond(Some(1), None)).lt_eq(col("ts")),
664 TimestampRange::from_start(Timestamp::new_millisecond(1)),
665 );
666
667 check_build_predicate(
669 lit(ScalarValue::TimestampMicrosecond(Some(1001), None)).lt_eq(col("ts")),
670 TimestampRange::from_start(Timestamp::new_millisecond(2)),
671 );
672
673 check_build_predicate(
675 col("ts").lt_eq(lit(ScalarValue::TimestampMicrosecond(Some(1001), None))),
676 TimestampRange::until_end(Timestamp::new_millisecond(1), true),
677 );
678
679 check_build_predicate(
681 lit(ScalarValue::TimestampSecond(Some(1), None)).lt_eq(col("ts")),
682 TimestampRange::from_start(Timestamp::new_millisecond(1000)),
683 );
684
685 check_build_predicate(
687 col("ts").lt_eq(lit(ScalarValue::TimestampSecond(Some(1), None))),
688 TimestampRange::until_end(Timestamp::new_millisecond(1000), true),
689 );
690 }
691
692 #[test]
693 fn test_extract_time_range_strict() {
694 fn ts_lit(ms: i64) -> Expr {
695 lit(ScalarValue::TimestampMillisecond(Some(ms), None))
696 }
697 let extract =
698 |filters: &[Expr]| extract_time_range_strict("ts", TimeUnit::Millisecond, filters);
699 let range = |start: i64, end: i64| {
700 TimestampRange::new(
701 Timestamp::new_millisecond(start),
702 Timestamp::new_millisecond(end),
703 )
704 .unwrap()
705 };
706
707 assert_eq!(extract(&[]), TimeRangeExtraction::Absent);
709 assert_eq!(
710 extract(&[col("host").eq(lit("a"))]),
711 TimeRangeExtraction::Absent
712 );
713
714 assert_eq!(
716 extract(&[
717 col("ts").gt_eq(ts_lit(1000)),
718 col("ts").lt(ts_lit(2000)),
719 col("host").eq(lit("a")),
720 ]),
721 TimeRangeExtraction::Extracted(range(1000, 2000))
722 );
723
724 assert_eq!(
726 extract(&[col("ts").gt_eq(ts_lit(1000))]),
727 TimeRangeExtraction::Extracted(TimestampRange::from_start(Timestamp::new_millisecond(
728 1000
729 )))
730 );
731 assert_eq!(
732 extract(&[col("ts").lt(ts_lit(2000))]),
733 TimeRangeExtraction::Extracted(TimestampRange::until_end(
734 Timestamp::new_millisecond(2000),
735 false
736 ))
737 );
738
739 assert_eq!(
741 extract(&[col("ts").between(ts_lit(1000), ts_lit(2000))]),
742 TimeRangeExtraction::Extracted(range(1000, 2001))
743 );
744
745 assert_eq!(
747 extract(&[col("ts").eq(ts_lit(1500))]),
748 TimeRangeExtraction::Extracted(TimestampRange::single(Timestamp::new_millisecond(
749 1500
750 )))
751 );
752
753 assert_eq!(
755 extract(&[col("ts").gt_eq(ts_lit(1000)).and(col("ts").lt(col("t2")))]),
756 TimeRangeExtraction::Extracted(TimestampRange::from_start(Timestamp::new_millisecond(
757 1000
758 )))
759 );
760
761 let TimeRangeExtraction::Extracted(empty) =
763 extract(&[col("ts").gt_eq(ts_lit(2000)), col("ts").lt(ts_lit(1000))])
764 else {
765 panic!("expected an extraction");
766 };
767 assert!(empty.is_empty());
768
769 assert_eq!(
772 extract(&[col("ts").gt(ts_lit(1000)).or(col("host").eq(lit("a")))]),
773 TimeRangeExtraction::Unsupported
774 );
775 assert_eq!(
776 extract(&[!col("ts").gt(ts_lit(1000))]),
777 TimeRangeExtraction::Unsupported
778 );
779 assert_eq!(
780 extract(&[col("ts").gt_eq(col("t2"))]),
781 TimeRangeExtraction::Unsupported
782 );
783 }
784
785 async fn gen_test_parquet_file(dir: &TempDir, cnt: usize) -> (String, Arc<Schema>) {
786 let path = dir
787 .path()
788 .join("test-prune.parquet")
789 .to_string_lossy()
790 .to_string();
791
792 let name_field = Field::new("name", DataType::Utf8, true);
793 let count_field = Field::new("cnt", DataType::Int32, true);
794 let schema = Arc::new(Schema::new(vec![name_field, count_field]));
795
796 let file = std::fs::OpenOptions::new()
797 .write(true)
798 .create(true)
799 .truncate(true)
800 .open(path.clone())
801 .unwrap();
802
803 let write_props = WriterProperties::builder()
804 .set_max_row_group_row_count(Some(10))
805 .build();
806 let mut writer = ArrowWriter::try_new(file, schema.clone(), Some(write_props)).unwrap();
807
808 for i in (0..cnt).step_by(10) {
809 let name_array = Arc::new(StringArray::from(
810 (i..(i + 10).min(cnt))
811 .map(|i| i.to_string())
812 .collect::<Vec<_>>(),
813 )) as Arc<_>;
814 let count_array = Arc::new(Int32Array::from(
815 (i..(i + 10).min(cnt)).map(|i| i as i32).collect::<Vec<_>>(),
816 )) as Arc<_>;
817 let rb = RecordBatch::try_new(schema.clone(), vec![name_array, count_array]).unwrap();
818 writer.write(&rb).unwrap();
819 }
820 let _ = writer.close().unwrap();
821 (path, schema)
822 }
823
824 async fn assert_prune(array_cnt: usize, filters: Vec<Expr>, expect: Vec<bool>) {
825 let dir = create_temp_dir("prune_parquet");
826 let (path, arrow_schema) = gen_test_parquet_file(&dir, array_cnt).await;
827 let schema = Arc::new(datatypes::schema::Schema::try_from(arrow_schema.clone()).unwrap());
828 let arrow_predicate = Predicate::new(filters);
829 let builder = ParquetRecordBatchStreamBuilder::new(
830 tokio::fs::OpenOptions::new()
831 .read(true)
832 .open(path)
833 .await
834 .unwrap(),
835 )
836 .await
837 .unwrap();
838 let metadata = builder.metadata().clone();
839 let row_groups = metadata.row_groups();
840
841 let stats = RowGroupPruningStatistics::new(row_groups, &schema);
842 let res = arrow_predicate.prune_with_stats(&stats, &arrow_schema);
843 assert_eq!(expect, res);
844 }
845
846 #[test]
847 fn test_clear_dyn_filters_preserves_static_predicates() {
848 use datafusion_physical_expr::expressions::lit as physical_lit;
849
850 let static_exprs = vec![col("a").eq(lit(1_i32))];
851 let dynamic_filter = Arc::new(DynamicFilterPhysicalExpr::new(vec![], physical_lit(true)));
852 let predicate =
853 Predicate::with_dyn_filters(static_exprs.clone(), vec![dynamic_filter.clone()]);
854
855 predicate.clear_dyn_filters();
856 dynamic_filter.update(physical_lit(false)).unwrap();
858
859 assert_eq!(predicate.exprs(), static_exprs);
860 assert!(predicate.dyn_filters().is_empty());
861 assert!(predicate.dyn_filter_phy_exprs().unwrap().is_empty());
862 }
863
864 #[tokio::test]
865 async fn test_dynamic_pruning_keeps_null_row_group() {
866 use datafusion_physical_expr::expressions::{
867 Column as PhysicalColumn, lit as physical_lit,
868 };
869
870 let dir = create_temp_dir("dynamic_pruning_nulls");
871 let path = dir.path().join("nullable.parquet");
872 let arrow_schema = Arc::new(Schema::new(vec![Field::new("a", DataType::Int32, true)]));
873 let file = std::fs::File::create(&path).unwrap();
874 let mut writer = ArrowWriter::try_new(file, arrow_schema.clone(), None).unwrap();
875 for values in [[None, Some(1)], [Some(1), Some(1)], [None, None]] {
876 let batch = RecordBatch::try_new(
877 arrow_schema.clone(),
878 vec![Arc::new(Int32Array::from(values.to_vec()))],
879 )
880 .unwrap();
881 writer.write(&batch).unwrap();
882 writer.flush().unwrap();
883 }
884 writer.close().unwrap();
885 let builder =
886 ParquetRecordBatchStreamBuilder::new(tokio::fs::File::open(path).await.unwrap())
887 .await
888 .unwrap();
889 let schema = Arc::new(datatypes::schema::Schema::try_from(arrow_schema.clone()).unwrap());
890 let stats = RowGroupPruningStatistics::new(builder.metadata().row_groups(), &schema);
891 let filter = Arc::new(DynamicFilterPhysicalExpr::new(
892 vec![Arc::new(PhysicalColumn::new("a", 0))],
893 physical_lit(true),
894 ));
895 let predicate = Predicate::with_dyn_filters(vec![], vec![filter.clone()]);
896 assert_eq!(
897 predicate.prune_with_stats(&stats, &arrow_schema),
898 vec![true; 3]
899 );
900 filter
901 .update(
902 Predicate::to_physical_expr(
903 &col("a").gt_eq(lit(10_i32)).and(col("a").lt_eq(lit(10_i32))),
904 &arrow_schema,
905 )
906 .unwrap(),
907 )
908 .unwrap();
909 assert_eq!(
911 predicate.prune_with_stats(&stats, &arrow_schema),
912 vec![true, false, true],
913 );
914 }
915
916 #[tokio::test]
917 async fn test_clear_dyn_filters_restores_static_row_group_pruning() {
918 use datafusion_physical_expr::expressions::{
919 Column as PhysicalColumn, lit as physical_lit,
920 };
921
922 let dir = create_temp_dir("dynamic_pruning_reset");
923 let (path, arrow_schema) = gen_test_parquet_file(&dir, 30).await;
924 let schema = Arc::new(datatypes::schema::Schema::try_from(arrow_schema.clone()).unwrap());
925 let builder =
926 ParquetRecordBatchStreamBuilder::new(tokio::fs::File::open(path).await.unwrap())
927 .await
928 .unwrap();
929 let stats = RowGroupPruningStatistics::new(builder.metadata().row_groups(), &schema);
930 let dynamic_filter = Arc::new(DynamicFilterPhysicalExpr::new(
931 vec![Arc::new(PhysicalColumn::new("cnt", 1))],
932 physical_lit(true),
933 ));
934 let predicate = Predicate::with_dyn_filters(
935 vec![col("cnt").gt_eq(lit(10_i32))],
936 vec![dynamic_filter.clone()],
937 );
938
939 dynamic_filter
940 .update(
941 Predicate::to_physical_expr(&col("cnt").gt(lit(100_i32)), &arrow_schema).unwrap(),
942 )
943 .unwrap();
944 assert_eq!(
945 predicate.prune_with_stats(&stats, &arrow_schema),
946 vec![false; 3]
947 );
948
949 predicate.clear_dyn_filters();
952 assert_eq!(
953 predicate.prune_with_stats(&stats, &arrow_schema),
954 vec![false, true, true]
955 );
956 }
957
958 fn gen_predicate(max_val: i32, op: Operator) -> Vec<Expr> {
959 vec![datafusion_expr::Expr::BinaryExpr(BinaryExpr {
960 left: Box::new(datafusion_expr::Expr::Column(Column::from_name("cnt"))),
961 op,
962 right: Box::new(max_val.lit()),
963 })]
964 }
965
966 #[tokio::test]
967 async fn test_prune_empty() {
968 assert_prune(3, vec![], vec![true]).await;
969 }
970
971 #[tokio::test]
972 async fn test_prune_all_match() {
973 let p = gen_predicate(3, Operator::Gt);
974 assert_prune(2, p, vec![false]).await;
975 }
976
977 #[tokio::test]
978 async fn test_prune_gt() {
979 let p = gen_predicate(29, Operator::Gt);
980 assert_prune(
981 100,
982 p,
983 vec![
984 false, false, false, true, true, true, true, true, true, true,
985 ],
986 )
987 .await;
988 }
989
990 #[tokio::test]
991 async fn test_prune_eq_expr() {
992 let p = gen_predicate(30, Operator::Eq);
993 assert_prune(40, p, vec![false, false, false, true]).await;
994 }
995
996 #[tokio::test]
997 async fn test_prune_neq_expr() {
998 let p = gen_predicate(30, Operator::NotEq);
999 assert_prune(40, p, vec![true, true, true, true]).await;
1000 }
1001
1002 #[tokio::test]
1003 async fn test_prune_gteq_expr() {
1004 let p = gen_predicate(29, Operator::GtEq);
1005 assert_prune(40, p, vec![false, false, true, true]).await;
1006 }
1007
1008 #[tokio::test]
1009 async fn test_prune_lt_expr() {
1010 let p = gen_predicate(30, Operator::Lt);
1011 assert_prune(40, p, vec![true, true, true, false]).await;
1012 }
1013
1014 #[tokio::test]
1015 async fn test_prune_lteq_expr() {
1016 let p = gen_predicate(30, Operator::LtEq);
1017 assert_prune(40, p, vec![true, true, true, true]).await;
1018 }
1019
1020 #[tokio::test]
1021 async fn test_prune_between_expr() {
1022 let p = gen_predicate(30, Operator::LtEq);
1023 assert_prune(40, p, vec![true, true, true, true]).await;
1024 }
1025
1026 #[tokio::test]
1027 async fn test_or() {
1028 let e = datafusion_expr::Expr::Column(Column::from_name("cnt"))
1030 .gt(30.lit())
1031 .or(datafusion_expr::Expr::Column(Column::from_name("cnt")).lt(20.lit()));
1032 assert_prune(40, vec![e], vec![true, true, false, true]).await;
1033 }
1034
1035 #[tokio::test]
1036 async fn test_to_physical_expr() {
1037 let predicate = Predicate::new(vec![
1038 col("host").eq(lit("host_a")),
1039 col("ts").gt(lit(ScalarValue::TimestampMicrosecond(Some(123), None))),
1040 ]);
1041
1042 let schema = Arc::new(arrow::datatypes::Schema::new(vec![Field::new(
1043 "host",
1044 arrow::datatypes::DataType::Utf8,
1045 false,
1046 )]));
1047
1048 let predicates = predicate.to_physical_exprs(&schema).unwrap();
1049 assert!(!predicates.is_empty());
1050
1051 let physical_expr = Predicate::to_physical_expr(&col("host").eq(lit("host_a")), &schema);
1052 assert!(physical_expr.is_ok());
1053 }
1054}