[feature](Nereids): add bushy tree rule; (#18130)
This commit is contained in:
@ -19,7 +19,13 @@ package org.apache.doris.nereids.rules;
|
||||
|
||||
import org.apache.doris.nereids.rules.exploration.join.InnerJoinLAsscom;
|
||||
import org.apache.doris.nereids.rules.exploration.join.InnerJoinLAsscomProject;
|
||||
import org.apache.doris.nereids.rules.exploration.join.InnerJoinLeftAssociate;
|
||||
import org.apache.doris.nereids.rules.exploration.join.InnerJoinLeftAssociateProject;
|
||||
import org.apache.doris.nereids.rules.exploration.join.InnerJoinRightAssociate;
|
||||
import org.apache.doris.nereids.rules.exploration.join.InnerJoinRightAssociateProject;
|
||||
import org.apache.doris.nereids.rules.exploration.join.JoinCommute;
|
||||
import org.apache.doris.nereids.rules.exploration.join.JoinExchange;
|
||||
import org.apache.doris.nereids.rules.exploration.join.JoinExchangeBothProject;
|
||||
import org.apache.doris.nereids.rules.exploration.join.LogicalJoinSemiJoinTranspose;
|
||||
import org.apache.doris.nereids.rules.exploration.join.LogicalJoinSemiJoinTransposeProject;
|
||||
import org.apache.doris.nereids.rules.exploration.join.OuterJoinLAsscom;
|
||||
@ -92,15 +98,6 @@ public class RuleSet {
|
||||
.add(PushdownProjectThroughSemiJoin.INSTANCE)
|
||||
.build();
|
||||
|
||||
/**
|
||||
* This rule need to be shadow in DPHyp
|
||||
*/
|
||||
public static final List<Rule> JOIN_REORDER_RULE = planRuleFactories()
|
||||
.add(JoinCommute.ZIG_ZAG)
|
||||
.add(InnerJoinLAsscom.INSTANCE)
|
||||
.add(InnerJoinLAsscomProject.INSTANCE)
|
||||
.build();
|
||||
|
||||
public static final List<RuleFactory> PUSH_DOWN_FILTERS = ImmutableList.of(
|
||||
new PushdownFilterThroughProject(),
|
||||
new PushdownJoinOtherCondition(),
|
||||
@ -143,43 +140,27 @@ public class RuleSet {
|
||||
.add(new LogicalGenerateToPhysicalGenerate())
|
||||
.build();
|
||||
|
||||
public static final List<Rule> LEFT_DEEP_TREE_JOIN_REORDER = planRuleFactories()
|
||||
// .add(JoinCommute.LEFT_DEEP)
|
||||
// .add(JoinLAsscom.INNER)
|
||||
// .add(JoinLAsscomProject.INNER)
|
||||
// .add(JoinLAsscom.OUTER)
|
||||
// .add(JoinLAsscomProject.OUTER)
|
||||
// semi join Transpose ....
|
||||
.build();
|
||||
|
||||
public static final List<Rule> ZIG_ZAG_TREE_JOIN_REORDER = planRuleFactories()
|
||||
// .add(JoinCommute.ZIG_ZAG)
|
||||
// .add(JoinLAsscom.INNER)
|
||||
// .add(JoinLAsscomProject.INNER)
|
||||
// .add(JoinLAsscom.OUTER)
|
||||
// .add(JoinLAsscomProject.OUTER)
|
||||
// semi join Transpose ....
|
||||
.add(JoinCommute.ZIG_ZAG)
|
||||
.add(InnerJoinLAsscom.INSTANCE)
|
||||
.add(InnerJoinLAsscomProject.INSTANCE)
|
||||
.build();
|
||||
|
||||
public static final List<Rule> BUSHY_TREE_JOIN_REORDER = planRuleFactories()
|
||||
.add(JoinCommute.BUSHY)
|
||||
// TODO: add more rule
|
||||
// .add(JoinLeftAssociate.INNER)
|
||||
// .add(JoinLeftAssociateProject.INNER)
|
||||
// .add(JoinRightAssociate.INNER)
|
||||
// .add(JoinRightAssociateProject.INNER)
|
||||
// .add(JoinExchange.INNER)
|
||||
// .add(JoinExchangeBothProject.INNER)
|
||||
// .add(JoinExchangeLeftProject.INNER)
|
||||
// .add(JoinExchangeRightProject.INNER)
|
||||
// .add(JoinRightAssociate.OUTER)
|
||||
// .add(JoinLAsscom.OUTER)
|
||||
// semi join Transpose ....
|
||||
.add(InnerJoinLAsscom.INSTANCE)
|
||||
.add(InnerJoinLAsscomProject.INSTANCE)
|
||||
.add(InnerJoinLeftAssociate.INSTANCE)
|
||||
.add(InnerJoinLeftAssociateProject.INSTANCE)
|
||||
.add(InnerJoinRightAssociate.INSTANCE)
|
||||
.add(InnerJoinRightAssociateProject.INSTANCE)
|
||||
.add(JoinExchange.INSTANCE)
|
||||
.add(JoinExchangeBothProject.INSTANCE)
|
||||
.build();
|
||||
|
||||
public List<Rule> getExplorationRules() {
|
||||
List<Rule> rules = new ArrayList<>();
|
||||
rules.addAll(JOIN_REORDER_RULE);
|
||||
rules.addAll(ZIG_ZAG_TREE_JOIN_REORDER);
|
||||
rules.addAll(OTHER_REORDER_RULES);
|
||||
rules.addAll(EXPLORATION_RULES);
|
||||
return rules;
|
||||
@ -190,7 +171,7 @@ public class RuleSet {
|
||||
}
|
||||
|
||||
public List<Rule> getJoinOrderRule() {
|
||||
return JOIN_REORDER_RULE;
|
||||
return BUSHY_TREE_JOIN_REORDER;
|
||||
}
|
||||
|
||||
public List<Rule> getImplementationRules() {
|
||||
|
||||
@ -22,8 +22,6 @@ import org.apache.doris.nereids.rules.RuleType;
|
||||
import org.apache.doris.nereids.rules.exploration.OneExplorationRuleFactory;
|
||||
import org.apache.doris.nereids.trees.expressions.ExprId;
|
||||
import org.apache.doris.nereids.trees.expressions.Expression;
|
||||
import org.apache.doris.nereids.trees.expressions.NamedExpression;
|
||||
import org.apache.doris.nereids.trees.expressions.Slot;
|
||||
import org.apache.doris.nereids.trees.plans.GroupPlan;
|
||||
import org.apache.doris.nereids.trees.plans.Plan;
|
||||
import org.apache.doris.nereids.trees.plans.logical.LogicalJoin;
|
||||
@ -34,7 +32,6 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Rule for change inner join LAsscom (associative and commutive).
|
||||
@ -60,29 +57,17 @@ public class InnerJoinLAsscomProject extends OneExplorationRuleFactory {
|
||||
.when(join -> JoinReorderUtils.isAllSlotProject(join.left()))
|
||||
.then(topJoin -> {
|
||||
/* ********** init ********** */
|
||||
List<NamedExpression> projects = topJoin.left().getProjects();
|
||||
LogicalJoin<GroupPlan, GroupPlan> bottomJoin = topJoin.left().child();
|
||||
GroupPlan a = bottomJoin.left();
|
||||
GroupPlan b = bottomJoin.right();
|
||||
GroupPlan c = topJoin.right();
|
||||
Set<ExprId> bExprIdSet = b.getOutputExprIdSet();
|
||||
Set<ExprId> cExprIdSet = c.getOutputExprIdSet();
|
||||
|
||||
/* ********** Split projects ********** */
|
||||
Map<Boolean, List<NamedExpression>> map = JoinReorderUtils.splitProject(projects, bExprIdSet);
|
||||
List<NamedExpression> aProjects = map.get(false);
|
||||
List<NamedExpression> bProjects = map.get(true);
|
||||
if (aProjects.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/* ********** split HashConjuncts ********** */
|
||||
/* ********** split Conjuncts ********** */
|
||||
Map<Boolean, List<Expression>> splitHashConjuncts = splitConjuncts(
|
||||
topJoin.getHashJoinConjuncts(), bottomJoin.getHashJoinConjuncts(), bExprIdSet);
|
||||
List<Expression> newTopHashConjuncts = splitHashConjuncts.get(true);
|
||||
List<Expression> newBottomHashConjuncts = splitHashConjuncts.get(false);
|
||||
|
||||
/* ********** split OtherConjuncts ********** */
|
||||
Map<Boolean, List<Expression>> splitOtherConjuncts = splitConjuncts(
|
||||
topJoin.getOtherJoinConjuncts(), bottomJoin.getOtherJoinConjuncts(), bExprIdSet);
|
||||
List<Expression> newTopOtherConjuncts = splitOtherConjuncts.get(true);
|
||||
@ -92,19 +77,6 @@ public class InnerJoinLAsscomProject extends OneExplorationRuleFactory {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Add all slots used by OnCondition when projects not empty.
|
||||
Map<Boolean, Set<Slot>> abOnUsedSlots = Stream.concat(
|
||||
newTopHashConjuncts.stream(),
|
||||
newTopOtherConjuncts.stream())
|
||||
.flatMap(onExpr -> onExpr.getInputSlots().stream())
|
||||
.filter(slot -> !cExprIdSet.contains(slot.getExprId()))
|
||||
.collect(Collectors.partitioningBy(
|
||||
slot -> bExprIdSet.contains(slot.getExprId()), Collectors.toSet()));
|
||||
JoinReorderUtils.addSlotsUsedByOn(abOnUsedSlots.get(false), aProjects);
|
||||
JoinReorderUtils.addSlotsUsedByOn(abOnUsedSlots.get(true), bProjects);
|
||||
|
||||
aProjects.addAll(c.getOutput());
|
||||
|
||||
/* ********** new Plan ********** */
|
||||
LogicalJoin<Plan, Plan> newBottomJoin = topJoin.withConjunctsChildren(newBottomHashConjuncts,
|
||||
newBottomOtherConjuncts, a, c);
|
||||
@ -112,8 +84,14 @@ public class InnerJoinLAsscomProject extends OneExplorationRuleFactory {
|
||||
newBottomJoin.getJoinReorderContext().setHasLAsscom(false);
|
||||
newBottomJoin.getJoinReorderContext().setHasCommute(false);
|
||||
|
||||
Plan left = JoinReorderUtils.projectOrSelf(aProjects, newBottomJoin);
|
||||
Plan right = JoinReorderUtils.projectOrSelf(bProjects, b);
|
||||
// merge newTopHashConjuncts newTopOtherConjuncts topJoin.getOutputExprIdSet()
|
||||
// Set<ExprId> topUsedExprIds = new HashSet<>(topJoin.getOutputExprIdSet());
|
||||
// newTopHashConjuncts.forEach(expr -> topUsedExprIds.addAll(expr.getInputSlotExprIds()));
|
||||
// newTopOtherConjuncts.forEach(expr -> topUsedExprIds.addAll(expr.getInputSlotExprIds()));
|
||||
// Plan left = JoinReorderUtils.newProject(topUsedExprIds, newBottomJoin);
|
||||
// Plan right = JoinReorderUtils.newProject(topUsedExprIds, b);
|
||||
Plan left = newBottomJoin;
|
||||
Plan right = b;
|
||||
|
||||
LogicalJoin<Plan, Plan> newTopJoin = bottomJoin.withConjunctsChildren(newTopHashConjuncts,
|
||||
newTopOtherConjuncts, left, right);
|
||||
|
||||
@ -27,8 +27,6 @@ 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;
|
||||
@ -77,43 +75,47 @@ public class InnerJoinLeftAssociate extends OneExplorationRuleFactory {
|
||||
Set<ExprId> usedSlotExprIds = condition.getInputSlotExprIds();
|
||||
return abOutputExprIdSet.containsAll(usedSlotExprIds);
|
||||
}));
|
||||
|
||||
List<Expression> newBottomHashJoinConjuncts = hashConjunctsSplit.get(true);
|
||||
List<Expression> newTopHashJoinConjuncts = hashConjunctsSplit.get(false);
|
||||
Preconditions.checkArgument(newTopHashJoinConjuncts.size() > 0);
|
||||
if (newBottomHashJoinConjuncts.size() == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
List<Expression> newBottomOtherJoinConjuncts = otherConjunctsSplit.get(true);
|
||||
List<Expression> newTopOtherJoinConjuncts = otherConjunctsSplit.get(false);
|
||||
if (newBottomHashJoinConjuncts.isEmpty() && newBottomOtherJoinConjuncts.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// new join.
|
||||
LogicalJoin<Plan, Plan> newBottomJoin = topJoin.withConjunctsChildren(
|
||||
newBottomHashJoinConjuncts, newBottomOtherJoinConjuncts, a, b);
|
||||
newBottomJoin.getJoinReorderContext().copyFrom(bottomJoin.getJoinReorderContext());
|
||||
newBottomJoin.getJoinReorderContext().setHasCommute(false);
|
||||
newBottomJoin.getJoinReorderContext().setHasRightAssociate(false);
|
||||
newBottomJoin.getJoinReorderContext().setHasLeftAssociate(false);
|
||||
newBottomJoin.getJoinReorderContext().setHasExchange(false);
|
||||
|
||||
LogicalJoin<Plan, Plan> newTopJoin = bottomJoin.withConjunctsChildren(
|
||||
newTopHashJoinConjuncts, newTopOtherJoinConjuncts, newBottomJoin, c);
|
||||
newTopJoin.getJoinReorderContext().copyFrom(topJoin.getJoinReorderContext());
|
||||
newTopJoin.getJoinReorderContext().setHasLeftAssociate(true);
|
||||
newTopJoin.getJoinReorderContext().setHasCommute(false);
|
||||
setNewBottomJoinReorder(newBottomJoin, bottomJoin);
|
||||
setNewTopJoinReorder(newTopJoin, topJoin);
|
||||
|
||||
return newTopJoin;
|
||||
}).toRule(RuleType.LOGICAL_INNER_JOIN_LEFT_ASSOCIATIVE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check JoinReorderContext.
|
||||
*/
|
||||
/** Check JoinReorderContext. */
|
||||
public static boolean checkReorder(LogicalJoin<GroupPlan, ? extends Plan> topJoin) {
|
||||
return !topJoin.getJoinReorderContext().hasCommute()
|
||||
&& !topJoin.getJoinReorderContext().hasLeftAssociate()
|
||||
&& !topJoin.getJoinReorderContext().hasRightAssociate()
|
||||
&& !topJoin.getJoinReorderContext().hasExchange();
|
||||
}
|
||||
|
||||
/** Set JoinReorderContext */
|
||||
public static void setNewTopJoinReorder(LogicalJoin newTopJoin, LogicalJoin topJoin) {
|
||||
newTopJoin.getJoinReorderContext().copyFrom(topJoin.getJoinReorderContext());
|
||||
newTopJoin.getJoinReorderContext().setHasLeftAssociate(true);
|
||||
newTopJoin.getJoinReorderContext().setHasCommute(false);
|
||||
}
|
||||
|
||||
/** Set JoinReorderContext */
|
||||
public static void setNewBottomJoinReorder(LogicalJoin newBottomJoin, LogicalJoin bottomJoin) {
|
||||
newBottomJoin.getJoinReorderContext().copyFrom(bottomJoin.getJoinReorderContext());
|
||||
newBottomJoin.getJoinReorderContext().setHasCommute(false);
|
||||
newBottomJoin.getJoinReorderContext().setHasRightAssociate(false);
|
||||
newBottomJoin.getJoinReorderContext().setHasLeftAssociate(false);
|
||||
newBottomJoin.getJoinReorderContext().setHasExchange(false);
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,96 @@
|
||||
// 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.ExprId;
|
||||
import org.apache.doris.nereids.trees.expressions.Expression;
|
||||
import org.apache.doris.nereids.trees.plans.GroupPlan;
|
||||
import org.apache.doris.nereids.trees.plans.Plan;
|
||||
import org.apache.doris.nereids.trees.plans.logical.LogicalJoin;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Rule for inner join LeftAssociate.
|
||||
*/
|
||||
public class InnerJoinLeftAssociateProject extends OneExplorationRuleFactory {
|
||||
/*
|
||||
* topJoin newTopJoin
|
||||
* / \ / \
|
||||
* A bottomJoin -> newBottomJoin C
|
||||
* / \ / \
|
||||
* B C A B
|
||||
*/
|
||||
public static final InnerJoinLeftAssociateProject INSTANCE = new InnerJoinLeftAssociateProject();
|
||||
|
||||
@Override
|
||||
public Rule build() {
|
||||
return innerLogicalJoin(group(), logicalProject(innerLogicalJoin()))
|
||||
.when(InnerJoinLeftAssociate::checkReorder)
|
||||
.whenNot(join -> join.hasJoinHint() || join.right().child().hasJoinHint())
|
||||
.whenNot(join -> join.isMarkJoin() || join.right().child().isMarkJoin())
|
||||
.when(join -> JoinReorderUtils.isAllSlotProject(join.right()))
|
||||
.then(topJoin -> {
|
||||
LogicalJoin<GroupPlan, GroupPlan> bottomJoin = topJoin.right().child();
|
||||
GroupPlan a = topJoin.left();
|
||||
GroupPlan b = bottomJoin.left();
|
||||
GroupPlan c = bottomJoin.right();
|
||||
Set<ExprId> cExprIdSet = c.getOutputExprIdSet();
|
||||
|
||||
// Split condition
|
||||
Map<Boolean, List<Expression>> splitHashConjuncts = JoinReorderUtils.splitConjuncts(
|
||||
topJoin.getHashJoinConjuncts(), bottomJoin.getHashJoinConjuncts(), cExprIdSet);
|
||||
List<Expression> newTopHashConjuncts = splitHashConjuncts.get(true);
|
||||
List<Expression> newBottomHashConjuncts = splitHashConjuncts.get(false);
|
||||
Map<Boolean, List<Expression>> splitOtherConjuncts = JoinReorderUtils.splitConjuncts(
|
||||
topJoin.getOtherJoinConjuncts(), bottomJoin.getOtherJoinConjuncts(), cExprIdSet);
|
||||
List<Expression> newTopOtherConjuncts = splitOtherConjuncts.get(true);
|
||||
List<Expression> newBottomOtherConjuncts = splitOtherConjuncts.get(false);
|
||||
|
||||
if (newBottomOtherConjuncts.isEmpty() && newBottomHashConjuncts.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// new join.
|
||||
LogicalJoin<Plan, Plan> newBottomJoin = topJoin.withConjunctsChildren(
|
||||
newBottomHashConjuncts, newBottomOtherConjuncts, a, b);
|
||||
|
||||
// new Project.
|
||||
// Set<ExprId> topUsedExprIds = new HashSet<>(topJoin.getOutputExprIdSet());
|
||||
// newTopHashConjuncts.forEach(expr -> topUsedExprIds.addAll(expr.getInputSlotExprIds()));
|
||||
// newTopOtherConjuncts.forEach(expr -> topUsedExprIds.addAll(expr.getInputSlotExprIds()));
|
||||
// Plan left = JoinReorderUtils.newProject(topUsedExprIds, newBottomJoin);
|
||||
// Plan right = JoinReorderUtils.newProject(topUsedExprIds, c);
|
||||
Plan left = newBottomJoin;
|
||||
Plan right = c;
|
||||
|
||||
LogicalJoin<Plan, Plan> newTopJoin = bottomJoin.withConjunctsChildren(
|
||||
newTopHashConjuncts, newTopOtherConjuncts, left, right);
|
||||
InnerJoinLeftAssociate.setNewBottomJoinReorder(newBottomJoin, bottomJoin);
|
||||
InnerJoinLeftAssociate.setNewTopJoinReorder(newTopJoin, topJoin);
|
||||
|
||||
return JoinReorderUtils.projectOrSelf(new ArrayList<>(topJoin.getOutput()), newTopJoin);
|
||||
}).toRule(RuleType.LOGICAL_INNER_JOIN_LEFT_ASSOCIATIVE);
|
||||
}
|
||||
}
|
||||
@ -27,8 +27,6 @@ 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;
|
||||
@ -44,7 +42,6 @@ public class InnerJoinRightAssociate extends OneExplorationRuleFactory {
|
||||
// bottomJoin C -> A newBottomJoin
|
||||
// / \ / \
|
||||
// A B B C
|
||||
|
||||
public static final InnerJoinRightAssociate INSTANCE = new InnerJoinRightAssociate();
|
||||
|
||||
@Override
|
||||
@ -79,39 +76,44 @@ public class InnerJoinRightAssociate extends OneExplorationRuleFactory {
|
||||
|
||||
List<Expression> newBottomHashJoinConjuncts = hashConjunctsSplit.get(true);
|
||||
List<Expression> newTopHashJoinConjuncts = hashConjunctsSplit.get(false);
|
||||
Preconditions.checkArgument(newTopHashJoinConjuncts.size() > 0);
|
||||
if (newBottomHashJoinConjuncts.size() == 0) {
|
||||
List<Expression> newBottomOtherJoinConjuncts = otherConjunctsSplit.get(true);
|
||||
List<Expression> newTopOtherJoinConjuncts = otherConjunctsSplit.get(false);
|
||||
if (newBottomHashJoinConjuncts.isEmpty() && newBottomOtherJoinConjuncts.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
List<Expression> newBottomOtherJoinConjuncts = otherConjunctsSplit.get(true);
|
||||
List<Expression> newTopOtherJoinConjuncts = otherConjunctsSplit.get(false);
|
||||
|
||||
LogicalJoin<Plan, Plan> newBottomJoin = topJoin.withConjunctsChildren(
|
||||
newBottomHashJoinConjuncts, newBottomOtherJoinConjuncts, b, c);
|
||||
newBottomJoin.getJoinReorderContext().copyFrom(bottomJoin.getJoinReorderContext());
|
||||
newBottomJoin.getJoinReorderContext().setHasCommute(false);
|
||||
newBottomJoin.getJoinReorderContext().setHasRightAssociate(false);
|
||||
newBottomJoin.getJoinReorderContext().setHasLeftAssociate(false);
|
||||
newBottomJoin.getJoinReorderContext().setHasExchange(false);
|
||||
|
||||
LogicalJoin<Plan, Plan> newTopJoin = bottomJoin.withConjunctsChildren(newTopHashJoinConjuncts,
|
||||
newTopOtherJoinConjuncts, a, newBottomJoin);
|
||||
newTopJoin.getJoinReorderContext().copyFrom(topJoin.getJoinReorderContext());
|
||||
newTopJoin.getJoinReorderContext().setHasRightAssociate(true);
|
||||
newTopJoin.getJoinReorderContext().setHasCommute(false);
|
||||
setNewBottomJoinReorder(newBottomJoin, bottomJoin);
|
||||
setNewTopJoinReorder(newTopJoin, topJoin);
|
||||
|
||||
return newTopJoin;
|
||||
}).toRule(RuleType.LOGICAL_INNER_JOIN_RIGHT_ASSOCIATIVE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check JoinReorderContext.
|
||||
*/
|
||||
/** Check JoinReorderContext */
|
||||
public static boolean checkReorder(LogicalJoin<? extends Plan, GroupPlan> topJoin) {
|
||||
return !topJoin.getJoinReorderContext().hasCommute()
|
||||
&& !topJoin.getJoinReorderContext().hasRightAssociate()
|
||||
&& !topJoin.getJoinReorderContext().hasLeftAssociate()
|
||||
&& !topJoin.getJoinReorderContext().hasExchange();
|
||||
}
|
||||
|
||||
/** Set JoinReorderContext */
|
||||
public static void setNewTopJoinReorder(LogicalJoin newTopJoin, LogicalJoin topJoin) {
|
||||
newTopJoin.getJoinReorderContext().copyFrom(topJoin.getJoinReorderContext());
|
||||
newTopJoin.getJoinReorderContext().setHasRightAssociate(true);
|
||||
newTopJoin.getJoinReorderContext().setHasCommute(false);
|
||||
}
|
||||
|
||||
/** Set JoinReorderContext */
|
||||
public static void setNewBottomJoinReorder(LogicalJoin newBottomJoin, LogicalJoin bottomJoin) {
|
||||
newBottomJoin.getJoinReorderContext().copyFrom(bottomJoin.getJoinReorderContext());
|
||||
newBottomJoin.getJoinReorderContext().setHasCommute(false);
|
||||
newBottomJoin.getJoinReorderContext().setHasRightAssociate(false);
|
||||
newBottomJoin.getJoinReorderContext().setHasLeftAssociate(false);
|
||||
newBottomJoin.getJoinReorderContext().setHasExchange(false);
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,123 @@
|
||||
// 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.ExprId;
|
||||
import org.apache.doris.nereids.trees.expressions.Expression;
|
||||
import org.apache.doris.nereids.trees.plans.GroupPlan;
|
||||
import org.apache.doris.nereids.trees.plans.Plan;
|
||||
import org.apache.doris.nereids.trees.plans.logical.LogicalJoin;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Rule for inner join RightAssociate.
|
||||
*/
|
||||
public class InnerJoinRightAssociateProject extends OneExplorationRuleFactory {
|
||||
// topJoin newTopJoin
|
||||
// / \ / \
|
||||
// bottomJoin C -> A newBottomJoin
|
||||
// / \ / \
|
||||
// A B B C
|
||||
public static final InnerJoinRightAssociateProject INSTANCE = new InnerJoinRightAssociateProject();
|
||||
|
||||
@Override
|
||||
public Rule build() {
|
||||
return innerLogicalJoin(logicalProject(innerLogicalJoin()), group())
|
||||
.when(InnerJoinRightAssociate::checkReorder)
|
||||
.whenNot(join -> join.hasJoinHint() || join.left().child().hasJoinHint())
|
||||
.whenNot(join -> join.isMarkJoin() || join.left().child().isMarkJoin())
|
||||
.when(join -> JoinReorderUtils.isAllSlotProject(join.left()))
|
||||
.then(topJoin -> {
|
||||
LogicalJoin<GroupPlan, GroupPlan> bottomJoin = topJoin.left().child();
|
||||
GroupPlan a = bottomJoin.left();
|
||||
GroupPlan b = bottomJoin.right();
|
||||
GroupPlan c = topJoin.right();
|
||||
Set<ExprId> aExprIdSet = a.getOutputExprIdSet();
|
||||
|
||||
// Split condition
|
||||
Map<Boolean, List<Expression>> splitHashConjuncts = JoinReorderUtils.splitConjuncts(
|
||||
topJoin.getHashJoinConjuncts(), bottomJoin.getHashJoinConjuncts(), aExprIdSet);
|
||||
List<Expression> newTopHashConjuncts = splitHashConjuncts.get(true);
|
||||
List<Expression> newBottomHashConjuncts = splitHashConjuncts.get(false);
|
||||
Map<Boolean, List<Expression>> splitOtherConjuncts = JoinReorderUtils.splitConjuncts(
|
||||
topJoin.getOtherJoinConjuncts(), bottomJoin.getOtherJoinConjuncts(), aExprIdSet);
|
||||
List<Expression> newTopOtherConjuncts = splitOtherConjuncts.get(true);
|
||||
List<Expression> newBottomOtherConjuncts = splitOtherConjuncts.get(false);
|
||||
|
||||
if (newBottomOtherConjuncts.isEmpty() && newBottomHashConjuncts.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
LogicalJoin<Plan, Plan> newBottomJoin = topJoin.withConjunctsChildren(
|
||||
newBottomHashConjuncts, newBottomOtherConjuncts, b, c);
|
||||
|
||||
// new Project.
|
||||
// Set<ExprId> topUsedExprIds = new HashSet<>(topJoin.getOutputExprIdSet());
|
||||
// newTopHashConjuncts.forEach(expr -> topUsedExprIds.addAll(expr.getInputSlotExprIds()));
|
||||
// newTopOtherConjuncts.forEach(expr -> topUsedExprIds.addAll(expr.getInputSlotExprIds()));
|
||||
// Plan left = JoinReorderUtils.newProject(topUsedExprIds, a);
|
||||
// Plan right = JoinReorderUtils.newProject(topUsedExprIds, newBottomJoin);
|
||||
Plan left = a;
|
||||
Plan right = newBottomJoin;
|
||||
|
||||
LogicalJoin<Plan, Plan> newTopJoin = bottomJoin.withConjunctsChildren(
|
||||
newTopHashConjuncts, newTopOtherConjuncts, left, right);
|
||||
setNewBottomJoinReorder(newBottomJoin, bottomJoin);
|
||||
setNewTopJoinReorder(newTopJoin, topJoin);
|
||||
|
||||
return JoinReorderUtils.projectOrSelf(new ArrayList<>(topJoin.getOutput()), newTopJoin);
|
||||
}).toRule(RuleType.LOGICAL_INNER_JOIN_RIGHT_ASSOCIATIVE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check JoinReorderContext
|
||||
*/
|
||||
public static boolean checkReorder(LogicalJoin<? extends Plan, GroupPlan> topJoin) {
|
||||
return !topJoin.getJoinReorderContext().hasCommute()
|
||||
&& !topJoin.getJoinReorderContext().hasRightAssociate()
|
||||
&& !topJoin.getJoinReorderContext().hasLeftAssociate()
|
||||
&& !topJoin.getJoinReorderContext().hasExchange();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set JoinReorderContext
|
||||
*/
|
||||
public static void setNewTopJoinReorder(LogicalJoin newTopJoin, LogicalJoin topJoin) {
|
||||
newTopJoin.getJoinReorderContext().copyFrom(topJoin.getJoinReorderContext());
|
||||
newTopJoin.getJoinReorderContext().setHasRightAssociate(true);
|
||||
newTopJoin.getJoinReorderContext().setHasCommute(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set JoinReorderContext
|
||||
*/
|
||||
public static void setNewBottomJoinReorder(LogicalJoin newBottomJoin, LogicalJoin bottomJoin) {
|
||||
newBottomJoin.getJoinReorderContext().copyFrom(bottomJoin.getJoinReorderContext());
|
||||
newBottomJoin.getJoinReorderContext().setHasCommute(false);
|
||||
newBottomJoin.getJoinReorderContext().setHasRightAssociate(false);
|
||||
newBottomJoin.getJoinReorderContext().setHasLeftAssociate(false);
|
||||
newBottomJoin.getJoinReorderContext().setHasExchange(false);
|
||||
}
|
||||
}
|
||||
@ -17,7 +17,6 @@
|
||||
|
||||
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;
|
||||
@ -39,7 +38,6 @@ import java.util.Set;
|
||||
/**
|
||||
* rule factory for exchange without inside-project.
|
||||
*/
|
||||
@Developing
|
||||
public class JoinExchange extends OneExplorationRuleFactory {
|
||||
public static final JoinExchange INSTANCE = new JoinExchange();
|
||||
|
||||
@ -85,30 +83,17 @@ public class JoinExchange extends OneExplorationRuleFactory {
|
||||
if (newLeftJoinHashJoinConjuncts.size() == 0 || newRightJoinHashJoinConjuncts.size() == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
LogicalJoin<GroupPlan, GroupPlan> newLeftJoin = new LogicalJoin<>(JoinType.INNER_JOIN,
|
||||
newLeftJoinHashJoinConjuncts, newLeftJoinOtherJoinConjuncts, JoinHint.NONE,
|
||||
a, c);
|
||||
newLeftJoin.getJoinReorderContext().copyFrom(leftJoin.getJoinReorderContext());
|
||||
newLeftJoin.getJoinReorderContext().setHasCommute(false);
|
||||
newLeftJoin.getJoinReorderContext().setHasLeftAssociate(false);
|
||||
newLeftJoin.getJoinReorderContext().setHasRightAssociate(false);
|
||||
newLeftJoin.getJoinReorderContext().setHasExchange(false);
|
||||
|
||||
newLeftJoinHashJoinConjuncts, newLeftJoinOtherJoinConjuncts, JoinHint.NONE, a, c);
|
||||
LogicalJoin<GroupPlan, GroupPlan> newRightJoin = new LogicalJoin<>(JoinType.INNER_JOIN,
|
||||
newRightJoinHashJoinConjuncts, newRightJoinOtherJoinConjuncts, JoinHint.NONE,
|
||||
b, d);
|
||||
newRightJoin.getJoinReorderContext().copyFrom(leftJoin.getJoinReorderContext());
|
||||
newRightJoin.getJoinReorderContext().setHasCommute(false);
|
||||
newRightJoin.getJoinReorderContext().setHasLeftAssociate(false);
|
||||
newRightJoin.getJoinReorderContext().setHasRightAssociate(false);
|
||||
newRightJoin.getJoinReorderContext().setHasExchange(false);
|
||||
|
||||
LogicalJoin<LogicalJoin<GroupPlan, GroupPlan>, LogicalJoin<GroupPlan, GroupPlan>>
|
||||
newTopJoin = new LogicalJoin<>(JoinType.INNER_JOIN,
|
||||
newRightJoinHashJoinConjuncts, newRightJoinOtherJoinConjuncts, JoinHint.NONE, b, d);
|
||||
LogicalJoin newTopJoin = new LogicalJoin<>(JoinType.INNER_JOIN,
|
||||
newTopJoinHashJoinConjuncts, newTopJoinOtherJoinConjuncts, JoinHint.NONE,
|
||||
newLeftJoin, newRightJoin);
|
||||
newTopJoin.getJoinReorderContext().copyFrom(topJoin.getJoinReorderContext());
|
||||
newTopJoin.getJoinReorderContext().setHasExchange(true);
|
||||
setNewLeftJoinReorder(newLeftJoin, leftJoin);
|
||||
setNewRightJoinReorder(newRightJoin, leftJoin);
|
||||
setNewTopJoinReorder(newTopJoin, topJoin);
|
||||
|
||||
return newTopJoin;
|
||||
}).toRule(RuleType.LOGICAL_JOIN_EXCHANGE);
|
||||
@ -128,7 +113,31 @@ public class JoinExchange extends OneExplorationRuleFactory {
|
||||
}
|
||||
}
|
||||
|
||||
private void splitTopCondition(List<Expression> topCondition,
|
||||
public static void setNewTopJoinReorder(LogicalJoin newTopJoin, LogicalJoin topJoin) {
|
||||
newTopJoin.getJoinReorderContext().copyFrom(topJoin.getJoinReorderContext());
|
||||
newTopJoin.getJoinReorderContext().setHasExchange(true);
|
||||
}
|
||||
|
||||
public static void setNewLeftJoinReorder(LogicalJoin newLeftJoin, LogicalJoin leftJoin) {
|
||||
newLeftJoin.getJoinReorderContext().copyFrom(leftJoin.getJoinReorderContext());
|
||||
newLeftJoin.getJoinReorderContext().setHasCommute(false);
|
||||
newLeftJoin.getJoinReorderContext().setHasLeftAssociate(false);
|
||||
newLeftJoin.getJoinReorderContext().setHasRightAssociate(false);
|
||||
newLeftJoin.getJoinReorderContext().setHasExchange(false);
|
||||
}
|
||||
|
||||
public static void setNewRightJoinReorder(LogicalJoin newRightJoin, LogicalJoin rightJoin) {
|
||||
newRightJoin.getJoinReorderContext().copyFrom(rightJoin.getJoinReorderContext());
|
||||
newRightJoin.getJoinReorderContext().setHasCommute(false);
|
||||
newRightJoin.getJoinReorderContext().setHasLeftAssociate(false);
|
||||
newRightJoin.getJoinReorderContext().setHasRightAssociate(false);
|
||||
newRightJoin.getJoinReorderContext().setHasExchange(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* split condition.
|
||||
*/
|
||||
public static void splitTopCondition(List<Expression> topCondition,
|
||||
Set<ExprId> acOutputExprIdSet, Set<ExprId> bdOutputExprIdSet,
|
||||
List<Expression> newLeftCondition, List<Expression> newRightCondition,
|
||||
List<Expression> remainTopCondition) {
|
||||
|
||||
@ -0,0 +1,123 @@
|
||||
// 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.ExprId;
|
||||
import org.apache.doris.nereids.trees.expressions.Expression;
|
||||
import org.apache.doris.nereids.trees.plans.GroupPlan;
|
||||
import org.apache.doris.nereids.trees.plans.JoinHint;
|
||||
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.
|
||||
*/
|
||||
public class JoinExchangeBothProject extends OneExplorationRuleFactory {
|
||||
public static final JoinExchangeBothProject INSTANCE = new JoinExchangeBothProject();
|
||||
|
||||
/*
|
||||
* topJoin newTopJoin
|
||||
* / \ / \
|
||||
* leftJoin rightJoin --> newLeftJoin newRightJoin
|
||||
* / \ / \ / \ / \
|
||||
* A B C D A C B D
|
||||
*/
|
||||
@Override
|
||||
public Rule build() {
|
||||
return innerLogicalJoin(logicalProject(innerLogicalJoin()), logicalProject(innerLogicalJoin()))
|
||||
.when(JoinExchange::checkReorder)
|
||||
.when(join -> JoinReorderUtils.isAllSlotProject(join.left())
|
||||
&& JoinReorderUtils.isAllSlotProject(join.right()))
|
||||
.whenNot(join -> join.hasJoinHint()
|
||||
|| join.left().child().hasJoinHint() || join.right().child().hasJoinHint())
|
||||
.whenNot(join -> join.isMarkJoin() || join.left().child().isMarkJoin() || join.right().child().isMarkJoin())
|
||||
.then(topJoin -> {
|
||||
LogicalJoin<GroupPlan, GroupPlan> leftJoin = topJoin.left().child();
|
||||
LogicalJoin<GroupPlan, GroupPlan> rightJoin = topJoin.right().child();
|
||||
GroupPlan a = leftJoin.left();
|
||||
GroupPlan b = leftJoin.right();
|
||||
GroupPlan c = rightJoin.left();
|
||||
GroupPlan d = rightJoin.right();
|
||||
|
||||
Set<ExprId> acOutputExprIdSet = JoinUtils.getJoinOutputExprIdSet(a, c);
|
||||
Set<ExprId> bdOutputExprIdSet = JoinUtils.getJoinOutputExprIdSet(b, d);
|
||||
|
||||
List<Expression> newLeftJoinHashJoinConjuncts = Lists.newArrayList();
|
||||
List<Expression> newRightJoinHashJoinConjuncts = Lists.newArrayList();
|
||||
List<Expression> newTopJoinHashJoinConjuncts = new ArrayList<>(leftJoin.getHashJoinConjuncts());
|
||||
newTopJoinHashJoinConjuncts.addAll(rightJoin.getHashJoinConjuncts());
|
||||
JoinExchange.splitTopCondition(topJoin.getHashJoinConjuncts(), acOutputExprIdSet, bdOutputExprIdSet,
|
||||
newLeftJoinHashJoinConjuncts, newRightJoinHashJoinConjuncts, newTopJoinHashJoinConjuncts);
|
||||
|
||||
List<Expression> newLeftJoinOtherJoinConjuncts = Lists.newArrayList();
|
||||
List<Expression> newRightJoinOtherJoinConjuncts = Lists.newArrayList();
|
||||
List<Expression> newTopJoinOtherJoinConjuncts = new ArrayList<>(leftJoin.getOtherJoinConjuncts());
|
||||
newTopJoinOtherJoinConjuncts.addAll(rightJoin.getOtherJoinConjuncts());
|
||||
JoinExchange.splitTopCondition(topJoin.getOtherJoinConjuncts(), acOutputExprIdSet,
|
||||
bdOutputExprIdSet, newLeftJoinOtherJoinConjuncts, newRightJoinOtherJoinConjuncts,
|
||||
newTopJoinOtherJoinConjuncts);
|
||||
|
||||
if (newLeftJoinHashJoinConjuncts.size() == 0 || newRightJoinHashJoinConjuncts.size() == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
LogicalJoin<GroupPlan, GroupPlan> newLeftJoin = new LogicalJoin<>(JoinType.INNER_JOIN,
|
||||
newLeftJoinHashJoinConjuncts, newLeftJoinOtherJoinConjuncts, JoinHint.NONE, a, c);
|
||||
LogicalJoin<GroupPlan, GroupPlan> newRightJoin = new LogicalJoin<>(JoinType.INNER_JOIN,
|
||||
newRightJoinHashJoinConjuncts, newRightJoinOtherJoinConjuncts, JoinHint.NONE, b, d);
|
||||
// Set<ExprId> topUsedExprIds = new HashSet<>(topJoin.getOutputExprIdSet());
|
||||
// newTopJoinHashJoinConjuncts.forEach(expr -> topUsedExprIds.addAll(expr.getInputSlotExprIds()));
|
||||
// newTopJoinOtherJoinConjuncts.forEach(expr -> topUsedExprIds.addAll(expr.getInputSlotExprIds()));
|
||||
// Plan left = JoinReorderUtils.newProject(topUsedExprIds, newLeftJoin);
|
||||
// Plan right = JoinReorderUtils.newProject(topUsedExprIds, newRightJoin);
|
||||
LogicalJoin newTopJoin = new LogicalJoin<>(JoinType.INNER_JOIN,
|
||||
newTopJoinHashJoinConjuncts, newTopJoinOtherJoinConjuncts, JoinHint.NONE,
|
||||
newLeftJoin, newRightJoin);
|
||||
JoinExchange.setNewLeftJoinReorder(newLeftJoin, leftJoin);
|
||||
JoinExchange.setNewRightJoinReorder(newRightJoin, leftJoin);
|
||||
JoinExchange.setNewTopJoinReorder(newTopJoin, topJoin);
|
||||
|
||||
return JoinReorderUtils.projectOrSelf(new ArrayList<>(topJoin.getOutput()), newTopJoin);
|
||||
}).toRule(RuleType.LOGICAL_JOIN_EXCHANGE);
|
||||
}
|
||||
|
||||
/**
|
||||
* check reorder masks.
|
||||
*/
|
||||
public static boolean checkReorder(LogicalJoin<? extends Plan, ? extends Plan> topJoin) {
|
||||
if (topJoin.getJoinReorderContext().hasCommute()
|
||||
|| topJoin.getJoinReorderContext().hasLeftAssociate()
|
||||
|| topJoin.getJoinReorderContext().hasRightAssociate()
|
||||
|| topJoin.getJoinReorderContext().hasExchange()) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,122 @@
|
||||
// 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.ExprId;
|
||||
import org.apache.doris.nereids.trees.expressions.Expression;
|
||||
import org.apache.doris.nereids.trees.plans.GroupPlan;
|
||||
import org.apache.doris.nereids.trees.plans.JoinHint;
|
||||
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.
|
||||
*/
|
||||
public class JoinExchangeLeftProject extends OneExplorationRuleFactory {
|
||||
public static final JoinExchangeLeftProject INSTANCE = new JoinExchangeLeftProject();
|
||||
|
||||
/*
|
||||
* topJoin newTopJoin
|
||||
* / \ / \
|
||||
* leftJoin rightJoin --> newLeftJoin newRightJoin
|
||||
* / \ / \ / \ / \
|
||||
* A B C D A C B D
|
||||
*/
|
||||
@Override
|
||||
public Rule build() {
|
||||
return innerLogicalJoin(logicalProject(innerLogicalJoin()), innerLogicalJoin())
|
||||
.when(JoinExchange::checkReorder)
|
||||
.when(join -> JoinReorderUtils.isAllSlotProject(join.left()))
|
||||
.whenNot(join -> join.hasJoinHint()
|
||||
|| join.left().child().hasJoinHint() || join.right().hasJoinHint())
|
||||
.whenNot(join -> join.isMarkJoin() || join.left().child().isMarkJoin() || join.right().isMarkJoin())
|
||||
.then(topJoin -> {
|
||||
LogicalJoin<GroupPlan, GroupPlan> leftJoin = topJoin.left().child();
|
||||
LogicalJoin<GroupPlan, GroupPlan> rightJoin = topJoin.right();
|
||||
GroupPlan a = leftJoin.left();
|
||||
GroupPlan b = leftJoin.right();
|
||||
GroupPlan c = rightJoin.left();
|
||||
GroupPlan d = rightJoin.right();
|
||||
|
||||
Set<ExprId> acOutputExprIdSet = JoinUtils.getJoinOutputExprIdSet(a, c);
|
||||
Set<ExprId> bdOutputExprIdSet = JoinUtils.getJoinOutputExprIdSet(b, d);
|
||||
|
||||
List<Expression> newLeftJoinHashJoinConjuncts = Lists.newArrayList();
|
||||
List<Expression> newRightJoinHashJoinConjuncts = Lists.newArrayList();
|
||||
List<Expression> newTopJoinHashJoinConjuncts = new ArrayList<>(leftJoin.getHashJoinConjuncts());
|
||||
newTopJoinHashJoinConjuncts.addAll(rightJoin.getHashJoinConjuncts());
|
||||
JoinExchange.splitTopCondition(topJoin.getHashJoinConjuncts(), acOutputExprIdSet, bdOutputExprIdSet,
|
||||
newLeftJoinHashJoinConjuncts, newRightJoinHashJoinConjuncts, newTopJoinHashJoinConjuncts);
|
||||
|
||||
List<Expression> newLeftJoinOtherJoinConjuncts = Lists.newArrayList();
|
||||
List<Expression> newRightJoinOtherJoinConjuncts = Lists.newArrayList();
|
||||
List<Expression> newTopJoinOtherJoinConjuncts = new ArrayList<>(leftJoin.getOtherJoinConjuncts());
|
||||
newTopJoinOtherJoinConjuncts.addAll(rightJoin.getOtherJoinConjuncts());
|
||||
JoinExchange.splitTopCondition(topJoin.getOtherJoinConjuncts(), acOutputExprIdSet,
|
||||
bdOutputExprIdSet, newLeftJoinOtherJoinConjuncts, newRightJoinOtherJoinConjuncts,
|
||||
newTopJoinOtherJoinConjuncts);
|
||||
|
||||
if (newLeftJoinHashJoinConjuncts.size() == 0 || newRightJoinHashJoinConjuncts.size() == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
LogicalJoin<GroupPlan, GroupPlan> newLeftJoin = new LogicalJoin<>(JoinType.INNER_JOIN,
|
||||
newLeftJoinHashJoinConjuncts, newLeftJoinOtherJoinConjuncts, JoinHint.NONE, a, c);
|
||||
LogicalJoin<GroupPlan, GroupPlan> newRightJoin = new LogicalJoin<>(JoinType.INNER_JOIN,
|
||||
newRightJoinHashJoinConjuncts, newRightJoinOtherJoinConjuncts, JoinHint.NONE, b, d);
|
||||
// Set<ExprId> topUsedExprIds = new HashSet<>(topJoin.getOutputExprIdSet());
|
||||
// newTopJoinHashJoinConjuncts.forEach(expr -> topUsedExprIds.addAll(expr.getInputSlotExprIds()));
|
||||
// newTopJoinOtherJoinConjuncts.forEach(expr -> topUsedExprIds.addAll(expr.getInputSlotExprIds()));
|
||||
// Plan left = JoinReorderUtils.newProject(topUsedExprIds, newLeftJoin);
|
||||
// Plan right = JoinReorderUtils.newProject(topUsedExprIds, newRightJoin);
|
||||
LogicalJoin newTopJoin = new LogicalJoin<>(JoinType.INNER_JOIN,
|
||||
newTopJoinHashJoinConjuncts, newTopJoinOtherJoinConjuncts, JoinHint.NONE,
|
||||
newLeftJoin, newRightJoin);
|
||||
JoinExchange.setNewLeftJoinReorder(newLeftJoin, leftJoin);
|
||||
JoinExchange.setNewRightJoinReorder(newRightJoin, leftJoin);
|
||||
JoinExchange.setNewTopJoinReorder(newTopJoin, topJoin);
|
||||
|
||||
return JoinReorderUtils.projectOrSelf(new ArrayList<>(topJoin.getOutput()), newTopJoin);
|
||||
}).toRule(RuleType.LOGICAL_JOIN_EXCHANGE);
|
||||
}
|
||||
|
||||
/**
|
||||
* check reorder masks.
|
||||
*/
|
||||
public static boolean checkReorder(LogicalJoin<? extends Plan, ? extends Plan> topJoin) {
|
||||
if (topJoin.getJoinReorderContext().hasCommute()
|
||||
|| topJoin.getJoinReorderContext().hasLeftAssociate()
|
||||
|| topJoin.getJoinReorderContext().hasRightAssociate()
|
||||
|| topJoin.getJoinReorderContext().hasExchange()) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,122 @@
|
||||
// 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.ExprId;
|
||||
import org.apache.doris.nereids.trees.expressions.Expression;
|
||||
import org.apache.doris.nereids.trees.plans.GroupPlan;
|
||||
import org.apache.doris.nereids.trees.plans.JoinHint;
|
||||
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.
|
||||
*/
|
||||
public class JoinExchangeRightProject extends OneExplorationRuleFactory {
|
||||
public static final JoinExchangeRightProject INSTANCE = new JoinExchangeRightProject();
|
||||
|
||||
/*
|
||||
* topJoin newTopJoin
|
||||
* / \ / \
|
||||
* leftJoin rightJoin --> newLeftJoin newRightJoin
|
||||
* / \ / \ / \ / \
|
||||
* A B C D A C B D
|
||||
*/
|
||||
@Override
|
||||
public Rule build() {
|
||||
return innerLogicalJoin(innerLogicalJoin(), logicalProject(innerLogicalJoin()))
|
||||
.when(JoinExchange::checkReorder)
|
||||
.when(join -> JoinReorderUtils.isAllSlotProject(join.right()))
|
||||
.whenNot(join -> join.hasJoinHint()
|
||||
|| join.left().hasJoinHint() || join.right().child().hasJoinHint())
|
||||
.whenNot(join -> join.isMarkJoin() || join.left().isMarkJoin() || join.right().child().isMarkJoin())
|
||||
.then(topJoin -> {
|
||||
LogicalJoin<GroupPlan, GroupPlan> leftJoin = topJoin.left();
|
||||
LogicalJoin<GroupPlan, GroupPlan> rightJoin = topJoin.right().child();
|
||||
GroupPlan a = leftJoin.left();
|
||||
GroupPlan b = leftJoin.right();
|
||||
GroupPlan c = rightJoin.left();
|
||||
GroupPlan d = rightJoin.right();
|
||||
|
||||
Set<ExprId> acOutputExprIdSet = JoinUtils.getJoinOutputExprIdSet(a, c);
|
||||
Set<ExprId> bdOutputExprIdSet = JoinUtils.getJoinOutputExprIdSet(b, d);
|
||||
|
||||
List<Expression> newLeftJoinHashJoinConjuncts = Lists.newArrayList();
|
||||
List<Expression> newRightJoinHashJoinConjuncts = Lists.newArrayList();
|
||||
List<Expression> newTopJoinHashJoinConjuncts = new ArrayList<>(leftJoin.getHashJoinConjuncts());
|
||||
newTopJoinHashJoinConjuncts.addAll(rightJoin.getHashJoinConjuncts());
|
||||
JoinExchange.splitTopCondition(topJoin.getHashJoinConjuncts(), acOutputExprIdSet, bdOutputExprIdSet,
|
||||
newLeftJoinHashJoinConjuncts, newRightJoinHashJoinConjuncts, newTopJoinHashJoinConjuncts);
|
||||
|
||||
List<Expression> newLeftJoinOtherJoinConjuncts = Lists.newArrayList();
|
||||
List<Expression> newRightJoinOtherJoinConjuncts = Lists.newArrayList();
|
||||
List<Expression> newTopJoinOtherJoinConjuncts = new ArrayList<>(leftJoin.getOtherJoinConjuncts());
|
||||
newTopJoinOtherJoinConjuncts.addAll(rightJoin.getOtherJoinConjuncts());
|
||||
JoinExchange.splitTopCondition(topJoin.getOtherJoinConjuncts(), acOutputExprIdSet,
|
||||
bdOutputExprIdSet, newLeftJoinOtherJoinConjuncts, newRightJoinOtherJoinConjuncts,
|
||||
newTopJoinOtherJoinConjuncts);
|
||||
|
||||
if (newLeftJoinHashJoinConjuncts.size() == 0 || newRightJoinHashJoinConjuncts.size() == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
LogicalJoin<GroupPlan, GroupPlan> newLeftJoin = new LogicalJoin<>(JoinType.INNER_JOIN,
|
||||
newLeftJoinHashJoinConjuncts, newLeftJoinOtherJoinConjuncts, JoinHint.NONE, a, c);
|
||||
LogicalJoin<GroupPlan, GroupPlan> newRightJoin = new LogicalJoin<>(JoinType.INNER_JOIN,
|
||||
newRightJoinHashJoinConjuncts, newRightJoinOtherJoinConjuncts, JoinHint.NONE, b, d);
|
||||
// Set<ExprId> topUsedExprIds = new HashSet<>(topJoin.getOutputExprIdSet());
|
||||
// newTopJoinHashJoinConjuncts.forEach(expr -> topUsedExprIds.addAll(expr.getInputSlotExprIds()));
|
||||
// newTopJoinOtherJoinConjuncts.forEach(expr -> topUsedExprIds.addAll(expr.getInputSlotExprIds()));
|
||||
// Plan left = JoinReorderUtils.newProject(topUsedExprIds, newLeftJoin);
|
||||
// Plan right = JoinReorderUtils.newProject(topUsedExprIds, newRightJoin);
|
||||
LogicalJoin newTopJoin = new LogicalJoin<>(JoinType.INNER_JOIN,
|
||||
newTopJoinHashJoinConjuncts, newTopJoinOtherJoinConjuncts, JoinHint.NONE,
|
||||
newLeftJoin, newRightJoin);
|
||||
JoinExchange.setNewLeftJoinReorder(newLeftJoin, leftJoin);
|
||||
JoinExchange.setNewRightJoinReorder(newRightJoin, leftJoin);
|
||||
JoinExchange.setNewTopJoinReorder(newTopJoin, topJoin);
|
||||
|
||||
return JoinReorderUtils.projectOrSelf(new ArrayList<>(topJoin.getOutput()), newTopJoin);
|
||||
}).toRule(RuleType.LOGICAL_JOIN_EXCHANGE);
|
||||
}
|
||||
|
||||
/**
|
||||
* check reorder masks.
|
||||
*/
|
||||
public static boolean checkReorder(LogicalJoin<? extends Plan, ? extends Plan> topJoin) {
|
||||
if (topJoin.getJoinReorderContext().hasCommute()
|
||||
|| topJoin.getJoinReorderContext().hasLeftAssociate()
|
||||
|| topJoin.getJoinReorderContext().hasRightAssociate()
|
||||
|| topJoin.getJoinReorderContext().hasExchange()) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -18,12 +18,14 @@
|
||||
package org.apache.doris.nereids.rules.exploration.join;
|
||||
|
||||
import org.apache.doris.nereids.trees.expressions.ExprId;
|
||||
import org.apache.doris.nereids.trees.expressions.Expression;
|
||||
import org.apache.doris.nereids.trees.expressions.NamedExpression;
|
||||
import org.apache.doris.nereids.trees.expressions.Slot;
|
||||
import org.apache.doris.nereids.trees.plans.GroupPlan;
|
||||
import org.apache.doris.nereids.trees.plans.Plan;
|
||||
import org.apache.doris.nereids.trees.plans.logical.LogicalJoin;
|
||||
import org.apache.doris.nereids.trees.plans.logical.LogicalProject;
|
||||
import org.apache.doris.nereids.util.Utils;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@ -52,21 +54,21 @@ class JoinReorderUtils {
|
||||
}
|
||||
|
||||
/**
|
||||
* If projectExprs is empty or project output equal plan output, return the original plan.
|
||||
* If projects is empty or project output equal plan output, return the original plan.
|
||||
*/
|
||||
public static Plan projectOrSelf(List<NamedExpression> projectExprs, Plan plan) {
|
||||
if (projectExprs.isEmpty() || projectExprs.stream().map(NamedExpression::getExprId).collect(Collectors.toSet())
|
||||
.equals(plan.getOutputExprIdSet())) {
|
||||
public static Plan projectOrSelf(List<NamedExpression> projects, Plan plan) {
|
||||
Set<Slot> outputSet = plan.getOutputSet();
|
||||
if (projects.isEmpty() || (outputSet.size() == projects.size() && outputSet.containsAll(projects))) {
|
||||
return plan;
|
||||
}
|
||||
return new LogicalProject<>(projectExprs, plan);
|
||||
return new LogicalProject<>(projects, plan);
|
||||
}
|
||||
|
||||
public static Plan projectOrSelfInOrder(List<NamedExpression> projectExprs, Plan plan) {
|
||||
if (projectExprs.isEmpty() || projectExprs.equals(plan.getOutput())) {
|
||||
public static Plan projectOrSelfInOrder(List<NamedExpression> projects, Plan plan) {
|
||||
if (projects.isEmpty() || projects.equals(plan.getOutput())) {
|
||||
return plan;
|
||||
}
|
||||
return new LogicalProject<>(projectExprs, plan);
|
||||
return new LogicalProject<>(projects, plan);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -92,4 +94,30 @@ class JoinReorderUtils {
|
||||
.filter(childSlots::contains)
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
public static Plan newProject(Set<ExprId> requiredExprIds, Plan plan) {
|
||||
List<NamedExpression> projects = plan.getOutput().stream()
|
||||
.filter(namedExpr -> requiredExprIds.contains(namedExpr.getExprId()))
|
||||
.collect(Collectors.toList());
|
||||
return new LogicalProject<>(projects, plan);
|
||||
}
|
||||
|
||||
public static Map<Boolean, List<Expression>> splitConjuncts(List<Expression> topConjuncts,
|
||||
List<Expression> bottomConjuncts, Set<ExprId> bExprIdSet) {
|
||||
// top: (A B)(error) (A C) (B C) (A B C)
|
||||
// Split topJoin Condition to two part according to include B.
|
||||
Map<Boolean, List<Expression>> splitOn = topConjuncts.stream()
|
||||
.collect(Collectors.partitioningBy(topHashOn -> {
|
||||
Set<ExprId> usedExprIds = topHashOn.getInputSlotExprIds();
|
||||
return Utils.isIntersecting(usedExprIds, bExprIdSet);
|
||||
}));
|
||||
// * don't include B, just include (A C)
|
||||
// we add it into newBottomJoin HashConjuncts.
|
||||
// * include B, include (A B C) or (A B)
|
||||
// we add it into newTopJoin HashConjuncts.
|
||||
List<Expression> newTopHashConjuncts = splitOn.get(true);
|
||||
newTopHashConjuncts.addAll(bottomConjuncts);
|
||||
|
||||
return splitOn;
|
||||
}
|
||||
}
|
||||
|
||||
@ -75,7 +75,7 @@ public class LogicalJoinSemiJoinTransposeProject implements ExplorationRuleFacto
|
||||
Plan newTopJoin = bottomJoin.withChildren(newBottomJoin, c);
|
||||
return JoinReorderUtils.projectOrSelf(new ArrayList<>(topJoin.getOutput()),
|
||||
newTopJoin);
|
||||
}).toRule(RuleType.LOGICAL_JOIN_LOGICAL_SEMI_JOIN_TRANSPOSE)
|
||||
}).toRule(RuleType.LOGICAL_JOIN_LOGICAL_SEMI_JOIN_TRANSPOSE_PROJECT)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -64,7 +64,7 @@ public class PushdownProjectThroughSemiJoin extends OneExplorationRuleFactory {
|
||||
Plan newLeft = JoinReorderUtils.projectOrSelf(newProject, join.left());
|
||||
|
||||
Plan newJoin = join.withChildren(newLeft, join.right());
|
||||
return JoinReorderUtils.projectOrSelf(new ArrayList<>(project.getOutput()), newJoin);
|
||||
return JoinReorderUtils.projectOrSelfInOrder(new ArrayList<>(project.getOutput()), newJoin);
|
||||
}).toRule(RuleType.PUSH_DOWN_PROJECT_THROUGH_SEMI_JOIN);
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,178 @@
|
||||
// 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.datasets.tpch;
|
||||
|
||||
import org.apache.doris.nereids.util.PlanChecker;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class RewriteTPCHTest extends TPCHTestBase {
|
||||
@Test
|
||||
void testQ1() {
|
||||
PlanChecker.from(connectContext)
|
||||
.analyze(TPCHUtils.Q1)
|
||||
.rewrite();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testQ2() {
|
||||
PlanChecker.from(connectContext)
|
||||
.analyze(TPCHUtils.Q2)
|
||||
.rewrite();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testQ3() {
|
||||
PlanChecker.from(connectContext)
|
||||
.analyze(TPCHUtils.Q3)
|
||||
.rewrite();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testQ4() {
|
||||
PlanChecker.from(connectContext)
|
||||
.analyze(TPCHUtils.Q4)
|
||||
.rewrite();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testQ5() {
|
||||
PlanChecker.from(connectContext)
|
||||
.analyze(TPCHUtils.Q5)
|
||||
.rewrite();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testQ6() {
|
||||
PlanChecker.from(connectContext)
|
||||
.analyze(TPCHUtils.Q6)
|
||||
.rewrite();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testQ7() {
|
||||
PlanChecker.from(connectContext)
|
||||
.analyze(TPCHUtils.Q7)
|
||||
.rewrite();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testQ8() {
|
||||
PlanChecker.from(connectContext)
|
||||
.analyze(TPCHUtils.Q8)
|
||||
.rewrite();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testQ9() {
|
||||
PlanChecker.from(connectContext)
|
||||
.analyze(TPCHUtils.Q9)
|
||||
.rewrite();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testQ10() {
|
||||
PlanChecker.from(connectContext)
|
||||
.analyze(TPCHUtils.Q10)
|
||||
.rewrite();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testQ11() {
|
||||
PlanChecker.from(connectContext)
|
||||
.analyze(TPCHUtils.Q11)
|
||||
.rewrite();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testQ12() {
|
||||
PlanChecker.from(connectContext)
|
||||
.analyze(TPCHUtils.Q12)
|
||||
.rewrite();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testQ13() {
|
||||
PlanChecker.from(connectContext)
|
||||
.analyze(TPCHUtils.Q13)
|
||||
.rewrite();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testQ14() {
|
||||
PlanChecker.from(connectContext)
|
||||
.analyze(TPCHUtils.Q14)
|
||||
.rewrite();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testQ15() {
|
||||
PlanChecker.from(connectContext)
|
||||
.analyze(TPCHUtils.Q15)
|
||||
.rewrite();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testQ16() {
|
||||
PlanChecker.from(connectContext)
|
||||
.analyze(TPCHUtils.Q16)
|
||||
.rewrite();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testQ17() {
|
||||
PlanChecker.from(connectContext)
|
||||
.analyze(TPCHUtils.Q17)
|
||||
.rewrite();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testQ18() {
|
||||
PlanChecker.from(connectContext)
|
||||
.analyze(TPCHUtils.Q18)
|
||||
.rewrite();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testQ19() {
|
||||
PlanChecker.from(connectContext)
|
||||
.analyze(TPCHUtils.Q19)
|
||||
.rewrite();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testQ20() {
|
||||
PlanChecker.from(connectContext)
|
||||
.analyze(TPCHUtils.Q20)
|
||||
.rewrite();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testQ21() {
|
||||
PlanChecker.from(connectContext)
|
||||
.analyze(TPCHUtils.Q21)
|
||||
.rewrite();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testQ22() {
|
||||
PlanChecker.from(connectContext)
|
||||
.analyze(TPCHUtils.Q22)
|
||||
.rewrite();
|
||||
}
|
||||
}
|
||||
@ -52,7 +52,8 @@ class InnerJoinLAsscomProjectTest implements MemoPatternMatchSupported {
|
||||
private final LogicalOlapScan scan3 = PlanConstructor.newLogicalOlapScan(2, "t3", 0);
|
||||
|
||||
@Test
|
||||
void testJoinLAsscomProject() {
|
||||
@Disabled
|
||||
void testSimple() {
|
||||
/*
|
||||
* Star-Join
|
||||
* t1 -- t2
|
||||
@ -91,6 +92,7 @@ class InnerJoinLAsscomProjectTest implements MemoPatternMatchSupported {
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
void testAlias() {
|
||||
LogicalPlan plan = new LogicalPlanBuilder(scan1)
|
||||
.join(scan2, JoinType.INNER_JOIN, Pair.of(0, 0))
|
||||
@ -99,7 +101,6 @@ class InnerJoinLAsscomProjectTest implements MemoPatternMatchSupported {
|
||||
.build();
|
||||
|
||||
PlanChecker.from(MemoTestUtils.createConnectContext(), plan)
|
||||
.printlnTree()
|
||||
.applyTopDown(new PushdownAliasThroughJoin())
|
||||
.applyExploration(InnerJoinLAsscomProject.INSTANCE.build())
|
||||
.printlnExploration()
|
||||
@ -107,20 +108,22 @@ class InnerJoinLAsscomProjectTest implements MemoPatternMatchSupported {
|
||||
logicalJoin(
|
||||
logicalProject(
|
||||
logicalJoin(
|
||||
logicalProject(logicalOlapScan().when(
|
||||
scan -> scan.getTable().getName().equals("t1"))),
|
||||
logicalProject(logicalOlapScan().when(scan -> scan.getTable().getName().equals("t1"))),
|
||||
logicalOlapScan().when(scan -> scan.getTable().getName().equals("t3"))
|
||||
)
|
||||
).when(project -> project.getProjects().size() == 3), // t1.id Add t3.id, t3.name
|
||||
).when(project -> project.getProjects().size() == 3),
|
||||
// t1.id Add t3.id, t3.name
|
||||
logicalProject(
|
||||
logicalProject(
|
||||
logicalOlapScan().when(scan -> scan.getTable().getName().equals("t2")))
|
||||
logicalOlapScan().when(scan -> scan.getTable().getName().equals("t2"))
|
||||
)
|
||||
).when(project -> project.getProjects().size() == 1)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
public void testHashAndOther() {
|
||||
// Alias (scan1 join scan2 on scan1.id=scan2.id and scan1.name>scan2.name);
|
||||
List<Expression> bottomHashJoinConjunct = ImmutableList.of(
|
||||
|
||||
@ -0,0 +1,69 @@
|
||||
// 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.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.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class InnerJoinLeftAssociateProjectTest implements MemoPatternMatchSupported {
|
||||
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
|
||||
@Disabled
|
||||
void testSimple() {
|
||||
LogicalPlan plan = new LogicalPlanBuilder(scan1)
|
||||
.join(
|
||||
new LogicalPlanBuilder(scan2)
|
||||
.join(scan3, JoinType.INNER_JOIN, Pair.of(0, 0))
|
||||
.project(ImmutableList.of(0, 2))
|
||||
.build(),
|
||||
JoinType.INNER_JOIN, Pair.of(0, 0)
|
||||
)
|
||||
.build();
|
||||
|
||||
PlanChecker.from(MemoTestUtils.createConnectContext(), plan)
|
||||
.applyExploration(InnerJoinLeftAssociateProject.INSTANCE.build())
|
||||
.printlnExploration()
|
||||
.matchesExploration(
|
||||
logicalJoin(
|
||||
logicalProject(
|
||||
logicalJoin(
|
||||
logicalOlapScan().when(scan -> scan.getTable().getName().equals("t1")),
|
||||
logicalOlapScan().when(scan -> scan.getTable().getName().equals("t2"))
|
||||
)
|
||||
),
|
||||
logicalProject(
|
||||
logicalOlapScan().when(scan -> scan.getTable().getName().equals("t3"))
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,64 @@
|
||||
// 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.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.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class InnerJoinRightAssociateProjectTest implements MemoPatternMatchSupported {
|
||||
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
|
||||
@Disabled
|
||||
void testSimple() {
|
||||
LogicalPlan plan = new LogicalPlanBuilder(scan1)
|
||||
.join(scan2, JoinType.INNER_JOIN, Pair.of(0, 0))
|
||||
.project(ImmutableList.of(0, 2))
|
||||
.join(scan3, JoinType.INNER_JOIN, Pair.of(1, 0))
|
||||
.build();
|
||||
|
||||
PlanChecker.from(MemoTestUtils.createConnectContext(), plan)
|
||||
.applyExploration(InnerJoinRightAssociateProject.INSTANCE.build())
|
||||
.matchesExploration(
|
||||
logicalJoin(
|
||||
logicalProject(
|
||||
logicalOlapScan().when(scan -> scan.getTable().getName().equals("t1"))),
|
||||
logicalProject(
|
||||
logicalJoin(
|
||||
logicalOlapScan().when(scan -> scan.getTable().getName().equals("t2")),
|
||||
logicalOlapScan().when(scan -> scan.getTable().getName().equals("t3"))
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,76 @@
|
||||
// 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.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.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class JoinExchangeBothProjectTest implements MemoPatternMatchSupported {
|
||||
@Test
|
||||
@Disabled
|
||||
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)
|
||||
.join(scan2, JoinType.INNER_JOIN, Pair.of(0, 0))
|
||||
.project(ImmutableList.of(0, 2))
|
||||
.build())
|
||||
.join(
|
||||
new LogicalPlanBuilder(scan3)
|
||||
.join(scan4, JoinType.INNER_JOIN, Pair.of(0, 0))
|
||||
.project(ImmutableList.of(0, 2))
|
||||
.build(),
|
||||
JoinType.INNER_JOIN, ImmutableList.of(Pair.of(0, 0), Pair.of(1, 1)))
|
||||
.build();
|
||||
|
||||
PlanChecker.from(MemoTestUtils.createConnectContext(), plan)
|
||||
.applyExploration(JoinExchangeBothProject.INSTANCE.build())
|
||||
.matchesExploration(
|
||||
logicalJoin(
|
||||
logicalProject(
|
||||
logicalJoin(
|
||||
logicalOlapScan().when(scan -> scan.getTable().getName().equals("t1")),
|
||||
logicalOlapScan().when(scan -> scan.getTable().getName().equals("t3"))
|
||||
)
|
||||
),
|
||||
logicalProject(
|
||||
logicalJoin(
|
||||
logicalOlapScan().when(scan -> scan.getTable().getName().equals("t2")),
|
||||
logicalOlapScan().when(scan -> scan.getTable().getName().equals("t4"))
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,74 @@
|
||||
// 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.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.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class JoinExchangeLeftProjectTest implements MemoPatternMatchSupported {
|
||||
@Test
|
||||
@Disabled
|
||||
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)
|
||||
.join(scan2, JoinType.INNER_JOIN, Pair.of(0, 0))
|
||||
.project(ImmutableList.of(0, 1, 2))
|
||||
.build())
|
||||
.join(
|
||||
new LogicalPlanBuilder(scan3)
|
||||
.join(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(JoinExchangeLeftProject.INSTANCE.build())
|
||||
.printlnExploration()
|
||||
.matchesExploration(
|
||||
logicalJoin(
|
||||
logicalJoin(
|
||||
logicalOlapScan().when(scan -> scan.getTable().getName().equals("t1")),
|
||||
logicalOlapScan().when(scan -> scan.getTable().getName().equals("t3"))
|
||||
),
|
||||
logicalProject(
|
||||
logicalJoin(
|
||||
logicalOlapScan().when(scan -> scan.getTable().getName().equals("t2")),
|
||||
logicalOlapScan().when(scan -> scan.getTable().getName().equals("t4"))
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,74 @@
|
||||
// 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.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.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class JoinExchangeRightProjectTest implements MemoPatternMatchSupported {
|
||||
@Test
|
||||
@Disabled
|
||||
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)
|
||||
.join(scan2, JoinType.INNER_JOIN, Pair.of(0, 0))
|
||||
.build())
|
||||
.join(
|
||||
new LogicalPlanBuilder(scan3)
|
||||
.join(scan4, JoinType.INNER_JOIN, Pair.of(0, 0))
|
||||
.project(ImmutableList.of(0, 1, 2))
|
||||
.build(),
|
||||
JoinType.INNER_JOIN, ImmutableList.of(Pair.of(0, 0), Pair.of(2, 2)))
|
||||
.build();
|
||||
|
||||
PlanChecker.from(MemoTestUtils.createConnectContext(), plan)
|
||||
.applyExploration(JoinExchangeRightProject.INSTANCE.build())
|
||||
.printlnExploration()
|
||||
.matchesExploration(
|
||||
logicalJoin(
|
||||
logicalJoin(
|
||||
logicalOlapScan().when(scan -> scan.getTable().getName().equals("t1")),
|
||||
logicalOlapScan().when(scan -> scan.getTable().getName().equals("t3"))
|
||||
),
|
||||
logicalProject(
|
||||
logicalJoin(
|
||||
logicalOlapScan().when(scan -> scan.getTable().getName().equals("t2")),
|
||||
logicalOlapScan().when(scan -> scan.getTable().getName().equals("t4"))
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user