You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@doris.apache.org by ja...@apache.org on 2023/04/18 01:31:13 UTC

[doris] branch master updated: [feature](Nereids): pullup semiJoin through aggregate. (#18669)

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

jakevin pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/master by this push:
     new 10b252856d [feature](Nereids): pullup semiJoin through aggregate. (#18669)
10b252856d is described below

commit 10b252856dcb26d87d4ab9fcda37a7870b2156c1
Author: jakevin <ja...@gmail.com>
AuthorDate: Tue Apr 18 09:31:07 2023 +0800

    [feature](Nereids): pullup semiJoin through aggregate. (#18669)
---
 .../org/apache/doris/nereids/rules/RuleSet.java    |  4 ++
 .../org/apache/doris/nereids/rules/RuleType.java   |  2 +
 .../rules/exploration/AggSemiJoinTranspose.java    | 45 ++++++++++++++++
 .../exploration/AggSemiJoinTransposeProject.java   | 47 +++++++++++++++++
 .../logical/PushdownFilterThroughAggregation.java  | 60 ++++++++++------------
 .../rewrite/logical/SemiJoinAggTranspose.java      | 32 +++++-------
 .../logical/SemiJoinAggTransposeProject.java       | 24 +--------
 .../SemiJoinLogicalJoinTransposeProject.java       |  1 +
 .../nereids/trees/plans/logical/LogicalJoin.java   |  8 +++
 .../exploration/AggSemiJoinTransposeTest.java      | 60 ++++++++++++++++++++++
 10 files changed, 207 insertions(+), 76 deletions(-)

diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/RuleSet.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/RuleSet.java
index 2917996e27..aa30417fc2 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/RuleSet.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/RuleSet.java
@@ -17,6 +17,8 @@
 
 package org.apache.doris.nereids.rules;
 
+import org.apache.doris.nereids.rules.exploration.AggSemiJoinTranspose;
+import org.apache.doris.nereids.rules.exploration.AggSemiJoinTransposeProject;
 import org.apache.doris.nereids.rules.exploration.MergeProjectsCBO;
 import org.apache.doris.nereids.rules.exploration.PushdownFilterThroughProjectCBO;
 import org.apache.doris.nereids.rules.exploration.join.InnerJoinLAsscom;
@@ -98,6 +100,8 @@ public class RuleSet {
             .add(LogicalJoinSemiJoinTransposeProject.INSTANCE)
             .add(PushdownProjectThroughInnerJoin.INSTANCE)
             .add(PushdownProjectThroughSemiJoin.INSTANCE)
+            .add(AggSemiJoinTranspose.INSTANCE)
+            .add(AggSemiJoinTransposeProject.INSTANCE)
             .build();
 
     public static final List<RuleFactory> PUSH_DOWN_FILTERS = ImmutableList.of(
diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/RuleType.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/RuleType.java
index 9406267fc4..3113a120c2 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/RuleType.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/RuleType.java
@@ -238,6 +238,8 @@ public enum RuleType {
     LOGICAL_INNER_JOIN_LEFT_ASSOCIATIVE_PROJECT(RuleTypeClass.EXPLORATION),
     LOGICAL_INNER_JOIN_RIGHT_ASSOCIATIVE(RuleTypeClass.EXPLORATION),
     LOGICAL_INNER_JOIN_RIGHT_ASSOCIATIVE_PROJECT(RuleTypeClass.EXPLORATION),
+    LOGICAL_AGG_SEMI_JOIN_TRANSPOSE(RuleTypeClass.EXPLORATION),
+    LOGICAL_AGG_SEMI_JOIN_TRANSPOSE_PROJECT(RuleTypeClass.EXPLORATION),
     PUSH_DOWN_PROJECT_THROUGH_SEMI_JOIN(RuleTypeClass.EXPLORATION),
     PUSH_DOWN_PROJECT_THROUGH_INNER_JOIN(RuleTypeClass.EXPLORATION),
     EAGER_COUNT(RuleTypeClass.EXPLORATION),
diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/AggSemiJoinTranspose.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/AggSemiJoinTranspose.java
new file mode 100644
index 0000000000..de995cee36
--- /dev/null
+++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/AggSemiJoinTranspose.java
@@ -0,0 +1,45 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.apache.doris.nereids.rules.exploration;
+
+import org.apache.doris.nereids.rules.Rule;
+import org.apache.doris.nereids.rules.RuleType;
+import org.apache.doris.nereids.rules.rewrite.logical.SemiJoinAggTranspose;
+import org.apache.doris.nereids.trees.plans.GroupPlan;
+import org.apache.doris.nereids.trees.plans.logical.LogicalJoin;
+
+/**
+ * Pull up SemiJoin through Agg.
+ */
+public class AggSemiJoinTranspose extends OneExplorationRuleFactory {
+    public static final AggSemiJoinTranspose INSTANCE = new AggSemiJoinTranspose();
+
+    @Override
+    public Rule build() {
+        return logicalAggregate(logicalJoin())
+                .when(agg -> agg.child().getJoinType().isLeftSemiOrAntiJoin())
+                .then(agg -> {
+                    LogicalJoin<GroupPlan, GroupPlan> join = agg.child();
+                    if (!SemiJoinAggTranspose.canTranspose(agg, join)) {
+                        return null;
+                    }
+                    return join.withChildren(agg.withChildren(join.left()), join.right());
+                })
+                .toRule(RuleType.LOGICAL_AGG_SEMI_JOIN_TRANSPOSE);
+    }
+}
diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/AggSemiJoinTransposeProject.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/AggSemiJoinTransposeProject.java
new file mode 100644
index 0000000000..45b687cc60
--- /dev/null
+++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/AggSemiJoinTransposeProject.java
@@ -0,0 +1,47 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.apache.doris.nereids.rules.exploration;
+
+import org.apache.doris.nereids.rules.Rule;
+import org.apache.doris.nereids.rules.RuleType;
+import org.apache.doris.nereids.rules.rewrite.logical.SemiJoinAggTranspose;
+import org.apache.doris.nereids.trees.plans.GroupPlan;
+import org.apache.doris.nereids.trees.plans.logical.LogicalJoin;
+import org.apache.doris.nereids.trees.plans.logical.LogicalProject;
+
+/**
+ * Pull up SemiJoin through Agg.
+ */
+public class AggSemiJoinTransposeProject extends OneExplorationRuleFactory {
+    public static final AggSemiJoinTransposeProject INSTANCE = new AggSemiJoinTransposeProject();
+
+    @Override
+    public Rule build() {
+        return logicalAggregate(logicalProject(logicalJoin()))
+                .when(agg -> agg.child().child().getJoinType().isLeftSemiOrAntiJoin())
+                .then(agg -> {
+                    LogicalProject<LogicalJoin<GroupPlan, GroupPlan>> project = agg.child();
+                    LogicalJoin<GroupPlan, GroupPlan> join = project.child();
+                    if (!SemiJoinAggTranspose.canTranspose(agg, join)) {
+                        return null;
+                    }
+                    return join.withChildren(agg.withChildren(project.withChildren(join.left())), join.right());
+                })
+                .toRule(RuleType.LOGICAL_AGG_SEMI_JOIN_TRANSPOSE_PROJECT);
+    }
+}
diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/logical/PushdownFilterThroughAggregation.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/logical/PushdownFilterThroughAggregation.java
index a891eb07ee..e64e659308 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/logical/PushdownFilterThroughAggregation.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/logical/PushdownFilterThroughAggregation.java
@@ -27,7 +27,6 @@ import org.apache.doris.nereids.trees.plans.logical.LogicalAggregate;
 import org.apache.doris.nereids.trees.plans.logical.LogicalFilter;
 import org.apache.doris.nereids.util.PlanUtils;
 
-import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Sets;
 
 import java.util.HashSet;
@@ -36,28 +35,21 @@ import java.util.Set;
 /**
  * Push the predicate in the LogicalFilter to the aggregate child.
  * For example:
+ * <pre>
  * Logical plan tree:
- *                 any_node
- *                   |
  *                filter (a>0 and b>0)
  *                   |
  *                group by(a, c)
- *                   |
- *                 scan
  * transformed to:
- *                 project
- *                   |
  *              upper filter (b>0)
  *                   |
  *                group by(a, c)
  *                   |
  *              bottom filter (a>0)
- *                   |
- *                 scan
+ * </pre>
  * Note:
- *    'a>0' could be push down, because 'a' is in group by keys;
- *    but 'b>0' could not push down, because 'b' is not in group by keys.
- *
+ * 'a>0' could be push down, because 'a' is in group by keys;
+ * but 'b>0' could not push down, because 'b' is not in group by keys.
  */
 
 public class PushdownFilterThroughAggregation extends OneRewriteRuleFactory {
@@ -66,17 +58,7 @@ public class PushdownFilterThroughAggregation extends OneRewriteRuleFactory {
     public Rule build() {
         return logicalFilter(logicalAggregate()).then(filter -> {
             LogicalAggregate<Plan> aggregate = filter.child();
-            Set<Slot> canPushDownSlots = new HashSet<>();
-            if (aggregate.hasRepeat()) {
-                // When there is a repeat, the push-down condition is consistent with the repeat
-                canPushDownSlots.addAll(aggregate.getSourceRepeat().get().getCommonGroupingSetExpressions());
-            } else {
-                for (Expression groupByExpression : aggregate.getGroupByExpressions()) {
-                    if (groupByExpression instanceof Slot) {
-                        canPushDownSlots.add((Slot) groupByExpression);
-                    }
-                }
-            }
+            Set<Slot> canPushDownSlots = getCanPushDownSlots(aggregate);
 
             Set<Expression> pushDownPredicates = Sets.newHashSet();
             Set<Expression> filterPredicates = Sets.newHashSet();
@@ -88,20 +70,30 @@ public class PushdownFilterThroughAggregation extends OneRewriteRuleFactory {
                     filterPredicates.add(conjunct);
                 }
             });
-
-            return pushDownPredicate(filter, aggregate, pushDownPredicates, filterPredicates);
+            if (pushDownPredicates.size() == 0) {
+                return null;
+            }
+            Plan bottomFilter = new LogicalFilter<>(pushDownPredicates, aggregate.child(0));
+            aggregate = (LogicalAggregate<Plan>) aggregate.withChildren(bottomFilter);
+            return PlanUtils.filterOrSelf(filterPredicates, aggregate);
         }).toRule(RuleType.PUSHDOWN_PREDICATE_THROUGH_AGGREGATION);
     }
 
-    private Plan pushDownPredicate(LogicalFilter filter, LogicalAggregate aggregate,
-            Set<Expression> pushDownPredicates, Set<Expression> filterPredicates) {
-        if (pushDownPredicates.size() == 0) {
-            // nothing pushed down, just return origin plan
-            return filter;
+    /**
+     * get the slots that can be pushed down
+     */
+    public static Set<Slot> getCanPushDownSlots(LogicalAggregate<? extends Plan> aggregate) {
+        Set<Slot> canPushDownSlots = new HashSet<>();
+        if (aggregate.hasRepeat()) {
+            // When there is a repeat, the push-down condition is consistent with the repeat
+            canPushDownSlots.addAll(aggregate.getSourceRepeat().get().getCommonGroupingSetExpressions());
+        } else {
+            for (Expression groupByExpression : aggregate.getGroupByExpressions()) {
+                if (groupByExpression instanceof Slot) {
+                    canPushDownSlots.add((Slot) groupByExpression);
+                }
+            }
         }
-        LogicalFilter bottomFilter = new LogicalFilter<>(pushDownPredicates, aggregate.child(0));
-
-        aggregate = aggregate.withChildren(ImmutableList.of(bottomFilter));
-        return PlanUtils.filterOrSelf(filterPredicates, aggregate);
+        return canPushDownSlots;
     }
 }
diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/logical/SemiJoinAggTranspose.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/logical/SemiJoinAggTranspose.java
index f6ecde1f4e..ed8f743629 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/logical/SemiJoinAggTranspose.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/logical/SemiJoinAggTranspose.java
@@ -20,14 +20,12 @@ package org.apache.doris.nereids.rules.rewrite.logical;
 import org.apache.doris.nereids.rules.Rule;
 import org.apache.doris.nereids.rules.RuleType;
 import org.apache.doris.nereids.rules.rewrite.OneRewriteRuleFactory;
-import org.apache.doris.nereids.trees.expressions.Expression;
 import org.apache.doris.nereids.trees.expressions.Slot;
 import org.apache.doris.nereids.trees.plans.Plan;
 import org.apache.doris.nereids.trees.plans.logical.LogicalAggregate;
+import org.apache.doris.nereids.trees.plans.logical.LogicalJoin;
 
-import java.util.HashSet;
 import java.util.Set;
-import java.util.stream.Collectors;
 
 /**
  * Pushdown semi-join through agg
@@ -39,26 +37,20 @@ public class SemiJoinAggTranspose extends OneRewriteRuleFactory {
                 .when(join -> join.getJoinType().isLeftSemiOrAntiJoin())
                 .then(join -> {
                     LogicalAggregate<Plan> aggregate = join.left();
-                    Set<Slot> canPushDownSlots = new HashSet<>();
-                    if (aggregate.hasRepeat()) {
-                        // When there is a repeat, the push-down condition is consistent with the repeat
-                        canPushDownSlots.addAll(aggregate.getSourceRepeat().get().getCommonGroupingSetExpressions());
-                    } else {
-                        for (Expression groupByExpression : aggregate.getGroupByExpressions()) {
-                            if (groupByExpression instanceof Slot) {
-                                canPushDownSlots.add((Slot) groupByExpression);
-                            }
-                        }
-                    }
-                    Set<Slot> leftOutputSet = join.left().getOutputSet();
-                    Set<Slot> conditionSlot = join.getConditionSlot()
-                            .stream()
-                            .filter(leftOutputSet::contains)
-                            .collect(Collectors.toSet());
-                    if (!canPushDownSlots.containsAll(conditionSlot)) {
+                    if (!canTranspose(aggregate, join)) {
                         return null;
                     }
                     return aggregate.withChildren(join.withChildren(aggregate.child(), join.right()));
                 }).toRule(RuleType.LOGICAL_SEMI_JOIN_AGG_TRANSPOSE);
     }
+
+    /**
+     * check if we can transpose agg and semi join
+     */
+    public static boolean canTranspose(LogicalAggregate<? extends Plan> aggregate,
+            LogicalJoin<? extends Plan, ? extends Plan> join) {
+        Set<Slot> canPushDownSlots = PushdownFilterThroughAggregation.getCanPushDownSlots(aggregate);
+        Set<Slot> leftConditionSlot = join.getLeftConditionSlot();
+        return canPushDownSlots.containsAll(leftConditionSlot);
+    }
 }
diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/logical/SemiJoinAggTransposeProject.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/logical/SemiJoinAggTransposeProject.java
index 1820cfde37..838a41cdc3 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/logical/SemiJoinAggTransposeProject.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/logical/SemiJoinAggTransposeProject.java
@@ -20,16 +20,11 @@ package org.apache.doris.nereids.rules.rewrite.logical;
 import org.apache.doris.nereids.rules.Rule;
 import org.apache.doris.nereids.rules.RuleType;
 import org.apache.doris.nereids.rules.rewrite.OneRewriteRuleFactory;
-import org.apache.doris.nereids.trees.expressions.Expression;
 import org.apache.doris.nereids.trees.expressions.Slot;
 import org.apache.doris.nereids.trees.plans.Plan;
 import org.apache.doris.nereids.trees.plans.logical.LogicalAggregate;
 import org.apache.doris.nereids.trees.plans.logical.LogicalProject;
 
-import java.util.HashSet;
-import java.util.Set;
-import java.util.stream.Collectors;
-
 /**
  * Pushdown semi-join through agg
  */
@@ -38,27 +33,12 @@ public class SemiJoinAggTransposeProject extends OneRewriteRuleFactory {
     public Rule build() {
         return logicalJoin(logicalProject(logicalAggregate()), any())
                 .when(join -> join.getJoinType().isLeftSemiOrAntiJoin())
+                .when(join -> join.left().isAllSlots())
                 .when(join -> join.left().getProjects().stream().allMatch(n -> n instanceof Slot))
                 .then(join -> {
                     LogicalProject<LogicalAggregate<Plan>> project = join.left();
                     LogicalAggregate<Plan> aggregate = project.child();
-                    Set<Slot> canPushDownSlots = new HashSet<>();
-                    if (aggregate.hasRepeat()) {
-                        // When there is a repeat, the push-down condition is consistent with the repeat
-                        canPushDownSlots.addAll(aggregate.getSourceRepeat().get().getCommonGroupingSetExpressions());
-                    } else {
-                        for (Expression groupByExpression : aggregate.getGroupByExpressions()) {
-                            if (groupByExpression instanceof Slot) {
-                                canPushDownSlots.add((Slot) groupByExpression);
-                            }
-                        }
-                    }
-                    Set<Slot> leftOutputSet = join.left().getOutputSet();
-                    Set<Slot> conditionSlot = join.getConditionSlot()
-                            .stream()
-                            .filter(leftOutputSet::contains)
-                            .collect(Collectors.toSet());
-                    if (!canPushDownSlots.containsAll(conditionSlot)) {
+                    if (!SemiJoinAggTranspose.canTranspose(aggregate, join)) {
                         return null;
                     }
                     Plan newPlan = aggregate.withChildren(join.withChildren(aggregate.child(), join.right()));
diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/logical/SemiJoinLogicalJoinTransposeProject.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/logical/SemiJoinLogicalJoinTransposeProject.java
index aeabe44000..94e3273f08 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/logical/SemiJoinLogicalJoinTransposeProject.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/logical/SemiJoinLogicalJoinTransposeProject.java
@@ -46,6 +46,7 @@ public class SemiJoinLogicalJoinTransposeProject extends OneRewriteRuleFactory {
                         && (topJoin.left().child().getJoinType().isInnerJoin()
                         || topJoin.left().child().getJoinType().isLeftOuterJoin()
                         || topJoin.left().child().getJoinType().isRightOuterJoin())))
+                .when(join -> join.left().isAllSlots())
                 .whenNot(join -> join.hasJoinHint() || join.left().child().hasJoinHint())
                 .whenNot(join -> join.isMarkJoin() || join.left().child().isMarkJoin())
                 .when(join -> join.left().getProjects().stream().allMatch(expr -> expr instanceof Slot))
diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalJoin.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalJoin.java
index 81f1e08b8e..12c0ab13c2 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalJoin.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalJoin.java
@@ -145,6 +145,14 @@ public class LogicalJoin<LEFT_CHILD_TYPE extends Plan, RIGHT_CHILD_TYPE extends
                 .flatMap(expr -> expr.getInputSlotExprIds().stream()).collect(Collectors.toSet());
     }
 
+    public Set<Slot> getLeftConditionSlot() {
+        Set<Slot> leftOutputSet = this.left().getOutputSet();
+        return Stream.concat(hashJoinConjuncts.stream(), otherJoinConjuncts.stream())
+                .flatMap(expr -> expr.getInputSlots().stream())
+                .filter(leftOutputSet::contains)
+                .collect(ImmutableSet.toImmutableSet());
+    }
+
     public Optional<Expression> getOnClauseCondition() {
         return ExpressionUtils.optionalAnd(hashJoinConjuncts, otherJoinConjuncts);
     }
diff --git a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/exploration/AggSemiJoinTransposeTest.java b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/exploration/AggSemiJoinTransposeTest.java
new file mode 100644
index 0000000000..d88e53b16e
--- /dev/null
+++ b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/exploration/AggSemiJoinTransposeTest.java
@@ -0,0 +1,60 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.apache.doris.nereids.rules.exploration;
+
+import org.apache.doris.common.Pair;
+import org.apache.doris.nereids.trees.expressions.Alias;
+import org.apache.doris.nereids.trees.expressions.functions.agg.Sum;
+import org.apache.doris.nereids.trees.plans.JoinType;
+import org.apache.doris.nereids.trees.plans.logical.LogicalOlapScan;
+import org.apache.doris.nereids.trees.plans.logical.LogicalPlan;
+import org.apache.doris.nereids.util.LogicalPlanBuilder;
+import org.apache.doris.nereids.util.MemoPatternMatchSupported;
+import org.apache.doris.nereids.util.MemoTestUtils;
+import org.apache.doris.nereids.util.PlanChecker;
+import org.apache.doris.nereids.util.PlanConstructor;
+
+import com.google.common.collect.ImmutableList;
+import org.junit.jupiter.api.Test;
+
+class AggSemiJoinTransposeTest implements MemoPatternMatchSupported {
+    private final LogicalOlapScan scan1 = PlanConstructor.newLogicalOlapScan(0, "t1", 0);
+    private final LogicalOlapScan scan2 = PlanConstructor.newLogicalOlapScan(1, "t2", 0);
+
+    @Test
+    void simple() {
+        LogicalPlan plan = new LogicalPlanBuilder(scan1)
+                .join(scan2, JoinType.LEFT_SEMI_JOIN, Pair.of(0, 0))
+                .aggGroupUsingIndex(ImmutableList.of(0),
+                        ImmutableList.of(
+                                scan1.getOutput().get(0),
+                                new Alias(new Sum(scan1.getOutput().get(1)), "sum")
+                        )
+                )
+                .build();
+        PlanChecker.from(MemoTestUtils.createConnectContext(), plan)
+                .applyExploration(AggSemiJoinTranspose.INSTANCE.build())
+                .printlnExploration()
+                .matchesExploration(
+                        leftSemiLogicalJoin(
+                                logicalAggregate(),
+                                logicalOlapScan()
+                        )
+                );
+    }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org