From 0dfdbe4508caf4741e87d807f704ec688ea31fb6 Mon Sep 17 00:00:00 2001 From: jakevin Date: Thu, 10 Nov 2022 12:21:06 +0800 Subject: [PATCH] [feature](Nereids): InnerJoinLeftAssociate, InnerJoinRightAssociate and JoinExchange. (#14051) --- .../apache/doris/nereids/rules/RuleType.java | 13 +- .../join/InnerJoinLeftAssociate.java | 121 +++++++++++++++ .../join/InnerJoinRightAssociate.java | 116 +++++++++++++++ .../rules/exploration/join/JoinExchange.java | 139 ++++++++++++++++++ .../rules/rewrite/logical/ReorderJoin.java | 9 +- .../apache/doris/nereids/util/JoinUtils.java | 8 + .../join/InnerJoinLeftAssociateTest.java | 68 +++++++++ .../join/InnerJoinRightAssociateTest.java | 65 ++++++++ .../exploration/join/JoinExchangeTest.java | 68 +++++++++ 9 files changed, 596 insertions(+), 11 deletions(-) create mode 100644 fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/join/InnerJoinLeftAssociate.java create mode 100644 fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/join/InnerJoinRightAssociate.java create mode 100644 fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/join/JoinExchange.java create mode 100644 fe/fe-core/src/test/java/org/apache/doris/nereids/rules/exploration/join/InnerJoinLeftAssociateTest.java create mode 100644 fe/fe-core/src/test/java/org/apache/doris/nereids/rules/exploration/join/InnerJoinRightAssociateTest.java create mode 100644 fe/fe-core/src/test/java/org/apache/doris/nereids/rules/exploration/join/JoinExchangeTest.java 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 bbe81e49c9..89a00279e6 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 @@ -140,7 +140,6 @@ public enum RuleType { // exploration rules TEST_EXPLORATION(RuleTypeClass.EXPLORATION), LOGICAL_JOIN_COMMUTATE(RuleTypeClass.EXPLORATION), - LOGICAL_LEFT_JOIN_ASSOCIATIVE(RuleTypeClass.EXPLORATION), LOGICAL_INNER_JOIN_LASSCOM(RuleTypeClass.EXPLORATION), LOGICAL_INNER_JOIN_LASSCOM_PROJECT(RuleTypeClass.EXPLORATION), LOGICAL_OUTER_JOIN_LASSCOM(RuleTypeClass.EXPLORATION), @@ -148,6 +147,14 @@ public enum RuleType { LOGICAL_SEMI_JOIN_LOGICAL_JOIN_TRANSPOSE(RuleTypeClass.EXPLORATION), LOGICAL_SEMI_JOIN_LOGICAL_JOIN_TRANSPOSE_PROJECT(RuleTypeClass.EXPLORATION), LOGICAL_SEMI_JOIN_SEMI_JOIN_TRANPOSE(RuleTypeClass.EXPLORATION), + LOGICAL_JOIN_EXCHANGE(RuleTypeClass.EXPLORATION), + LOGICAL_JOIN_EXCHANGE_LEFT_PROJECT(RuleTypeClass.EXPLORATION), + LOGICAL_JOIN_EXCHANGE_RIGHT_PROJECT(RuleTypeClass.EXPLORATION), + LOGICAL_JOIN_EXCHANGE_BOTH_PROJECT(RuleTypeClass.EXPLORATION), + LOGICAL_INNER_JOIN_LEFT_ASSOCIATIVE(RuleTypeClass.EXPLORATION), + LOGICAL_INNER_JOIN_LEFT_ASSOCIATIVE_PROJECT(RuleTypeClass.EXPLORATION), + LOGICAL_INNER_JOIN_RIGHT_ASSOCIATIVE(RuleTypeClass.EXPLORATION), + LOGICAL_INNER_JOIN_RIGHT_ASSOCIATIVE_PROJECT(RuleTypeClass.EXPLORATION), // implementation rules LOGICAL_ONE_ROW_RELATION_TO_PHYSICAL_ONE_ROW_RELATION(RuleTypeClass.IMPLEMENTATION), @@ -183,8 +190,8 @@ public enum RuleType { return ruleTypeClass; } - public - Rule build(PatternMatcher patternMatcher) { + public Rule build( + PatternMatcher patternMatcher) { return patternMatcher.toRule(this); } diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/join/InnerJoinLeftAssociate.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/join/InnerJoinLeftAssociate.java new file mode 100644 index 0000000000..118fc2aa3c --- /dev/null +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/join/InnerJoinLeftAssociate.java @@ -0,0 +1,121 @@ +// 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.join; + +import org.apache.doris.nereids.rules.Rule; +import org.apache.doris.nereids.rules.RuleType; +import org.apache.doris.nereids.rules.exploration.OneExplorationRuleFactory; +import org.apache.doris.nereids.trees.expressions.Expression; +import org.apache.doris.nereids.trees.expressions.Slot; +import org.apache.doris.nereids.trees.plans.GroupPlan; +import org.apache.doris.nereids.trees.plans.JoinType; +import org.apache.doris.nereids.trees.plans.Plan; +import org.apache.doris.nereids.trees.plans.logical.LogicalJoin; +import org.apache.doris.nereids.util.JoinUtils; + +import com.google.common.base.Preconditions; + +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * Rule for inner join LeftAssociate. + */ +public class InnerJoinLeftAssociate extends OneExplorationRuleFactory { + /* + * topJoin newTopJoin + * / \ / \ + * A bottomJoin -> newBottomJoin C + * / \ / \ + * B C A B + */ + public static final InnerJoinLeftAssociate INSTANCE = new InnerJoinLeftAssociate(); + + @Override + public Rule build() { + return innerLogicalJoin(group(), innerLogicalJoin()) + .when(InnerJoinLeftAssociate::checkReorder) + .then(topJoin -> { + LogicalJoin bottomJoin = topJoin.right(); + GroupPlan a = topJoin.left(); + GroupPlan b = bottomJoin.left(); + GroupPlan c = bottomJoin.right(); + + // Split condition + Set abOutputSet = JoinUtils.getJoinOutputSet(a, b); + Map> hashConjunctsSplit = Stream.concat( + topJoin.getHashJoinConjuncts().stream(), + bottomJoin.getHashJoinConjuncts().stream()) + .collect(Collectors.partitioningBy(condition -> { + Set usedSlot = condition.getInputSlots(); + return abOutputSet.containsAll(usedSlot); + })); + + Map> otherConjunctsSplit = Stream.concat( + topJoin.getOtherJoinConjuncts().stream(), + bottomJoin.getOtherJoinConjuncts().stream()) + .collect(Collectors.partitioningBy(condition -> { + Set usedSlot = condition.getInputSlots(); + return abOutputSet.containsAll(usedSlot); + })); + + List newBottomHashJoinConjuncts = hashConjunctsSplit.get(true); + List newTopHashJoinConjuncts = hashConjunctsSplit.get(false); + Preconditions.checkArgument(newTopHashJoinConjuncts.size() > 0); + if (newBottomHashJoinConjuncts.size() == 0) { + return null; + } + + List newBottomOtherJoinConjuncts = otherConjunctsSplit.get(true); + List newTopOtherJoinConjuncts = otherConjunctsSplit.get(false); + + // new join. + LogicalJoin newBottomJoin = new LogicalJoin<>(JoinType.INNER_JOIN, + newBottomHashJoinConjuncts, newBottomOtherJoinConjuncts, + a, b, bottomJoin.getJoinReorderContext()); + newBottomJoin.getJoinReorderContext().setHasCommute(false); + newBottomJoin.getJoinReorderContext().setHasRightAssociate(false); + newBottomJoin.getJoinReorderContext().setHasLeftAssociate(false); + newBottomJoin.getJoinReorderContext().setHasExchange(false); + + LogicalJoin, GroupPlan> newTopJoin = new LogicalJoin<>( + JoinType.INNER_JOIN, newTopHashJoinConjuncts, newTopOtherJoinConjuncts, + newBottomJoin, c, topJoin.getJoinReorderContext()); + newTopJoin.getJoinReorderContext().setHasLeftAssociate(true); + newTopJoin.getJoinReorderContext().setHasCommute(false); + + return newTopJoin; + }).toRule(RuleType.LOGICAL_INNER_JOIN_LEFT_ASSOCIATIVE); + } + + /** + * Check JoinReorderContext. + */ + public static boolean checkReorder(LogicalJoin topJoin) { + if (topJoin.getJoinReorderContext().hasCommute() + || topJoin.getJoinReorderContext().hasLeftAssociate() + || topJoin.getJoinReorderContext().hasRightAssociate() + || topJoin.getJoinReorderContext().hasExchange()) { + return false; + } + return true; + } +} diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/join/InnerJoinRightAssociate.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/join/InnerJoinRightAssociate.java new file mode 100644 index 0000000000..caa16258b2 --- /dev/null +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/join/InnerJoinRightAssociate.java @@ -0,0 +1,116 @@ +// 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.join; + +import org.apache.doris.nereids.rules.Rule; +import org.apache.doris.nereids.rules.RuleType; +import org.apache.doris.nereids.rules.exploration.OneExplorationRuleFactory; +import org.apache.doris.nereids.trees.expressions.Expression; +import org.apache.doris.nereids.trees.expressions.Slot; +import org.apache.doris.nereids.trees.plans.GroupPlan; +import org.apache.doris.nereids.trees.plans.JoinType; +import org.apache.doris.nereids.trees.plans.Plan; +import org.apache.doris.nereids.trees.plans.logical.LogicalJoin; +import org.apache.doris.nereids.util.JoinUtils; + +import com.google.common.base.Preconditions; + +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * Rule for inner join RightAssociate. + */ +public class InnerJoinRightAssociate extends OneExplorationRuleFactory { + // topJoin newTopJoin + // / \ / \ + // bottomJoin C -> A newBottomJoin + // / \ / \ + // A B B C + + public static final InnerJoinRightAssociate INSTANCE = new InnerJoinRightAssociate(); + + @Override + public Rule build() { + return innerLogicalJoin(innerLogicalJoin(), group()) + .when(InnerJoinRightAssociate::checkReorder) + .then(topJoin -> { + LogicalJoin bottomJoin = topJoin.left(); + GroupPlan a = bottomJoin.left(); + GroupPlan b = bottomJoin.right(); + GroupPlan c = topJoin.right(); + + // Split condition + Set bcOutputSet = JoinUtils.getJoinOutputSet(b, c); + Map> hashConjunctsSplit = Stream.concat( + topJoin.getHashJoinConjuncts().stream(), + bottomJoin.getHashJoinConjuncts().stream()) + .collect(Collectors.partitioningBy(condition -> { + Set usedSlot = condition.getInputSlots(); + return bcOutputSet.containsAll(usedSlot); + })); + + Map> otherConjunctsSplit = Stream.concat( + topJoin.getOtherJoinConjuncts().stream(), + bottomJoin.getOtherJoinConjuncts().stream()) + .collect(Collectors.partitioningBy(condition -> { + Set usedSlot = condition.getInputSlots(); + return bcOutputSet.containsAll(usedSlot); + })); + + List newBottomHashJoinConjuncts = hashConjunctsSplit.get(true); + List newTopHashJoinConjuncts = hashConjunctsSplit.get(false); + Preconditions.checkArgument(newTopHashJoinConjuncts.size() > 0); + if (newBottomHashJoinConjuncts.size() == 0) { + return null; + } + + List newBottomOtherJoinConjuncts = otherConjunctsSplit.get(true); + List newTopOtherJoinConjuncts = otherConjunctsSplit.get(false); + + LogicalJoin newBottomJoin = new LogicalJoin<>(JoinType.INNER_JOIN, + newBottomHashJoinConjuncts, newBottomOtherJoinConjuncts, + b, c, bottomJoin.getJoinReorderContext()); + newBottomJoin.getJoinReorderContext().setHasCommute(false); + newBottomJoin.getJoinReorderContext().setHasRightAssociate(false); + newBottomJoin.getJoinReorderContext().setHasLeftAssociate(false); + newBottomJoin.getJoinReorderContext().setHasExchange(false); + + LogicalJoin> newTopJoin = new LogicalJoin<>( + JoinType.INNER_JOIN, newTopHashJoinConjuncts, newTopOtherJoinConjuncts, + a, newBottomJoin, topJoin.getJoinReorderContext()); + newTopJoin.getJoinReorderContext().setHasRightAssociate(true); + newTopJoin.getJoinReorderContext().setHasCommute(false); + + return newTopJoin; + }).toRule(RuleType.LOGICAL_INNER_JOIN_RIGHT_ASSOCIATIVE); + } + + /** + * Check JoinReorderContext. + */ + public static boolean checkReorder(LogicalJoin topJoin) { + return !topJoin.getJoinReorderContext().hasCommute() + && !topJoin.getJoinReorderContext().hasRightAssociate() + && !topJoin.getJoinReorderContext().hasRightAssociate() + && !topJoin.getJoinReorderContext().hasExchange(); + } +} diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/join/JoinExchange.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/join/JoinExchange.java new file mode 100644 index 0000000000..f2b221dc27 --- /dev/null +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/join/JoinExchange.java @@ -0,0 +1,139 @@ +// 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.join; + +import org.apache.doris.nereids.annotation.Developing; +import org.apache.doris.nereids.rules.Rule; +import org.apache.doris.nereids.rules.RuleType; +import org.apache.doris.nereids.rules.exploration.OneExplorationRuleFactory; +import org.apache.doris.nereids.trees.expressions.Expression; +import org.apache.doris.nereids.trees.expressions.Slot; +import org.apache.doris.nereids.trees.plans.GroupPlan; +import org.apache.doris.nereids.trees.plans.JoinType; +import org.apache.doris.nereids.trees.plans.Plan; +import org.apache.doris.nereids.trees.plans.logical.LogicalJoin; +import org.apache.doris.nereids.util.JoinUtils; + +import com.google.common.collect.Lists; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +/** + * rule factory for exchange without inside-project. + */ +@Developing +public class JoinExchange extends OneExplorationRuleFactory { + public static final JoinExchange INSTANCE = new JoinExchange(); + + /* + * topJoin newTopJoin + * / \ / \ + * leftJoin rightJoin --> newLeftJoin newRightJoin + * / \ / \ / \ / \ + * A B C D A C B D + */ + @Override + public Rule build() { + return innerLogicalJoin(innerLogicalJoin(), innerLogicalJoin()) + .when(JoinExchange::checkReorder) + .then(topJoin -> { + LogicalJoin leftJoin = topJoin.left(); + LogicalJoin rightJoin = topJoin.right(); + GroupPlan a = leftJoin.left(); + GroupPlan b = leftJoin.right(); + GroupPlan c = rightJoin.left(); + GroupPlan d = rightJoin.right(); + + Set acOutputSet = JoinUtils.getJoinOutputSet(a, c); + Set bdOutputSet = JoinUtils.getJoinOutputSet(b, d); + + List newLeftJoinHashJoinConjuncts = Lists.newArrayList(); + List newRightJoinHashJoinConjuncts = Lists.newArrayList(); + List newTopJoinHashJoinConjuncts = new ArrayList<>(leftJoin.getHashJoinConjuncts()); + newTopJoinHashJoinConjuncts.addAll(rightJoin.getHashJoinConjuncts()); + splitTopConditon(topJoin.getHashJoinConjuncts(), acOutputSet, bdOutputSet, + newLeftJoinHashJoinConjuncts, newRightJoinHashJoinConjuncts, newTopJoinHashJoinConjuncts); + + List newLeftJoinOtherJoinConjuncts = Lists.newArrayList(); + List newRightJoinOtherJoinConjuncts = Lists.newArrayList(); + List newTopJoinOtherJoinConjuncts = new ArrayList<>(leftJoin.getOtherJoinConjuncts()); + newTopJoinOtherJoinConjuncts.addAll(rightJoin.getOtherJoinConjuncts()); + splitTopConditon(topJoin.getOtherJoinConjuncts(), acOutputSet, bdOutputSet, + newLeftJoinOtherJoinConjuncts, newRightJoinOtherJoinConjuncts, + newTopJoinOtherJoinConjuncts); + + if (newLeftJoinHashJoinConjuncts.size() == 0 || newRightJoinHashJoinConjuncts.size() == 0) { + return null; + } + LogicalJoin newLeftJoin = new LogicalJoin<>(JoinType.INNER_JOIN, + newLeftJoinHashJoinConjuncts, newLeftJoinOtherJoinConjuncts, + a, c, leftJoin.getJoinReorderContext()); + newLeftJoin.getJoinReorderContext().setHasCommute(false); + newLeftJoin.getJoinReorderContext().setHasLeftAssociate(false); + newLeftJoin.getJoinReorderContext().setHasRightAssociate(false); + newLeftJoin.getJoinReorderContext().setHasExchange(false); + + LogicalJoin newRightJoin = new LogicalJoin<>(JoinType.INNER_JOIN, + newRightJoinHashJoinConjuncts, newRightJoinOtherJoinConjuncts, + b, d, rightJoin.getJoinReorderContext()); + newRightJoin.getJoinReorderContext().setHasCommute(false); + newRightJoin.getJoinReorderContext().setHasLeftAssociate(false); + newRightJoin.getJoinReorderContext().setHasRightAssociate(false); + newRightJoin.getJoinReorderContext().setHasExchange(false); + + LogicalJoin, LogicalJoin> + newTopJoin = new LogicalJoin<>(JoinType.INNER_JOIN, + newTopJoinHashJoinConjuncts, newTopJoinOtherJoinConjuncts, + newLeftJoin, newRightJoin, topJoin.getJoinReorderContext()); + newTopJoin.getJoinReorderContext().setHasExchange(true); + + return newTopJoin; + }).toRule(RuleType.LOGICAL_JOIN_EXCHANGE); + } + + /** + * check reorder masks. + */ + public static boolean checkReorder(LogicalJoin topJoin) { + if (topJoin.getJoinReorderContext().hasCommute() + || topJoin.getJoinReorderContext().hasLeftAssociate() + || topJoin.getJoinReorderContext().hasRightAssociate() + || topJoin.getJoinReorderContext().hasExchange()) { + return false; + } else { + return true; + } + } + + private void splitTopConditon(List topCondition, Set acOutputSet, Set bdOutputSet, + List newLeftCondition, List newRightCondition, + List remainTopCondition) { + for (Expression hashJoinConjunct : topCondition) { + Set inputSlots = hashJoinConjunct.getInputSlots(); + if (acOutputSet.containsAll(inputSlots)) { + newLeftCondition.add(hashJoinConjunct); + } else if (bdOutputSet.containsAll(inputSlots)) { + newRightCondition.add(hashJoinConjunct); + } else { + remainTopCondition.add(hashJoinConjunct); + } + } + } +} diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/logical/ReorderJoin.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/logical/ReorderJoin.java index 3ee308527b..2bc2e1d888 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/logical/ReorderJoin.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/logical/ReorderJoin.java @@ -303,13 +303,6 @@ public class ReorderJoin extends OneRewriteRuleFactory { && !changeChildren; } - private Set getJoinOutput(Plan left, Plan right) { - HashSet joinOutput = new HashSet<>(); - joinOutput.addAll(left.getOutput()); - joinOutput.addAll(right.getOutput()); - return joinOutput; - } - /** * Find hash condition from joinFilter * Get InnerJoin from left, right from [candidates]. @@ -328,7 +321,7 @@ public class ReorderJoin extends OneRewriteRuleFactory { Plan candidate = candidates.get(i); Set rightOutputSet = candidate.getOutputSet(); - Set joinOutput = getJoinOutput(left, candidate); + Set joinOutput = JoinUtils.getJoinOutputSet(left, candidate); List currentJoinFilter = joinFilter.stream() .filter(expr -> { diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/util/JoinUtils.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/util/JoinUtils.java index 6a802979b2..125c18d199 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/util/JoinUtils.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/util/JoinUtils.java @@ -40,6 +40,7 @@ import org.apache.doris.qe.ConnectContext; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -318,4 +319,11 @@ public class JoinUtils { } }); } + + public static Set getJoinOutputSet(Plan left, Plan right) { + HashSet joinOutput = new HashSet<>(); + joinOutput.addAll(left.getOutput()); + joinOutput.addAll(right.getOutput()); + return joinOutput; + } } diff --git a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/exploration/join/InnerJoinLeftAssociateTest.java b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/exploration/join/InnerJoinLeftAssociateTest.java new file mode 100644 index 0000000000..1891702473 --- /dev/null +++ b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/exploration/join/InnerJoinLeftAssociateTest.java @@ -0,0 +1,68 @@ +// 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.join; + +import org.apache.doris.common.Pair; +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.MemoTestUtils; +import org.apache.doris.nereids.util.PatternMatchSupported; +import org.apache.doris.nereids.util.PlanChecker; +import org.apache.doris.nereids.util.PlanConstructor; + +import org.junit.jupiter.api.Test; + +class InnerJoinLeftAssociateTest implements PatternMatchSupported { + private final LogicalOlapScan scan1 = PlanConstructor.newLogicalOlapScan(0, "t1", 0); + private final LogicalOlapScan scan2 = PlanConstructor.newLogicalOlapScan(1, "t2", 0); + private final LogicalOlapScan scan3 = PlanConstructor.newLogicalOlapScan(2, "t3", 0); + + @Test + public void testSimple() { + /* + * LogicalJoin ( type=INNER_JOIN, hashJoinConjuncts=[(id#4 = id#0)], otherJoinConjuncts=[] ) + * |--LogicalOlapScan ( qualified=db.t1, output=[id#4, name#5], candidateIndexIds=[], selectedIndexId=-1, preAgg=ON ) + * +--LogicalJoin ( type=INNER_JOIN, hashJoinConjuncts=[(id#0 = id#2)], otherJoinConjuncts=[] ) + * |--LogicalOlapScan ( qualified=db.t2, output=[id#0, name#1], candidateIndexIds=[], selectedIndexId=-1, preAgg=ON ) + * +--LogicalOlapScan ( qualified=db.t3, output=[id#2, name#3], candidateIndexIds=[], selectedIndexId=-1, preAgg=ON ) + */ + LogicalPlan plan = new LogicalPlanBuilder(scan1) + .hashJoinUsing( + new LogicalPlanBuilder(scan2) + .hashJoinUsing(scan3, JoinType.INNER_JOIN, Pair.of(0, 0)) + .build(), + JoinType.INNER_JOIN, Pair.of(0, 0) + ) + .build(); + + PlanChecker.from(MemoTestUtils.createConnectContext(), plan) + .applyExploration(InnerJoinLeftAssociate.INSTANCE.build()) + .printlnOrigin() + .matchesExploration( + logicalJoin( + logicalJoin( + logicalOlapScan().when(scan -> scan.getTable().getName().equals("t1")), + logicalOlapScan().when(scan -> scan.getTable().getName().equals("t2")) + ), + logicalOlapScan().when(scan -> scan.getTable().getName().equals("t3")) + ) + ); + } +} diff --git a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/exploration/join/InnerJoinRightAssociateTest.java b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/exploration/join/InnerJoinRightAssociateTest.java new file mode 100644 index 0000000000..1208e934bc --- /dev/null +++ b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/exploration/join/InnerJoinRightAssociateTest.java @@ -0,0 +1,65 @@ +// 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.join; + +import org.apache.doris.common.Pair; +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.MemoTestUtils; +import org.apache.doris.nereids.util.PatternMatchSupported; +import org.apache.doris.nereids.util.PlanChecker; +import org.apache.doris.nereids.util.PlanConstructor; + +import org.junit.jupiter.api.Test; + +class InnerJoinRightAssociateTest implements PatternMatchSupported { + private final LogicalOlapScan scan1 = PlanConstructor.newLogicalOlapScan(0, "t1", 0); + private final LogicalOlapScan scan2 = PlanConstructor.newLogicalOlapScan(1, "t2", 0); + private final LogicalOlapScan scan3 = PlanConstructor.newLogicalOlapScan(2, "t3", 0); + + @Test + public void testSimple() { + /*- + * LogicalJoin ( type=INNER_JOIN, hashJoinConjuncts=[(id#2 = id#4)], otherJoinConjuncts=[] ) + * |--LogicalJoin ( type=INNER_JOIN, hashJoinConjuncts=[(id#0 = id#2)], otherJoinConjuncts=[] ) + * | |--LogicalOlapScan ( qualified=db.t1, output=[id#0, name#1], candidateIndexIds=[], selectedIndexId=-1, preAgg=ON ) + * | +--LogicalOlapScan ( qualified=db.t2, output=[id#2, name#3], candidateIndexIds=[], selectedIndexId=-1, preAgg=ON ) + * +--LogicalOlapScan ( qualified=db.t3, output=[id#4, name#5], candidateIndexIds=[], selectedIndexId=-1, preAgg=ON ) + */ + LogicalPlan plan = new LogicalPlanBuilder(scan1) + .hashJoinUsing(scan2, JoinType.INNER_JOIN, Pair.of(0, 0)) + .hashJoinUsing(scan3, JoinType.INNER_JOIN, Pair.of(2, 0)) + .build(); + + PlanChecker.from(MemoTestUtils.createConnectContext(), plan) + .applyExploration(InnerJoinRightAssociate.INSTANCE.build()) + .printlnOrigin() + .printlnExploration() + .matchesExploration( + logicalJoin( + logicalOlapScan().when(scan -> scan.getTable().getName().equals("t1")), + logicalJoin( + logicalOlapScan().when(scan -> scan.getTable().getName().equals("t2")), + logicalOlapScan().when(scan -> scan.getTable().getName().equals("t3")) + ) + ) + ); + } +} diff --git a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/exploration/join/JoinExchangeTest.java b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/exploration/join/JoinExchangeTest.java new file mode 100644 index 0000000000..810b8c167a --- /dev/null +++ b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/exploration/join/JoinExchangeTest.java @@ -0,0 +1,68 @@ +// 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.join; + +import org.apache.doris.common.Pair; +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.MemoTestUtils; +import org.apache.doris.nereids.util.PatternMatchSupported; +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 JoinExchangeTest implements PatternMatchSupported { + @Test + public void testSimple() { + LogicalOlapScan scan1 = PlanConstructor.newLogicalOlapScan(0, "t1", 0); + LogicalOlapScan scan2 = PlanConstructor.newLogicalOlapScan(1, "t2", 0); + LogicalOlapScan scan3 = PlanConstructor.newLogicalOlapScan(2, "t3", 0); + LogicalOlapScan scan4 = PlanConstructor.newLogicalOlapScan(3, "t4", 0); + + LogicalPlan plan = new LogicalPlanBuilder( + new LogicalPlanBuilder(scan1) + .hashJoinUsing(scan2, JoinType.INNER_JOIN, Pair.of(0, 0)) + .build()) + .hashJoinUsing( + new LogicalPlanBuilder(scan3) + .hashJoinUsing(scan4, JoinType.INNER_JOIN, Pair.of(0, 0)) + .build(), + JoinType.INNER_JOIN, ImmutableList.of(Pair.of(0, 0), Pair.of(2, 2))) + .build(); + + PlanChecker.from(MemoTestUtils.createConnectContext(), plan) + .applyExploration(JoinExchange.INSTANCE.build()) + .matchesExploration( + logicalJoin( + logicalJoin( + logicalOlapScan().when(scan -> scan.getTable().getName().equals("t1")), + logicalOlapScan().when(scan -> scan.getTable().getName().equals("t3")) + ), + logicalJoin( + logicalOlapScan().when(scan -> scan.getTable().getName().equals("t2")), + logicalOlapScan().when(scan -> scan.getTable().getName().equals("t4")) + ) + ) + ); + } +} +