@@ -35,6 +35,7 @@ use datafusion_expr::expr_rewriter::replace_col;
35
35
use datafusion_expr:: logical_plan:: { Join , JoinType , LogicalPlan , TableScan , Union } ;
36
36
use datafusion_expr:: utils:: {
37
37
conjunction, expr_to_columns, split_conjunction, split_conjunction_owned,
38
+ INFERRED_PREDICATE_ALIAS ,
38
39
} ;
39
40
use datafusion_expr:: {
40
41
and, or, BinaryExpr , Expr , Filter , Operator , Projection , TableProviderFilterPushDown ,
@@ -463,7 +464,11 @@ fn push_down_all_join(
463
464
} else if right_preserved && checker. is_right_only ( & predicate) {
464
465
right_push. push ( predicate) ;
465
466
} else {
466
- join_conditions. push ( predicate) ;
467
+ // Mark inferred predicates so subsequent optimization passes do not
468
+ // treat them as additional equijoin keys. They should remain as
469
+ // residual join filters to enable dynamic filtering without
470
+ // widening the join key set.
471
+ join_conditions. push ( predicate. alias ( INFERRED_PREDICATE_ALIAS ) ) ;
467
472
}
468
473
}
469
474
@@ -1487,6 +1492,53 @@ mod tests {
1487
1492
} } ;
1488
1493
}
1489
1494
1495
+ #[ test]
1496
+ fn inferred_predicate_stays_in_filter ( ) -> Result < ( ) > {
1497
+ use datafusion_common:: NullEquality ;
1498
+ use datafusion_expr:: logical_plan:: JoinConstraint ;
1499
+
1500
+ let left = test_table_scan_with_name ( "l" ) ?;
1501
+ let right = test_table_scan_with_name ( "r" ) ?;
1502
+
1503
+ let join = Join :: try_new (
1504
+ Arc :: new ( left) ,
1505
+ Arc :: new ( right) ,
1506
+ vec ! [ ] ,
1507
+ None ,
1508
+ JoinType :: Left ,
1509
+ JoinConstraint :: On ,
1510
+ NullEquality :: NullEqualsNull ,
1511
+ ) ?;
1512
+
1513
+ let inferred = col ( "l.a" ) . eq ( col ( "r.a" ) ) ;
1514
+ let Transformed { data : plan, .. } =
1515
+ push_down_all_join ( vec ! [ ] , vec ! [ inferred] , join, vec ! [ ] ) ?;
1516
+
1517
+ let join = match plan {
1518
+ LogicalPlan :: Join ( j) => j,
1519
+ _ => panic ! ( "expected join" ) ,
1520
+ } ;
1521
+
1522
+ assert ! ( join. on. is_empty( ) ) ;
1523
+ let filter = join. filter . clone ( ) . expect ( "expected filter" ) ;
1524
+ match filter {
1525
+ Expr :: Alias ( alias) => assert_eq ! ( alias. name, INFERRED_PREDICATE_ALIAS ) ,
1526
+ _ => panic ! ( "expected aliased filter" ) ,
1527
+ }
1528
+
1529
+ let optimizer = Optimizer :: with_rules ( vec ! [
1530
+ Arc :: new( SimplifyExpressions :: new( ) ) ,
1531
+ Arc :: new( crate :: extract_equijoin_predicate:: ExtractEquijoinPredicate :: new( ) ) ,
1532
+ ] ) ;
1533
+ let optimized = optimizer. optimize ( LogicalPlan :: Join ( join) , & OptimizerContext :: new ( ) , observe) ?;
1534
+ if let LogicalPlan :: Join ( j) = optimized {
1535
+ assert ! ( j. on. is_empty( ) ) ;
1536
+ } else {
1537
+ panic ! ( "expected join" ) ;
1538
+ }
1539
+ Ok ( ( ) )
1540
+ }
1541
+
1490
1542
#[ test]
1491
1543
fn filter_before_projection ( ) -> Result < ( ) > {
1492
1544
let table_scan = test_table_scan ( ) ?;
0 commit comments