This is an automated email from the ASF dual-hosted git repository.

jonah pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/datafusion.git


The following commit(s) were added to refs/heads/main by this push:
     new 8b244eee3a Resolve empty relation opt for join types (#11066)
8b244eee3a is described below

commit 8b244eee3a731bc37d0a87efe455e47d58e68846
Author: Lorrens Pantelis <[email protected]>
AuthorDate: Tue Jun 25 04:14:55 2024 +0200

    Resolve empty relation opt for join types (#11066)
    
    * handle left/right anti with empty join_table & added tests for those
    
    * add tests and only resolve cases 1,2,3 of issue
    
    * forgot to change a test
    
    * Update datafusion/optimizer/src/propagate_empty_relation.rs
    
    Co-authored-by: Jonah Gao <[email protected]>
    
    ---------
    
    Co-authored-by: Andrew Lamb <[email protected]>
    Co-authored-by: Jonah Gao <[email protected]>
---
 .../optimizer/src/propagate_empty_relation.rs      | 57 ++++++++++++++++++++--
 datafusion/sqllogictest/test_files/joins.slt       | 30 ++++++++++++
 datafusion/sqllogictest/test_files/subquery.slt    |  5 +-
 3 files changed, 83 insertions(+), 9 deletions(-)

diff --git a/datafusion/optimizer/src/propagate_empty_relation.rs 
b/datafusion/optimizer/src/propagate_empty_relation.rs
index 6d7521dac7..63b357510f 100644
--- a/datafusion/optimizer/src/propagate_empty_relation.rs
+++ b/datafusion/optimizer/src/propagate_empty_relation.rs
@@ -88,9 +88,6 @@ impl OptimizerRule for PropagateEmptyRelation {
 
             LogicalPlan::Join(ref join) => {
                 // TODO: For Join, more join type need to be careful:
-                // For LeftAnti Join, if the right side is empty, the Join 
result is left side(should exclude null ??).
-                // For RightAnti Join, if the left side is empty, the Join 
result is right side(should exclude null ??).
-                // For Full Join, only both sides are empty, the Join result 
is empty.
                 // For LeftOut/Full Join, if the right side is empty, the Join 
can be eliminated with a Projection with left side
                 // columns + right side columns replaced with null values.
                 // For RightOut/Full Join, if the left side is empty, the Join 
can be eliminated with a Projection with right side
@@ -98,6 +95,13 @@ impl OptimizerRule for PropagateEmptyRelation {
                 let (left_empty, right_empty) = 
binary_plan_children_is_empty(&plan)?;
 
                 match join.join_type {
+                    // For Full Join, only both sides are empty, the Join 
result is empty.
+                    JoinType::Full if left_empty && right_empty => 
Ok(Transformed::yes(
+                        LogicalPlan::EmptyRelation(EmptyRelation {
+                            produce_one_row: false,
+                            schema: join.schema.clone(),
+                        }),
+                    )),
                     JoinType::Inner if left_empty || right_empty => 
Ok(Transformed::yes(
                         LogicalPlan::EmptyRelation(EmptyRelation {
                             produce_one_row: false,
@@ -134,13 +138,19 @@ impl OptimizerRule for PropagateEmptyRelation {
                             schema: join.schema.clone(),
                         }),
                     )),
+                    JoinType::LeftAnti if right_empty => {
+                        Ok(Transformed::yes((*join.left).clone()))
+                    }
+                    JoinType::RightAnti if left_empty => {
+                        Ok(Transformed::yes((*join.right).clone()))
+                    }
                     JoinType::RightAnti if right_empty => Ok(Transformed::yes(
                         LogicalPlan::EmptyRelation(EmptyRelation {
                             produce_one_row: false,
                             schema: join.schema.clone(),
                         }),
                     )),
-                    _ => Ok(Transformed::no(LogicalPlan::Join(join.clone()))),
+                    _ => Ok(Transformed::no(plan)),
                 }
             }
             LogicalPlan::Aggregate(ref agg) => {
@@ -467,8 +477,39 @@ mod tests {
         assert_together_optimized_plan(plan, expected, eq)
     }
 
+    // TODO: fix this long name
+    fn assert_anti_join_empty_join_table_is_base_table(
+        anti_left_join: bool,
+    ) -> Result<()> {
+        // if we have an anti join with an empty join table, then the result 
is the base_table
+        let (left, right, join_type, expected) = if anti_left_join {
+            let left = test_table_scan()?;
+            let right = LogicalPlanBuilder::from(test_table_scan()?)
+                .filter(Expr::Literal(ScalarValue::Boolean(Some(false))))?
+                .build()?;
+            let expected = left.display_indent().to_string();
+            (left, right, JoinType::LeftAnti, expected)
+        } else {
+            let right = test_table_scan()?;
+            let left = LogicalPlanBuilder::from(test_table_scan()?)
+                .filter(Expr::Literal(ScalarValue::Boolean(Some(false))))?
+                .build()?;
+            let expected = right.display_indent().to_string();
+            (left, right, JoinType::RightAnti, expected)
+        };
+
+        let plan = LogicalPlanBuilder::from(left)
+            .join_using(right, join_type, 
vec![Column::from_name("a".to_string())])?
+            .build()?;
+
+        assert_together_optimized_plan(plan, &expected, true)
+    }
+
     #[test]
     fn test_join_empty_propagation_rules() -> Result<()> {
+        // test full join with empty left and empty right
+        assert_empty_left_empty_right_lp(true, true, JoinType::Full, true)?;
+
         // test left join with empty left
         assert_empty_left_empty_right_lp(true, false, JoinType::Left, true)?;
 
@@ -491,7 +532,13 @@ mod tests {
         assert_empty_left_empty_right_lp(true, false, JoinType::LeftAnti, 
true)?;
 
         // test right anti join empty right
-        assert_empty_left_empty_right_lp(false, true, JoinType::RightAnti, 
true)
+        assert_empty_left_empty_right_lp(false, true, JoinType::RightAnti, 
true)?;
+
+        // test left anti join empty right
+        assert_anti_join_empty_join_table_is_base_table(true)?;
+
+        // test right anti join empty left
+        assert_anti_join_empty_join_table_is_base_table(false)
     }
 
     #[test]
diff --git a/datafusion/sqllogictest/test_files/joins.slt 
b/datafusion/sqllogictest/test_files/joins.slt
index c2949b98aa..501ae49774 100644
--- a/datafusion/sqllogictest/test_files/joins.slt
+++ b/datafusion/sqllogictest/test_files/joins.slt
@@ -3781,3 +3781,33 @@ EXPLAIN SELECT * FROM (
 ) AS a RIGHT ANTI JOIN (SELECT 1 AS a WHERE 1=0) AS b ON a.a=b.a;
 ----
 logical_plan EmptyRelation
+
+# FULL OUTER join with empty left and empty right table
+query TT
+EXPLAIN SELECT * FROM (
+       SELECT 1 as a WHERE 1=0
+) AS a FULL JOIN (SELECT 1 AS a WHERE 1=0) AS b ON a.a=b.a;
+----
+logical_plan EmptyRelation
+
+# Left ANTI join with empty right table 
+query TT
+EXPLAIN SELECT * FROM (
+       SELECT 1 as a
+) AS a LEFT ANTI JOIN (SELECT 1 AS a WHERE 1=0) as b ON a.a=b.a;
+----
+logical_plan
+01)SubqueryAlias: a
+02)--Projection: Int64(1) AS a
+03)----EmptyRelation
+
+# Right ANTI join with empty left table 
+query TT
+EXPLAIN SELECT * FROM (
+       SELECT 1 as a WHERE 1=0
+) AS a RIGHT ANTI JOIN (SELECT 1 AS a) as b ON a.a=b.a;
+----
+logical_plan
+01)SubqueryAlias: b
+02)--Projection: Int64(1) AS a
+03)----EmptyRelation
diff --git a/datafusion/sqllogictest/test_files/subquery.slt 
b/datafusion/sqllogictest/test_files/subquery.slt
index f56729f85e..30b3631681 100644
--- a/datafusion/sqllogictest/test_files/subquery.slt
+++ b/datafusion/sqllogictest/test_files/subquery.slt
@@ -638,10 +638,7 @@ SELECT t1_id, t1_name FROM t1 WHERE EXISTS (SELECT * FROM 
t2 WHERE t2_id = t1_id
 query TT
 explain SELECT t1_id, t1_name FROM t1 WHERE NOT EXISTS (SELECT * FROM t2 WHERE 
t2_id = t1_id limit 0)
 ----
-logical_plan
-01)LeftAnti Join: t1.t1_id = __correlated_sq_1.t2_id
-02)--TableScan: t1 projection=[t1_id, t1_name]
-03)--EmptyRelation
+logical_plan TableScan: t1 projection=[t1_id, t1_name]
 
 query IT rowsort
 SELECT t1_id, t1_name FROM t1 WHERE NOT EXISTS (SELECT * FROM t2 WHERE t2_id = 
t1_id limit 0)


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to