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]