1use std::collections::HashSet;
20
21use arrow::datatypes::DataType;
22use common_telemetry::debug;
23use datafusion_common::Result as DfResult;
24use datafusion_expr::{Expr, LogicalPlan, Operator};
25use datatypes::value::Value;
26use partition::expr::{Operand, PartitionExpr, RestrictedOp};
27
28pub struct PredicateExtractor;
30
31impl PredicateExtractor {
32 pub fn extract_partition_expressions(
35 plan: &LogicalPlan,
36 partition_columns: &[String],
37 ) -> DfResult<Vec<PartitionExpr>> {
38 let mut filter_exprs = Vec::new();
40 Self::collect_filter_expressions(plan, &mut filter_exprs)?;
41
42 if filter_exprs.is_empty() {
43 return Ok(Vec::new());
44 }
45
46 let mut partition_exprs = Vec::with_capacity(filter_exprs.len());
48 let partition_set: HashSet<String> = partition_columns.iter().cloned().collect();
49
50 for filter_expr in filter_exprs {
51 match DataFusionExprConverter::convert(&filter_expr) {
52 Ok(partition_expr) => {
53 match ExpressionChecker::check_expression_for_pruning(
55 &partition_expr,
56 &partition_set,
57 ) {
58 ExpressionCheckResult::UseAsIs(expr) => {
59 partition_exprs.push(expr);
60 }
61 ExpressionCheckResult::UsePartial(exprs) => {
62 partition_exprs.extend(exprs);
63 }
64 ExpressionCheckResult::Drop => {
65 debug!(
66 "Dropping mixed expression for correctness: {}",
67 partition_expr
68 );
69 }
70 }
71 }
72 Err(err) => {
73 debug!(
74 "Failed to convert filter expression to PartitionExpr: {}, skipping",
75 err
76 );
77 continue;
78 }
79 }
80 }
81
82 debug!(
83 "Extracted {} partition expressions from logical plan for partition columns: {:?}",
84 partition_exprs.len(),
85 partition_columns
86 );
87
88 Ok(partition_exprs)
89 }
90
91 fn collect_filter_expressions(plan: &LogicalPlan, expressions: &mut Vec<Expr>) -> DfResult<()> {
101 if let LogicalPlan::Filter(filter) = plan {
102 expressions.push(filter.predicate.clone());
103 }
104
105 if let LogicalPlan::TableScan(table_scan) = plan
111 && let Some(expr) = Self::conjunction(table_scan.filters.iter().cloned())
112 {
113 expressions.push(expr);
114 }
115
116 for child in plan.inputs() {
118 Self::collect_filter_expressions(child, expressions)?;
119 }
120
121 if plan.inputs().len() > 1 {
123 expressions.clear();
124 }
125
126 Ok(())
127 }
128
129 fn conjunction(mut expressions: impl Iterator<Item = Expr>) -> Option<Expr> {
130 let first = expressions.next()?;
131 Some(expressions.fold(first, |acc, expr| acc.and(expr)))
132 }
133}
134
135#[derive(Debug, Clone)]
137enum ExpressionCheckResult {
138 UseAsIs(PartitionExpr),
140 UsePartial(Vec<PartitionExpr>),
142 Drop,
144}
145
146struct ExpressionChecker;
148
149impl ExpressionChecker {
150 fn check_expression_for_pruning(
152 expr: &PartitionExpr,
153 partition_columns: &HashSet<String>,
154 ) -> ExpressionCheckResult {
155 match expr.op() {
156 RestrictedOp::And => {
157 let mut partition_constraints = Vec::new();
160 Self::extract_and_constraints(expr, partition_columns, &mut partition_constraints);
161
162 if partition_constraints.is_empty() {
163 ExpressionCheckResult::Drop
164 } else if Self::expr_only_involves_partition_columns(expr, partition_columns) {
165 ExpressionCheckResult::UseAsIs(expr.clone())
167 } else {
168 ExpressionCheckResult::UsePartial(partition_constraints)
170 }
171 }
172 RestrictedOp::Or => {
173 if Self::expr_only_involves_partition_columns(expr, partition_columns) {
176 ExpressionCheckResult::UseAsIs(expr.clone())
177 } else {
178 ExpressionCheckResult::Drop
180 }
181 }
182 _ => {
183 if Self::expr_only_involves_partition_columns(expr, partition_columns) {
185 ExpressionCheckResult::UseAsIs(expr.clone())
186 } else {
187 ExpressionCheckResult::Drop
188 }
189 }
190 }
191 }
192
193 fn extract_and_constraints(
195 expr: &PartitionExpr,
196 partition_columns: &HashSet<String>,
197 result: &mut Vec<PartitionExpr>,
198 ) {
199 if let RestrictedOp::And = expr.op() {
200 Self::extract_constraints_from_operand(expr.lhs(), partition_columns, result);
202 Self::extract_constraints_from_operand(expr.rhs(), partition_columns, result);
203 } else {
204 if Self::expr_only_involves_partition_columns(expr, partition_columns) {
206 result.push(expr.clone());
207 }
208 }
209 }
210
211 fn extract_constraints_from_operand(
213 operand: &Operand,
214 partition_columns: &HashSet<String>,
215 result: &mut Vec<PartitionExpr>,
216 ) {
217 match operand {
218 Operand::Column(_) | Operand::Value(_) => {
219 }
221 Operand::Expr(expr) => {
222 Self::extract_and_constraints(expr, partition_columns, result);
223 }
224 }
225 }
226
227 fn expr_only_involves_partition_columns(
229 expr: &PartitionExpr,
230 partition_columns: &HashSet<String>,
231 ) -> bool {
232 Self::operand_only_involves_partition_columns(expr.lhs(), partition_columns)
233 && Self::operand_only_involves_partition_columns(expr.rhs(), partition_columns)
234 }
235
236 fn operand_only_involves_partition_columns(
238 operand: &Operand,
239 partition_columns: &HashSet<String>,
240 ) -> bool {
241 match operand {
242 Operand::Column(col) => partition_columns.contains(col),
243 Operand::Value(_) => true, Operand::Expr(expr) => {
245 Self::expr_only_involves_partition_columns(expr, partition_columns)
246 }
247 }
248 }
249}
250
251struct DataFusionExprConverter;
253
254impl DataFusionExprConverter {
255 pub fn convert(expr: &Expr) -> DfResult<PartitionExpr> {
257 match expr {
258 Expr::BinaryExpr(binary_expr) => {
259 let lhs = Self::convert_to_operand(&binary_expr.left)?;
260 let rhs = Self::convert_to_operand(&binary_expr.right)?;
261 let op = Self::convert_operator(&binary_expr.op)?;
262
263 Ok(PartitionExpr::new(lhs, op, rhs))
264 }
265 Expr::InList(inlist_expr) => {
266 let column_operand = Self::convert_to_operand(&inlist_expr.expr)?;
269
270 if inlist_expr.list.is_empty() {
271 return Err(datafusion_common::DataFusionError::Plan(
272 "InList with empty list is not supported".to_string(),
273 ));
274 }
275
276 let op = if inlist_expr.negated {
277 RestrictedOp::NotEq
278 } else {
279 RestrictedOp::Eq
280 };
281
282 let connector_op = if inlist_expr.negated {
283 RestrictedOp::And } else {
285 RestrictedOp::Or };
287
288 let mut expressions = Vec::new();
290 for value_expr in &inlist_expr.list {
291 let value_operand = Self::convert_to_operand(value_expr)?;
292 expressions.push(PartitionExpr::new(
293 column_operand.clone(),
294 op.clone(),
295 value_operand,
296 ));
297 }
298
299 let mut expr_iter = expressions.into_iter();
301 let mut result = expr_iter.next().unwrap();
302 for expr in expr_iter {
303 result = PartitionExpr::new(
304 Operand::Expr(result),
305 connector_op.clone(),
306 Operand::Expr(expr),
307 );
308 }
309
310 Ok(result)
311 }
312 Expr::Between(between_expr) => {
313 let column_operand = Self::convert_to_operand(&between_expr.expr)?;
316 let low_operand = Self::convert_to_operand(&between_expr.low)?;
317 let high_operand = Self::convert_to_operand(&between_expr.high)?;
318
319 if between_expr.negated {
320 let left_expr =
322 PartitionExpr::new(column_operand.clone(), RestrictedOp::Lt, low_operand);
323 let right_expr =
324 PartitionExpr::new(column_operand, RestrictedOp::Gt, high_operand);
325 Ok(PartitionExpr::new(
326 Operand::Expr(left_expr),
327 RestrictedOp::Or,
328 Operand::Expr(right_expr),
329 ))
330 } else {
331 let left_expr =
333 PartitionExpr::new(column_operand.clone(), RestrictedOp::GtEq, low_operand);
334 let right_expr =
335 PartitionExpr::new(column_operand, RestrictedOp::LtEq, high_operand);
336 Ok(PartitionExpr::new(
337 Operand::Expr(left_expr),
338 RestrictedOp::And,
339 Operand::Expr(right_expr),
340 ))
341 }
342 }
343 Expr::IsNull(expr) => {
344 let column_operand = Self::convert_to_operand(expr)?;
346 Ok(PartitionExpr::new(
347 column_operand,
348 RestrictedOp::Eq,
349 Operand::Value(Value::Null),
350 ))
351 }
352 Expr::IsNotNull(expr) => {
353 let column_operand = Self::convert_to_operand(expr)?;
355 Ok(PartitionExpr::new(
356 column_operand,
357 RestrictedOp::NotEq,
358 Operand::Value(Value::Null),
359 ))
360 }
361 Expr::Not(expr) => {
362 match expr.as_ref() {
364 Expr::BinaryExpr(binary_expr) => {
365 let lhs = Self::convert_to_operand(&binary_expr.left)?;
366 let rhs = Self::convert_to_operand(&binary_expr.right)?;
367 let inverted_op = Self::invert_operator(&binary_expr.op)?;
368
369 Ok(PartitionExpr::new(lhs, inverted_op, rhs))
370 }
371 Expr::IsNull(inner_expr) => {
372 let column_operand = Self::convert_to_operand(inner_expr)?;
374 Ok(PartitionExpr::new(
375 column_operand,
376 RestrictedOp::NotEq,
377 Operand::Value(Value::Null),
378 ))
379 }
380 Expr::IsNotNull(inner_expr) => {
381 let column_operand = Self::convert_to_operand(inner_expr)?;
383 Ok(PartitionExpr::new(
384 column_operand,
385 RestrictedOp::Eq,
386 Operand::Value(Value::Null),
387 ))
388 }
389 _ => {
390 debug!(
391 "Unsupported NOT expression for partition pruning: {:?}",
392 expr
393 );
394 Err(datafusion_common::DataFusionError::Plan(format!(
395 "NOT expression with inner type {:?} not supported for partition pruning",
396 expr
397 )))
398 }
399 }
400 }
401 _ => Err(datafusion_common::DataFusionError::Plan(format!(
402 "Unsupported expression type for conversion: {:?}",
403 expr
404 ))),
405 }
406 }
407
408 fn convert_to_operand(expr: &Expr) -> DfResult<Operand> {
410 match expr {
411 Expr::Column(col) => {
412 let column_name = if let Some(relation) = &col.relation {
415 debug!(
416 "Using qualified column reference: {}.{}",
417 relation, col.name
418 );
419 col.name.clone()
420 } else {
421 col.name.clone()
422 };
423 Ok(Operand::Column(column_name))
424 }
425 Expr::Literal(scalar_value, _) => {
426 let value = Value::try_from(scalar_value.clone()).unwrap();
427 Ok(Operand::Value(value))
428 }
429 Expr::Alias(alias_expr) => {
430 Self::convert_to_operand(&alias_expr.expr)
432 }
433 Expr::Cast(cast_expr) => {
434 if Self::is_safe_cast_for_partition_pruning(&cast_expr.data_type) {
437 Self::convert_to_operand(&cast_expr.expr)
438 } else {
439 debug!(
440 "Skipping unsafe cast for partition pruning: {:?}",
441 cast_expr.data_type
442 );
443 Err(datafusion_common::DataFusionError::Plan(format!(
444 "Cast to {:?} not supported for partition pruning",
445 cast_expr.data_type
446 )))
447 }
448 }
449 other => {
450 let partition_expr = Self::convert(other)?;
451 Ok(Operand::Expr(partition_expr))
452 }
453 }
454 }
455
456 fn convert_operator(op: &Operator) -> DfResult<RestrictedOp> {
458 match op {
459 Operator::Eq => Ok(RestrictedOp::Eq),
460 Operator::NotEq => Ok(RestrictedOp::NotEq),
461 Operator::Lt => Ok(RestrictedOp::Lt),
462 Operator::LtEq => Ok(RestrictedOp::LtEq),
463 Operator::Gt => Ok(RestrictedOp::Gt),
464 Operator::GtEq => Ok(RestrictedOp::GtEq),
465 Operator::And => Ok(RestrictedOp::And),
466 Operator::Or => Ok(RestrictedOp::Or),
467 _ => Err(datafusion_common::DataFusionError::Plan(format!(
468 "Unsupported operator: {:?}",
469 op
470 ))),
471 }
472 }
473
474 fn invert_operator(op: &Operator) -> DfResult<RestrictedOp> {
476 let Some(negated) = op.negate() else {
477 return Err(datafusion_common::DataFusionError::Plan(format!(
478 "Cannot invert operator: {:?}",
479 op
480 )));
481 };
482 Self::convert_operator(&negated)
483 }
484
485 fn is_safe_cast_for_partition_pruning(data_type: &DataType) -> bool {
488 match data_type {
489 DataType::Int8 => true,
491 DataType::Int16 => true,
492 DataType::Int32 => true,
493 DataType::Int64 => true,
494 DataType::UInt8 => true,
495 DataType::UInt16 => true,
496 DataType::UInt32 => true,
497 DataType::UInt64 => true,
498
499 DataType::Float32 => true,
501 DataType::Float64 => true,
502
503 DataType::Utf8 => true,
505 DataType::LargeUtf8 => true,
506 DataType::Dictionary(_, value_type) => {
509 Self::is_safe_cast_for_partition_pruning(value_type)
510 }
511
512 DataType::Date32 => true,
514 DataType::Date64 => true,
515 DataType::Timestamp(_, _) => true,
516
517 DataType::Boolean => true,
519
520 _ => false,
522 }
523 }
524}
525
526#[cfg(test)]
527mod tests {
528 use std::sync::Arc;
529
530 use datafusion::arrow::datatypes::{DataType, Field, Schema};
531 use datafusion::common::Column;
532 use datafusion::datasource::DefaultTableSource;
533 use datafusion_expr::{LogicalPlanBuilder, col, lit};
534 use datatypes::value::Value;
535 use partition::expr::{Operand, PartitionExpr, RestrictedOp};
536
537 use super::*;
538
539 fn create_test_table_scan() -> LogicalPlan {
540 let schema = Arc::new(Schema::new(vec![
541 Field::new(
542 "timestamp",
543 DataType::Timestamp(datafusion::arrow::datatypes::TimeUnit::Millisecond, None),
544 false,
545 ),
546 Field::new("user_id", DataType::Int64, false),
547 Field::new("value", DataType::Int64, false),
548 ]));
549
550 let empty_table = datafusion::datasource::empty::EmptyTable::new(schema);
551 let table_source = Arc::new(DefaultTableSource::new(Arc::new(empty_table)));
552
553 LogicalPlanBuilder::scan("test", table_source, None)
554 .unwrap()
555 .build()
556 .unwrap()
557 }
558
559 struct FilterTestCase {
560 name: &'static str,
561 filter_expr: Expr,
562 expected_partition_exprs: Vec<PartitionExpr>,
563 partition_columns: Vec<&'static str>,
564 }
565
566 impl FilterTestCase {
567 fn new(
568 name: &'static str,
569 filter_expr: Expr,
570 expected_partition_exprs: Vec<PartitionExpr>,
571 partition_columns: Vec<&'static str>,
572 ) -> Self {
573 Self {
574 name,
575 filter_expr,
576 expected_partition_exprs,
577 partition_columns,
578 }
579 }
580 }
581
582 fn check_partition_expressions(cases: Vec<FilterTestCase>) {
584 for case in cases {
585 let table_scan = create_test_table_scan();
586 let filter = case.filter_expr.clone();
587
588 let plan = LogicalPlanBuilder::from(table_scan)
589 .filter(filter)
590 .unwrap()
591 .build()
592 .unwrap();
593
594 let partition_columns: Vec<String> = case
595 .partition_columns
596 .iter()
597 .map(|s| s.to_string())
598 .collect();
599 let partition_exprs =
600 PredicateExtractor::extract_partition_expressions(&plan, &partition_columns)
601 .unwrap();
602 let expected = case.expected_partition_exprs.clone();
603 assert_eq!(
604 partition_exprs, expected,
605 "Test case '{}': expected partition expressions {:?}, got {:?}",
606 case.name, expected, partition_exprs
607 );
608 }
609 }
610
611 #[test]
612 fn test_extracts_table_scan_filters() {
613 let table_scan = create_test_table_scan();
614 let filter = col("user_id").gt_eq(lit(100i64));
615 let LogicalPlan::TableScan(scan) = table_scan else {
616 panic!("expected test table scan");
617 };
618 let plan = LogicalPlan::TableScan(datafusion_expr::logical_plan::TableScan {
619 filters: vec![filter],
620 ..scan
621 });
622
623 let partition_exprs =
624 PredicateExtractor::extract_partition_expressions(&plan, &["user_id".to_string()])
625 .unwrap();
626
627 assert_eq!(
628 partition_exprs,
629 vec![PartitionExpr::new(
630 Operand::Column("user_id".to_string()),
631 RestrictedOp::GtEq,
632 Operand::Value(Value::Int64(100)),
633 )]
634 );
635 }
636
637 #[test]
638 fn test_dictionary_cast_preserves_partition_constraint() {
639 let dictionary_type =
640 DataType::Dictionary(Box::new(DataType::UInt32), Box::new(DataType::Utf8));
641 let filter = col("tag").eq(Expr::Cast(datafusion_expr::expr::Cast {
642 expr: Box::new(lit("b")),
643 data_type: dictionary_type,
644 }));
645
646 let partition_expr = DataFusionExprConverter::convert(&filter).unwrap();
647 assert_eq!(
648 partition_expr,
649 PartitionExpr::new(
650 Operand::Column("tag".to_string()),
651 RestrictedOp::Eq,
652 Operand::Value(Value::String("b".into())),
653 )
654 );
655 }
656
657 #[test]
658 fn test_combines_table_scan_filters_as_conjunction() {
659 let table_scan = create_test_table_scan();
660 let filter_a = col("user_id").eq(lit(10i64));
661 let filter_b = col("value").eq(lit(20i64));
662 let LogicalPlan::TableScan(scan) = table_scan else {
663 panic!("expected test table scan");
664 };
665 let plan = LogicalPlan::TableScan(datafusion_expr::logical_plan::TableScan {
666 filters: vec![filter_a, filter_b],
667 ..scan
668 });
669
670 let partition_exprs = PredicateExtractor::extract_partition_expressions(
671 &plan,
672 &["user_id".to_string(), "value".to_string()],
673 )
674 .unwrap();
675
676 assert_eq!(
677 partition_exprs,
678 vec![PartitionExpr::new(
679 Operand::Expr(PartitionExpr::new(
680 Operand::Column("user_id".to_string()),
681 RestrictedOp::Eq,
682 Operand::Value(Value::Int64(10)),
683 )),
684 RestrictedOp::And,
685 Operand::Expr(PartitionExpr::new(
686 Operand::Column("value".to_string()),
687 RestrictedOp::Eq,
688 Operand::Value(Value::Int64(20)),
689 )),
690 )]
691 );
692 }
693
694 #[test]
695 fn test_basic_constraints_extraction() {
696 let cases = vec![
697 FilterTestCase::new(
698 "non_partition_column_ignored",
699 col("value").gt_eq(lit(100i64)),
700 vec![],
701 vec!["user_id"],
702 ),
703 FilterTestCase::new(
704 "simple_constraint",
705 col("user_id").gt_eq(lit(100i64)),
706 vec![PartitionExpr::new(
707 Operand::Column("user_id".to_string()),
708 RestrictedOp::GtEq,
709 Operand::Value(Value::Int64(100)),
710 )],
711 vec!["user_id"],
712 ),
713 FilterTestCase::new(
714 "or_expression",
715 col("user_id")
716 .eq(lit(100i64))
717 .or(col("user_id").eq(lit(200i64))),
718 vec![PartitionExpr::new(
719 Operand::Expr(PartitionExpr::new(
720 Operand::Column("user_id".to_string()),
721 RestrictedOp::Eq,
722 Operand::Value(Value::Int64(100)),
723 )),
724 RestrictedOp::Or,
725 Operand::Expr(PartitionExpr::new(
726 Operand::Column("user_id".to_string()),
727 RestrictedOp::Eq,
728 Operand::Value(Value::Int64(200)),
729 )),
730 )],
731 vec!["user_id"],
732 ),
733 FilterTestCase::new(
734 "complex_and_or",
735 col("user_id")
736 .gt_eq(lit(100i64))
737 .and(col("user_id").lt(lit(200i64)))
738 .or(col("user_id")
739 .gt_eq(lit(300i64))
740 .and(col("user_id").lt(lit(400i64)))),
741 vec![PartitionExpr::new(
742 Operand::Expr(PartitionExpr::new(
743 Operand::Expr(PartitionExpr::new(
744 Operand::Column("user_id".to_string()),
745 RestrictedOp::GtEq,
746 Operand::Value(Value::Int64(100)),
747 )),
748 RestrictedOp::And,
749 Operand::Expr(PartitionExpr::new(
750 Operand::Column("user_id".to_string()),
751 RestrictedOp::Lt,
752 Operand::Value(Value::Int64(200)),
753 )),
754 )),
755 RestrictedOp::Or,
756 Operand::Expr(PartitionExpr::new(
757 Operand::Expr(PartitionExpr::new(
758 Operand::Column("user_id".to_string()),
759 RestrictedOp::GtEq,
760 Operand::Value(Value::Int64(300)),
761 )),
762 RestrictedOp::And,
763 Operand::Expr(PartitionExpr::new(
764 Operand::Column("user_id".to_string()),
765 RestrictedOp::Lt,
766 Operand::Value(Value::Int64(400)),
767 )),
768 )),
769 )],
770 vec!["user_id"],
771 ),
772 ];
773 check_partition_expressions(cases);
774 }
775
776 #[test]
777 fn test_alias_expressions() {
778 let cases = vec![
779 FilterTestCase::new(
780 "simple_alias",
781 col("user_id").alias("uid").eq(lit(100i64)),
782 vec![PartitionExpr::new(
783 Operand::Column("user_id".to_string()),
784 RestrictedOp::Eq,
785 Operand::Value(Value::Int64(100)),
786 )],
787 vec!["user_id"],
788 ),
789 FilterTestCase::new(
790 "nested_alias",
791 col("user_id").alias("uid").alias("u").gt_eq(lit(50i64)),
792 vec![PartitionExpr::new(
793 Operand::Column("user_id".to_string()),
794 RestrictedOp::GtEq,
795 Operand::Value(Value::Int64(50)),
796 )],
797 vec!["user_id"],
798 ),
799 FilterTestCase::new(
800 "complex_alias_with_and_or",
801 col("user_id")
802 .alias("uid")
803 .gt_eq(lit(100i64))
804 .and(col("user_id").alias("u").lt(lit(200i64)))
805 .or(col("user_id").alias("id").eq(lit(300i64))),
806 vec![PartitionExpr::new(
807 Operand::Expr(PartitionExpr::new(
808 Operand::Expr(PartitionExpr::new(
809 Operand::Column("user_id".to_string()),
810 RestrictedOp::GtEq,
811 Operand::Value(Value::Int64(100)),
812 )),
813 RestrictedOp::And,
814 Operand::Expr(PartitionExpr::new(
815 Operand::Column("user_id".to_string()),
816 RestrictedOp::Lt,
817 Operand::Value(Value::Int64(200)),
818 )),
819 )),
820 RestrictedOp::Or,
821 Operand::Expr(PartitionExpr::new(
822 Operand::Column("user_id".to_string()),
823 RestrictedOp::Eq,
824 Operand::Value(Value::Int64(300)),
825 )),
826 )],
827 vec!["user_id"],
828 ),
829 ];
830 check_partition_expressions(cases);
831 }
832
833 #[test]
834 fn test_inlist_expressions() {
835 let cases = vec![
836 FilterTestCase::new(
837 "simple_inlist",
838 col("user_id").in_list(vec![lit(100i64), lit(200i64), lit(300i64)], false),
839 vec![PartitionExpr::new(
840 Operand::Expr(PartitionExpr::new(
841 Operand::Expr(PartitionExpr::new(
842 Operand::Column("user_id".to_string()),
843 RestrictedOp::Eq,
844 Operand::Value(Value::Int64(100)),
845 )),
846 RestrictedOp::Or,
847 Operand::Expr(PartitionExpr::new(
848 Operand::Column("user_id".to_string()),
849 RestrictedOp::Eq,
850 Operand::Value(Value::Int64(200)),
851 )),
852 )),
853 RestrictedOp::Or,
854 Operand::Expr(PartitionExpr::new(
855 Operand::Column("user_id".to_string()),
856 RestrictedOp::Eq,
857 Operand::Value(Value::Int64(300)),
858 )),
859 )],
860 vec!["user_id"],
861 ),
862 FilterTestCase::new(
863 "negated_inlist",
864 col("user_id").in_list(vec![lit(100i64), lit(200i64)], true),
865 vec![PartitionExpr::new(
866 Operand::Expr(PartitionExpr::new(
867 Operand::Column("user_id".to_string()),
868 RestrictedOp::NotEq,
869 Operand::Value(Value::Int64(100)),
870 )),
871 RestrictedOp::And,
872 Operand::Expr(PartitionExpr::new(
873 Operand::Column("user_id".to_string()),
874 RestrictedOp::NotEq,
875 Operand::Value(Value::Int64(200)),
876 )),
877 )],
878 vec!["user_id"],
879 ),
880 FilterTestCase::new(
881 "inlist_with_alias",
882 col("user_id")
883 .alias("uid")
884 .in_list(vec![lit(100i64), lit(200i64)], false),
885 vec![PartitionExpr::new(
886 Operand::Expr(PartitionExpr::new(
887 Operand::Column("user_id".to_string()),
888 RestrictedOp::Eq,
889 Operand::Value(Value::Int64(100)),
890 )),
891 RestrictedOp::Or,
892 Operand::Expr(PartitionExpr::new(
893 Operand::Column("user_id".to_string()),
894 RestrictedOp::Eq,
895 Operand::Value(Value::Int64(200)),
896 )),
897 )],
898 vec!["user_id"],
899 ),
900 ];
901 check_partition_expressions(cases);
902 }
903
904 #[test]
905 fn test_between_expressions() {
906 let cases = vec![
907 FilterTestCase::new(
908 "simple_between",
909 col("user_id").between(lit(100i64), lit(200i64)),
910 vec![PartitionExpr::new(
911 Operand::Expr(PartitionExpr::new(
912 Operand::Column("user_id".to_string()),
913 RestrictedOp::GtEq,
914 Operand::Value(Value::Int64(100)),
915 )),
916 RestrictedOp::And,
917 Operand::Expr(PartitionExpr::new(
918 Operand::Column("user_id".to_string()),
919 RestrictedOp::LtEq,
920 Operand::Value(Value::Int64(200)),
921 )),
922 )],
923 vec!["user_id"],
924 ),
925 FilterTestCase::new(
926 "negated_between",
927 Expr::Between(datafusion_expr::Between {
928 expr: Box::new(col("user_id")),
929 negated: true,
930 low: Box::new(lit(100i64)),
931 high: Box::new(lit(200i64)),
932 }),
933 vec![PartitionExpr::new(
934 Operand::Expr(PartitionExpr::new(
935 Operand::Column("user_id".to_string()),
936 RestrictedOp::Lt,
937 Operand::Value(Value::Int64(100)),
938 )),
939 RestrictedOp::Or,
940 Operand::Expr(PartitionExpr::new(
941 Operand::Column("user_id".to_string()),
942 RestrictedOp::Gt,
943 Operand::Value(Value::Int64(200)),
944 )),
945 )],
946 vec!["user_id"],
947 ),
948 FilterTestCase::new(
949 "between_with_alias",
950 col("user_id")
951 .alias("uid")
952 .between(lit(100i64), lit(200i64)),
953 vec![PartitionExpr::new(
954 Operand::Expr(PartitionExpr::new(
955 Operand::Column("user_id".to_string()),
956 RestrictedOp::GtEq,
957 Operand::Value(Value::Int64(100)),
958 )),
959 RestrictedOp::And,
960 Operand::Expr(PartitionExpr::new(
961 Operand::Column("user_id".to_string()),
962 RestrictedOp::LtEq,
963 Operand::Value(Value::Int64(200)),
964 )),
965 )],
966 vec!["user_id"],
967 ),
968 ];
969 check_partition_expressions(cases);
970 }
971
972 #[test]
973 fn test_null_expressions() {
974 let cases = vec![
975 FilterTestCase::new(
976 "is_null",
977 col("user_id").is_null(),
978 vec![PartitionExpr::new(
979 Operand::Column("user_id".to_string()),
980 RestrictedOp::Eq,
981 Operand::Value(Value::Null),
982 )],
983 vec!["user_id"],
984 ),
985 FilterTestCase::new(
986 "is_not_null",
987 col("user_id").is_not_null(),
988 vec![PartitionExpr::new(
989 Operand::Column("user_id".to_string()),
990 RestrictedOp::NotEq,
991 Operand::Value(Value::Null),
992 )],
993 vec!["user_id"],
994 ),
995 FilterTestCase::new(
996 "null_with_alias",
997 col("user_id").alias("uid").is_null(),
998 vec![PartitionExpr::new(
999 Operand::Column("user_id".to_string()),
1000 RestrictedOp::Eq,
1001 Operand::Value(Value::Null),
1002 )],
1003 vec!["user_id"],
1004 ),
1005 ];
1006 check_partition_expressions(cases);
1007 }
1008
1009 #[test]
1010 fn test_cast_expressions() {
1011 let cases = vec![
1012 FilterTestCase::new(
1013 "safe_cast",
1014 Expr::Cast(datafusion_expr::Cast {
1015 expr: Box::new(col("user_id")),
1016 data_type: DataType::Int64,
1017 })
1018 .eq(lit(100i64)),
1019 vec![PartitionExpr::new(
1020 Operand::Column("user_id".to_string()),
1021 RestrictedOp::Eq,
1022 Operand::Value(Value::Int64(100)),
1023 )],
1024 vec!["user_id"],
1025 ),
1026 FilterTestCase::new(
1027 "cast_with_alias",
1028 Expr::Cast(datafusion_expr::Cast {
1029 expr: Box::new(col("user_id").alias("uid")),
1030 data_type: DataType::Int64,
1031 })
1032 .eq(lit(100i64)),
1033 vec![PartitionExpr::new(
1034 Operand::Column("user_id".to_string()),
1035 RestrictedOp::Eq,
1036 Operand::Value(Value::Int64(100)),
1037 )],
1038 vec!["user_id"],
1039 ),
1040 FilterTestCase::new(
1041 "unsafe_cast",
1042 Expr::Cast(datafusion_expr::Cast {
1043 expr: Box::new(col("user_id")),
1044 data_type: DataType::List(std::sync::Arc::new(
1045 datafusion::arrow::datatypes::Field::new("item", DataType::Int32, true),
1046 )),
1047 })
1048 .eq(lit(100i64)),
1049 vec![],
1050 vec!["user_id"],
1051 ),
1052 ];
1053 check_partition_expressions(cases);
1054 }
1055
1056 #[test]
1057 fn test_not_expressions() {
1058 let cases = vec![
1059 FilterTestCase::new(
1060 "not_equality",
1061 Expr::Not(Box::new(col("user_id").eq(lit(100i64)))),
1062 vec![PartitionExpr::new(
1063 Operand::Column("user_id".to_string()),
1064 RestrictedOp::NotEq,
1065 Operand::Value(Value::Int64(100)),
1066 )],
1067 vec!["user_id"],
1068 ),
1069 FilterTestCase::new(
1070 "not_comparison",
1071 Expr::Not(Box::new(col("user_id").lt(lit(100i64)))),
1072 vec![PartitionExpr::new(
1073 Operand::Column("user_id".to_string()),
1074 RestrictedOp::GtEq,
1075 Operand::Value(Value::Int64(100)),
1076 )],
1077 vec!["user_id"],
1078 ),
1079 FilterTestCase::new(
1080 "not_is_null",
1081 Expr::Not(Box::new(col("user_id").is_null())),
1082 vec![PartitionExpr::new(
1083 Operand::Column("user_id".to_string()),
1084 RestrictedOp::NotEq,
1085 Operand::Value(Value::Null),
1086 )],
1087 vec!["user_id"],
1088 ),
1089 FilterTestCase::new(
1090 "not_with_alias",
1091 Expr::Not(Box::new(col("user_id").alias("uid").eq(lit(100i64)))),
1092 vec![PartitionExpr::new(
1093 Operand::Column("user_id".to_string()),
1094 RestrictedOp::NotEq,
1095 Operand::Value(Value::Int64(100)),
1096 )],
1097 vec!["user_id"],
1098 ),
1099 ];
1100 check_partition_expressions(cases);
1101 }
1102
1103 #[test]
1104 fn test_edge_cases() {
1105 let cases = vec![
1106 FilterTestCase::new(
1107 "qualified_column_name",
1108 {
1109 let qualified_col = Expr::Column(Column::new(Some("test"), "user_id"));
1110 qualified_col.eq(lit(100i64))
1111 },
1112 vec![PartitionExpr::new(
1113 Operand::Column("user_id".to_string()),
1114 RestrictedOp::Eq,
1115 Operand::Value(Value::Int64(100)),
1116 )],
1117 vec!["user_id"],
1118 ),
1119 FilterTestCase::new(
1120 "comprehensive_combinations",
1121 {
1122 let in_expr = col("user_id")
1123 .alias("uid")
1124 .in_list(vec![lit(100i64), lit(200i64)], false);
1125 let cast_expr = Expr::Cast(datafusion_expr::Cast {
1126 expr: Box::new(col("user_id")),
1127 data_type: DataType::Int64,
1128 });
1129 let between_expr = cast_expr.between(lit(300i64), lit(400i64));
1130 in_expr.or(between_expr)
1131 },
1132 vec![PartitionExpr::new(
1133 Operand::Expr(PartitionExpr::new(
1134 Operand::Expr(PartitionExpr::new(
1135 Operand::Column("user_id".to_string()),
1136 RestrictedOp::Eq,
1137 Operand::Value(Value::Int64(100)),
1138 )),
1139 RestrictedOp::Or,
1140 Operand::Expr(PartitionExpr::new(
1141 Operand::Column("user_id".to_string()),
1142 RestrictedOp::Eq,
1143 Operand::Value(Value::Int64(200)),
1144 )),
1145 )),
1146 RestrictedOp::Or,
1147 Operand::Expr(PartitionExpr::new(
1148 Operand::Expr(PartitionExpr::new(
1149 Operand::Column("user_id".to_string()),
1150 RestrictedOp::GtEq,
1151 Operand::Value(Value::Int64(300)),
1152 )),
1153 RestrictedOp::And,
1154 Operand::Expr(PartitionExpr::new(
1155 Operand::Column("user_id".to_string()),
1156 RestrictedOp::LtEq,
1157 Operand::Value(Value::Int64(400)),
1158 )),
1159 )),
1160 )],
1161 vec!["user_id"],
1162 ),
1163 ];
1164 check_partition_expressions(cases);
1165 }
1166
1167 #[test]
1168 fn test_mixed_partition_non_partition_expressions() {
1169 let cases = vec![
1170 FilterTestCase::new(
1172 "mixed_and_expression",
1173 col("user_id")
1174 .eq(lit(100i64))
1175 .and(col("value").gt(lit(50i64))),
1176 vec![PartitionExpr::new(
1177 Operand::Column("user_id".to_string()),
1178 RestrictedOp::Eq,
1179 Operand::Value(Value::Int64(100)),
1180 )],
1181 vec!["user_id"],
1182 ),
1183 FilterTestCase::new(
1185 "mixed_or_expression",
1186 col("user_id")
1187 .between(lit(1i64), lit(10i64))
1188 .or(col("value").gt(lit(50i64))),
1189 vec![], vec!["user_id"],
1191 ),
1192 FilterTestCase::new(
1194 "complex_mixed_and",
1195 col("user_id")
1196 .gt_eq(lit(100i64))
1197 .and(col("value").eq(lit(200i64)))
1198 .and(col("timestamp").lt(lit(1000i64))),
1199 vec![
1200 PartitionExpr::new(
1201 Operand::Column("user_id".to_string()),
1202 RestrictedOp::GtEq,
1203 Operand::Value(Value::Int64(100)),
1204 ),
1205 PartitionExpr::new(
1206 Operand::Column("timestamp".to_string()),
1207 RestrictedOp::Lt,
1208 Operand::Value(Value::Int64(1000)),
1209 ),
1210 ],
1211 vec!["user_id", "timestamp"], ),
1213 FilterTestCase::new(
1215 "pure_partition_and",
1216 col("user_id")
1217 .gt_eq(lit(100i64))
1218 .and(col("timestamp").lt(lit(1000i64))),
1219 vec![PartitionExpr::new(
1220 Operand::Expr(PartitionExpr::new(
1221 Operand::Column("user_id".to_string()),
1222 RestrictedOp::GtEq,
1223 Operand::Value(Value::Int64(100)),
1224 )),
1225 RestrictedOp::And,
1226 Operand::Expr(PartitionExpr::new(
1227 Operand::Column("timestamp".to_string()),
1228 RestrictedOp::Lt,
1229 Operand::Value(Value::Int64(1000)),
1230 )),
1231 )],
1232 vec!["user_id", "timestamp"],
1233 ),
1234 FilterTestCase::new(
1236 "pure_partition_or",
1237 col("user_id")
1238 .eq(lit(100i64))
1239 .or(col("user_id").eq(lit(200i64))),
1240 vec![PartitionExpr::new(
1241 Operand::Expr(PartitionExpr::new(
1242 Operand::Column("user_id".to_string()),
1243 RestrictedOp::Eq,
1244 Operand::Value(Value::Int64(100)),
1245 )),
1246 RestrictedOp::Or,
1247 Operand::Expr(PartitionExpr::new(
1248 Operand::Column("user_id".to_string()),
1249 RestrictedOp::Eq,
1250 Operand::Value(Value::Int64(200)),
1251 )),
1252 )],
1253 vec!["user_id"],
1254 ),
1255 FilterTestCase::new(
1257 "pure_non_partition",
1258 col("value").gt_eq(lit(100i64)),
1259 vec![], vec!["user_id"],
1261 ),
1262 FilterTestCase::new(
1264 "nested_mixed_expression",
1265 (col("user_id")
1266 .eq(lit(100i64))
1267 .and(col("value").gt(lit(50i64))))
1268 .or(col("user_id").eq(lit(200i64))),
1269 vec![], vec!["user_id"],
1271 ),
1272 FilterTestCase::new(
1274 "and_with_nested_mixed_or",
1275 col("user_id")
1276 .gt_eq(lit(100i64))
1277 .and(col("value").eq(lit(1i64)).or(col("value").eq(lit(2i64)))),
1278 vec![PartitionExpr::new(
1279 Operand::Column("user_id".to_string()),
1280 RestrictedOp::GtEq,
1281 Operand::Value(Value::Int64(100)),
1282 )],
1283 vec!["user_id"],
1284 ),
1285 ];
1286 check_partition_expressions(cases);
1287 }
1288}