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

HappenLee pushed a commit to branch 4.1_performance
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/4.1_performance by this push:
     new 213d7221504 (14_1)PushDownJoinOnAssertNumRows support hashJoin (#66384)
213d7221504 is described below

commit 213d722150465d90c7cd0f495dd412b9b5db9b54
Author: minghong <[email protected]>
AuthorDate: Mon Aug 3 19:00:38 2026 +0800

    (14_1)PushDownJoinOnAssertNumRows support hashJoin (#66384)
    
    ### What problem does this PR solve?
    
    Issue Number: close #xxx
    
    Related PR: #xxx
    
    Problem Summary:
    
    ### Release note
    
    None
    
    ### Check List (For Author)
    
    - Test <!-- At least one of them must be included. -->
        - [ ] Regression test
        - [ ] Unit Test
        - [ ] Manual test (add detailed scripts or steps below)
        - [ ] No need to test or manual test. Explain why:
    - [ ] This is a refactor/code format and no logic has been changed.
            - [ ] Previous test can cover this change.
            - [ ] No code files have been changed.
            - [ ] Other reason <!-- Add your reason?  -->
    
    - Behavior changed:
        - [ ] No.
        - [ ] Yes. <!-- Explain the behavior change -->
    
    - Does this need documentation?
        - [ ] No.
    - [ ] Yes. <!-- Add document PR link here. eg:
    https://github.com/apache/doris-website/pull/1214 -->
    
    ### Check List (For Reviewer who merge this PR)
    
    - [ ] Confirm the release note
    - [ ] Confirm test cases
    - [ ] Confirm document
    - [ ] Add branch pick label <!-- Add branch pick label that this PR
    should merge into -->
---
 .../rules/rewrite/PushDownJoinOnAssertNumRows.java |  26 +++-
 .../rewrite/PushDownJoinOnAssertNumRowsTest.java   | 163 +++++++++++++++++++++
 2 files changed, 184 insertions(+), 5 deletions(-)

diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/PushDownJoinOnAssertNumRows.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/PushDownJoinOnAssertNumRows.java
index d45cc5676fe..afc2f281bb1 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/PushDownJoinOnAssertNumRows.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/PushDownJoinOnAssertNumRows.java
@@ -74,6 +74,22 @@ import java.util.stream.Collectors;
  *             `-- bottomJoin(T2.a > x)
  *                   |-- Scan(T2)
  *                   `-- LogicalAssertNumRows(output=(x, ...))
+ *
+ * Case 3: Push down with equi (hash) condition, e.g. scalar subquery
+ * select * from T1 join T2 where T1.b=T2.b and T2.c = (select x from T3 ...)
+ * Before:
+ *     topJoin(T2.c = x)
+ *       |-- bottomJoin(T1.b = T2.b)
+ *       |     |-- Scan(T1)
+ *       |     `-- Scan(T2)
+ *       `-- LogicalAssertNumRows(output=(x, ...))
+ *
+ * After:
+ *     bottomJoin(T1.b = T2.b)
+ *       |-- Scan(T1)
+ *       `-- topJoin(T2.c = x)
+ *             |-- Scan(T2)
+ *             `-- LogicalAssertNumRows(output=(x, ...))
  * </pre>
  */
 public class PushDownJoinOnAssertNumRows extends OneRewriteRuleFactory {
@@ -114,10 +130,8 @@ public class PushDownJoinOnAssertNumRows extends 
OneRewriteRuleFactory {
             return false;
         }
 
-        if (topJoin.getHashJoinConjuncts().isEmpty()) {
-            return topJoin.getOtherJoinConjuncts().size() == 1;
-        }
-        return false;
+        // only one join condition, either hash (equi) or other (non-equi) 
conjunct.
+        return topJoin.getHashJoinConjuncts().size() + 
topJoin.getOtherJoinConjuncts().size() == 1;
     }
 
     private boolean isAssertOneRowEqOrProjectAssertOneRowEq(Plan plan) {
@@ -141,7 +155,9 @@ public class PushDownJoinOnAssertNumRows extends 
OneRewriteRuleFactory {
 
     private Plan pushDownAssertNumRowsJoin(LogicalJoin<?, ?> topJoin) {
         Plan assertBranch = topJoin.right();
-        Expression condition = topJoin.getOtherJoinConjuncts().get(0);
+        Expression condition = topJoin.getHashJoinConjuncts().isEmpty()
+                ? topJoin.getOtherJoinConjuncts().get(0)
+                : topJoin.getHashJoinConjuncts().get(0);
         List<Alias> aliasUsedInConditionFromLeftProject = new ArrayList<>();
         LogicalJoin<? extends Plan, ? extends Plan> bottomJoin;
         if (topJoin.left() instanceof LogicalProject) {
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/PushDownJoinOnAssertNumRowsTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/PushDownJoinOnAssertNumRowsTest.java
index d241433a219..15f9292dfd0 100644
--- 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/PushDownJoinOnAssertNumRowsTest.java
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/PushDownJoinOnAssertNumRowsTest.java
@@ -319,6 +319,169 @@ class PushDownJoinOnAssertNumRowsTest implements 
MemoPatternMatchSupported {
                                                                 
logicalAssertNumRows()))));
     }
 
+    /**
+     * Test push down to right child with equi (hash) condition on the top 
join:
+     * Before:
+     * topJoin(T2.score = x)
+     * |-- bottomJoin(T1.id = T2.sid)
+     * | |-- Scan(T1)
+     * | `-- Scan(T2)
+     * `-- LogicalAssertNumRows(output=(x, ...))
+     *
+     * After:
+     * bottomJoin(T1.id = T2.sid)
+     * |-- Scan(T1)
+     * `-- topJoin(T2.score = x)
+     * |-- Scan(T2)
+     * `-- LogicalAssertNumRows(output=(x, ...))
+     */
+    @Test
+    void testPushDownToRightChildWithHashConjunct() {
+        Plan oneRowRelation = new LogicalPlanBuilder(t3)
+                .limit(1)
+                .build();
+
+        AssertNumRowsElement assertElement = new AssertNumRowsElement(1, "", 
Assertion.EQ);
+        LogicalAssertNumRows<Plan> assertNumRows = new 
LogicalAssertNumRows<>(assertElement, oneRowRelation);
+
+        // Create bottom join: T1 JOIN T2 on T1.id = T2.sid
+        Expression bottomJoinCondition = new EqualTo(t1Slots.get(0), 
t2Slots.get(1));
+
+        LogicalPlan bottomJoin = new LogicalPlanBuilder(t1)
+                .join(t2, JoinType.INNER_JOIN, 
ImmutableList.of(bottomJoinCondition),
+                                ImmutableList.of())
+                .build();
+
+        // Create top join: (T1 JOIN T2) JOIN assertNumRows on T2.score = 
course.name
+        // This references T2 (right child of bottom join) and assertNumRows
+        Expression topJoinCondition = new EqualTo(t2Slots.get(2), 
t3Slots.get(1));
+
+        LogicalPlan root = new LogicalPlanBuilder(bottomJoin)
+                .join(assertNumRows, JoinType.INNER_JOIN, 
ImmutableList.of(topJoinCondition),
+                                ImmutableList.of())
+                .build();
+
+        // Apply the rule
+        PlanChecker.from(MemoTestUtils.createConnectContext(), root)
+                .applyTopDown(new PushDownJoinOnAssertNumRows())
+                .matches(logicalJoin(
+                                logicalOlapScan(),
+                                logicalJoin(
+                                                        logicalOlapScan(),
+                                                        
logicalAssertNumRows())));
+    }
+
+    /**
+     * Test push down to right child with equi (hash) condition and a project
+     * between the top join and the bottom join, mirroring the plan shape of
+     * TPC-DS Q58: store_sales join date_dim, plus a scalar subquery on 
date_dim:
+     * Before:
+     * topJoin(d_week_seq = x)
+     * |-- Project(all output of bottomJoin)
+     * |   `-- bottomJoin(ss_sold_date_sk = d_date_sk)
+     * |       |-- Scan(T1)
+     * |       `-- Scan(T2)
+     * `-- LogicalAssertNumRows(output=(x, ...))
+     *
+     * After:
+     * Project(all output)
+     * `-- bottomJoin(ss_sold_date_sk = d_date_sk)
+     *     |-- Scan(T1)
+     *     `-- topJoin(d_week_seq = x)
+     *         |-- Scan(T2)
+     *         `-- LogicalAssertNumRows(output=(x, ...))
+     */
+    @Test
+    void testPushDownToRightChildWithProjectAndHashConjunct() {
+        Plan oneRowRelation = new LogicalPlanBuilder(t3)
+                .limit(1)
+                .build();
+
+        AssertNumRowsElement assertElement = new AssertNumRowsElement(1, "", 
Assertion.EQ);
+        LogicalAssertNumRows<Plan> assertNumRows = new 
LogicalAssertNumRows<>(assertElement, oneRowRelation);
+
+        // Create bottom join: T1 JOIN T2 on T1.id = T2.sid
+        Expression bottomJoinCondition = new EqualTo(t1Slots.get(0), 
t2Slots.get(1));
+
+        LogicalPlan bottomJoin = new LogicalPlanBuilder(t1)
+                .join(t2, JoinType.INNER_JOIN, 
ImmutableList.of(bottomJoinCondition),
+                                ImmutableList.of())
+                .build();
+
+        // Wrap the bottom join in a project that passes through all its 
output.
+        LogicalProject<Plan> project = new 
LogicalProject<>(ImmutableList.copyOf(bottomJoin.getOutput()), bottomJoin);
+
+        // Create top join: project JOIN assertNumRows on T2.score = 
course.name
+        Expression topJoinCondition = new EqualTo(t2Slots.get(2), 
t3Slots.get(1));
+
+        LogicalPlan root = new LogicalPlanBuilder(project)
+                .join(assertNumRows, JoinType.INNER_JOIN, 
ImmutableList.of(topJoinCondition),
+                                ImmutableList.of())
+                .build();
+
+        // Apply the rule
+        PlanChecker.from(MemoTestUtils.createConnectContext(), root)
+                .applyTopDown(new PushDownJoinOnAssertNumRows())
+                .matches(logicalProject(
+                                logicalJoin(
+                                                logicalOlapScan(),
+                                                logicalJoin(
+                                                                
logicalOlapScan(),
+                                                                
logicalAssertNumRows()))));
+    }
+
+    /**
+     * Test push down to left child with equi (hash) condition on the top join:
+     * Before:
+     * topJoin(T1.age = x)
+     * |-- bottomJoin(T1.id = T2.sid)
+     * | |-- Scan(T1)
+     * | `-- Scan(T2)
+     * `-- LogicalAssertNumRows(output=(x, ...))
+     *
+     * After:
+     * bottomJoin(T1.id = T2.sid)
+     * |-- topJoin(T1.age = x)
+     * | |-- Scan(T1)
+     * | `-- LogicalAssertNumRows(output=(x, ...))
+     * `-- Scan(T2)
+     */
+    @Test
+    void testPushDownToLeftChildWithHashConjunct() {
+        Plan oneRowRelation = new LogicalPlanBuilder(t3)
+                .limit(1)
+                .build();
+
+        AssertNumRowsElement assertElement = new AssertNumRowsElement(1, "", 
Assertion.EQ);
+        LogicalAssertNumRows<Plan> assertNumRows = new 
LogicalAssertNumRows<>(assertElement, oneRowRelation);
+
+        // Create bottom join: T1 JOIN T2 on T1.id = T2.sid
+        Expression bottomJoinCondition = new EqualTo(t1Slots.get(0), 
t2Slots.get(1));
+
+        LogicalPlan bottomJoin = new LogicalPlanBuilder(t1)
+                .join(t2, JoinType.INNER_JOIN, 
ImmutableList.of(bottomJoinCondition),
+                                ImmutableList.of())
+                .build();
+
+        // Create top join: (T1 JOIN T2) JOIN assertNumRows on T1.age = 
course.id
+        // This references T1 (left child of bottom join) and assertNumRows
+        Expression topJoinCondition = new EqualTo(t1Slots.get(1), 
t3Slots.get(0));
+
+        LogicalPlan root = new LogicalPlanBuilder(bottomJoin)
+                .join(assertNumRows, JoinType.INNER_JOIN, 
ImmutableList.of(topJoinCondition),
+                                ImmutableList.of())
+                .build();
+
+        // Apply the rule
+        PlanChecker.from(MemoTestUtils.createConnectContext(), root)
+                .applyTopDown(new PushDownJoinOnAssertNumRows())
+                .matches(logicalJoin(
+                                logicalJoin(
+                                                        logicalOlapScan(),
+                                                        
logicalAssertNumRows()),
+                                logicalOlapScan()));
+    }
+
     /**
      * Test with CROSS JOIN type.
      */


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

Reply via email to