@@ -35,6 +35,7 @@ use datafusion_expr::expr_rewriter::replace_col;
3535use datafusion_expr:: logical_plan:: { Join , JoinType , LogicalPlan , TableScan , Union } ;
3636use datafusion_expr:: utils:: {
3737 conjunction, expr_to_columns, split_conjunction, split_conjunction_owned,
38+ INFERRED_PREDICATE_ALIAS ,
3839} ;
3940use datafusion_expr:: {
4041 and, or, BinaryExpr , Expr , Filter , Operator , Projection , TableProviderFilterPushDown ,
@@ -463,7 +464,11 @@ fn push_down_all_join(
463464 } else if right_preserved && checker. is_right_only ( & predicate) {
464465 right_push. push ( predicate) ;
465466 } 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 ) ) ;
467472 }
468473 }
469474
@@ -1487,6 +1492,53 @@ mod tests {
14871492 } } ;
14881493 }
14891494
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+
14901542 #[ test]
14911543 fn filter_before_projection ( ) -> Result < ( ) > {
14921544 let table_scan = test_table_scan ( ) ?;
0 commit comments