[feature-wip](nereids) Implement using join (#15311)
This commit is contained in:
@ -215,22 +215,6 @@ public class CascadesContext {
|
||||
return this;
|
||||
}
|
||||
|
||||
public void addToTable(Table table) {
|
||||
tables.add(table);
|
||||
}
|
||||
|
||||
public void lockTableOnRead() {
|
||||
for (Table t : tables) {
|
||||
t.readLock();
|
||||
}
|
||||
}
|
||||
|
||||
public void releaseTableReadLock() {
|
||||
for (Table t : tables) {
|
||||
t.readUnlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract tables.
|
||||
*/
|
||||
|
||||
@ -199,6 +199,7 @@ import org.apache.doris.nereids.trees.plans.logical.LogicalSort;
|
||||
import org.apache.doris.nereids.trees.plans.logical.LogicalSubQueryAlias;
|
||||
import org.apache.doris.nereids.trees.plans.logical.LogicalUnion;
|
||||
import org.apache.doris.nereids.trees.plans.logical.RelationUtil;
|
||||
import org.apache.doris.nereids.trees.plans.logical.UsingJoin;
|
||||
import org.apache.doris.nereids.types.DataType;
|
||||
import org.apache.doris.nereids.types.IntegerType;
|
||||
import org.apache.doris.nereids.types.TinyIntType;
|
||||
@ -1249,28 +1250,6 @@ public class LogicalPlanBuilder extends DorisParserBaseVisitor<Object> {
|
||||
} else {
|
||||
joinType = JoinType.CROSS_JOIN;
|
||||
}
|
||||
|
||||
// TODO: natural join, lateral join, using join, union join
|
||||
JoinCriteriaContext joinCriteria = join.joinCriteria();
|
||||
Optional<Expression> condition = Optional.empty();
|
||||
if (joinCriteria != null) {
|
||||
if (joinCriteria.booleanExpression() != null) {
|
||||
condition = Optional.ofNullable(getExpression(joinCriteria.booleanExpression()));
|
||||
}
|
||||
if (joinCriteria.USING() != null) {
|
||||
List<UnboundSlot> ids =
|
||||
visitIdentifierList(joinCriteria.identifierList())
|
||||
.stream().map(UnboundSlot::quoted).collect(
|
||||
Collectors.toList());
|
||||
return new LogicalJoin(JoinType.USING_JOIN, ids, last, plan(join.relationPrimary()));
|
||||
}
|
||||
} else {
|
||||
// keep same with original planner, allow cross/inner join
|
||||
if (!joinType.isInnerOrCrossJoin()) {
|
||||
throw new ParseException("on mustn't be empty except for cross/inner join", join);
|
||||
}
|
||||
}
|
||||
|
||||
JoinHint joinHint = Optional.ofNullable(join.joinHint()).map(hintCtx -> {
|
||||
String hint = typedVisit(join.joinHint());
|
||||
if (JoinHint.JoinHintType.SHUFFLE.toString().equalsIgnoreCase(hint)) {
|
||||
@ -1281,13 +1260,35 @@ public class LogicalPlanBuilder extends DorisParserBaseVisitor<Object> {
|
||||
throw new ParseException("Invalid join hint: " + hint, hintCtx);
|
||||
}
|
||||
}).orElse(JoinHint.NONE);
|
||||
|
||||
last = new LogicalJoin<>(joinType, ExpressionUtils.EMPTY_CONDITION,
|
||||
condition.map(ExpressionUtils::extractConjunction)
|
||||
.orElse(ExpressionUtils.EMPTY_CONDITION),
|
||||
joinHint,
|
||||
last,
|
||||
plan(join.relationPrimary()));
|
||||
// TODO: natural join, lateral join, using join, union join
|
||||
JoinCriteriaContext joinCriteria = join.joinCriteria();
|
||||
Optional<Expression> condition = Optional.empty();
|
||||
List<UnboundSlot> ids = null;
|
||||
if (joinCriteria != null) {
|
||||
if (joinCriteria.booleanExpression() != null) {
|
||||
condition = Optional.ofNullable(getExpression(joinCriteria.booleanExpression()));
|
||||
} else if (joinCriteria.USING() != null) {
|
||||
ids = visitIdentifierList(joinCriteria.identifierList())
|
||||
.stream().map(UnboundSlot::quoted).collect(
|
||||
Collectors.toList());
|
||||
}
|
||||
} else {
|
||||
// keep same with original planner, allow cross/inner join
|
||||
if (!joinType.isInnerOrCrossJoin()) {
|
||||
throw new ParseException("on mustn't be empty except for cross/inner join", join);
|
||||
}
|
||||
}
|
||||
if (ids == null) {
|
||||
last = new LogicalJoin<>(joinType, ExpressionUtils.EMPTY_CONDITION,
|
||||
condition.map(ExpressionUtils::extractConjunction)
|
||||
.orElse(ExpressionUtils.EMPTY_CONDITION),
|
||||
joinHint,
|
||||
last,
|
||||
plan(join.relationPrimary()));
|
||||
} else {
|
||||
last = new UsingJoin(joinType, last,
|
||||
plan(join.relationPrimary()), Collections.emptyList(), ids, joinHint);
|
||||
}
|
||||
}
|
||||
return last;
|
||||
}
|
||||
|
||||
@ -66,6 +66,7 @@ import org.apache.doris.nereids.trees.plans.logical.LogicalProject;
|
||||
import org.apache.doris.nereids.trees.plans.logical.LogicalRepeat;
|
||||
import org.apache.doris.nereids.trees.plans.logical.LogicalSetOperation;
|
||||
import org.apache.doris.nereids.trees.plans.logical.LogicalSort;
|
||||
import org.apache.doris.nereids.trees.plans.logical.UsingJoin;
|
||||
import org.apache.doris.planner.PlannerContext;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
@ -76,6 +77,7 @@ import org.apache.commons.lang.StringUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
@ -127,41 +129,63 @@ public class BindSlotReference implements AnalysisRuleFactory {
|
||||
return new LogicalFilter<>(boundConjuncts, filter.child());
|
||||
})
|
||||
),
|
||||
RuleType.BINDING_JOIN_SLOT.build(
|
||||
logicalJoin().when(Plan::canBind)
|
||||
.whenNot(j -> j.getJoinType().equals(JoinType.USING_JOIN)).thenApply(ctx -> {
|
||||
LogicalJoin<GroupPlan, GroupPlan> join = ctx.root;
|
||||
List<Expression> cond = join.getOtherJoinConjuncts().stream()
|
||||
.map(expr -> bind(expr, join.children(), join, ctx.cascadesContext))
|
||||
.collect(Collectors.toList());
|
||||
List<Expression> hashJoinConjuncts = join.getHashJoinConjuncts().stream()
|
||||
.map(expr -> bind(expr, join.children(), join, ctx.cascadesContext))
|
||||
.collect(Collectors.toList());
|
||||
return new LogicalJoin<>(join.getJoinType(),
|
||||
hashJoinConjuncts, cond, join.getHint(), join.left(), join.right());
|
||||
})
|
||||
),
|
||||
|
||||
RuleType.BINDING_USING_JOIN_SLOT.build(
|
||||
logicalJoin().when(j -> j.getJoinType().equals(JoinType.USING_JOIN)).thenApply(ctx -> {
|
||||
LogicalJoin<GroupPlan, GroupPlan> join = ctx.root;
|
||||
List<Expression> unboundSlots = join.getHashJoinConjuncts();
|
||||
List<Expression> leftSlots = unboundSlots.stream()
|
||||
.map(expr -> bind(expr, Collections.singletonList(join.left()),
|
||||
join, ctx.cascadesContext))
|
||||
.collect(Collectors.toList());
|
||||
List<Expression> rightSlots = unboundSlots.stream()
|
||||
.map(expr -> bind(expr, Collections.singletonList(join.right()),
|
||||
join, ctx.cascadesContext))
|
||||
.collect(Collectors.toList());
|
||||
usingJoin().thenApply(ctx -> {
|
||||
UsingJoin<GroupPlan, GroupPlan> using = ctx.root;
|
||||
LogicalJoin lj = new LogicalJoin(using.getJoinType() == JoinType.CROSS_JOIN
|
||||
? JoinType.INNER_JOIN : using.getJoinType(),
|
||||
using.getHashJoinConjuncts(),
|
||||
using.getOtherJoinConjuncts(), using.getHint(), using.left(),
|
||||
using.right());
|
||||
List<Expression> unboundSlots = lj.getHashJoinConjuncts();
|
||||
Set<String> slotNames = new HashSet<>();
|
||||
List<Slot> leftOutput = new ArrayList<>(lj.left().getOutput());
|
||||
// Suppose A JOIN B USING(name) JOIN C USING(name), [A JOIN B] is the left node, in this case,
|
||||
// C should combine with table B on C.name=B.name. so we reverse the output to make sure that
|
||||
// the most right slot is matched with priority.
|
||||
Collections.reverse(leftOutput);
|
||||
List<Expression> leftSlots = new ArrayList<>();
|
||||
Scope scope = toScope(leftOutput.stream()
|
||||
.filter(s -> !slotNames.contains(s.getName()))
|
||||
.peek(s -> slotNames.add(s.getName())).collect(
|
||||
Collectors.toList()));
|
||||
for (Expression unboundSlot : unboundSlots) {
|
||||
Expression expression = new SlotBinder(scope, lj, ctx.cascadesContext).bind(unboundSlot);
|
||||
leftSlots.add(expression);
|
||||
}
|
||||
slotNames.clear();
|
||||
scope = toScope(lj.right().getOutput().stream()
|
||||
.filter(s -> !slotNames.contains(s.getName()))
|
||||
.peek(s -> slotNames.add(s.getName())).collect(
|
||||
Collectors.toList()));
|
||||
List<Expression> rightSlots = new ArrayList<>();
|
||||
for (Expression unboundSlot : unboundSlots) {
|
||||
Expression expression = new SlotBinder(scope, lj, ctx.cascadesContext).bind(unboundSlot);
|
||||
rightSlots.add(expression);
|
||||
}
|
||||
int size = leftSlots.size();
|
||||
List<Expression> hashEqExpr = new ArrayList<>();
|
||||
for (int i = 0; i < size; i++) {
|
||||
hashEqExpr.add(new EqualTo(leftSlots.get(i), rightSlots.get(i)));
|
||||
}
|
||||
return new LogicalJoin(JoinType.INNER_JOIN, hashEqExpr,
|
||||
join.getOtherJoinConjuncts(), join.getHint(), join.left(), join.right());
|
||||
return lj.withHashJoinConjuncts(hashEqExpr);
|
||||
})
|
||||
),
|
||||
RuleType.BINDING_JOIN_SLOT.build(
|
||||
logicalJoin().when(Plan::canBind)
|
||||
.whenNot(j -> j.getJoinType().equals(JoinType.USING_JOIN)).thenApply(ctx -> {
|
||||
LogicalJoin<GroupPlan, GroupPlan> join = ctx.root;
|
||||
List<Expression> cond = join.getOtherJoinConjuncts().stream()
|
||||
.map(expr -> bind(expr, join.children(), join, ctx.cascadesContext))
|
||||
.collect(Collectors.toList());
|
||||
List<Expression> hashJoinConjuncts = join.getHashJoinConjuncts().stream()
|
||||
.map(expr -> bind(expr, join.children(), join, ctx.cascadesContext))
|
||||
.collect(Collectors.toList());
|
||||
return new LogicalJoin<>(join.getJoinType(),
|
||||
hashJoinConjuncts, cond, join.getHint(), join.left(), join.right());
|
||||
})
|
||||
),
|
||||
RuleType.BINDING_AGGREGATE_SLOT.build(
|
||||
logicalAggregate().when(Plan::canBind).thenApply(ctx -> {
|
||||
LogicalAggregate<GroupPlan> agg = ctx.root;
|
||||
|
||||
@ -52,6 +52,7 @@ public enum PlanType {
|
||||
LOGICAL_UNION,
|
||||
LOGICAL_EXCEPT,
|
||||
LOGICAL_INTERSECT,
|
||||
LOGICAL_USING_JOIN,
|
||||
GROUP_PLAN,
|
||||
|
||||
// physical plan
|
||||
|
||||
@ -0,0 +1,169 @@
|
||||
// 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.trees.plans.logical;
|
||||
|
||||
import org.apache.doris.nereids.memo.GroupExpression;
|
||||
import org.apache.doris.nereids.properties.LogicalProperties;
|
||||
import org.apache.doris.nereids.trees.expressions.Expression;
|
||||
import org.apache.doris.nereids.trees.expressions.Slot;
|
||||
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.PlanType;
|
||||
import org.apache.doris.nereids.trees.plans.algebra.Join;
|
||||
import org.apache.doris.nereids.trees.plans.visitor.PlanVisitor;
|
||||
import org.apache.doris.nereids.util.ExpressionUtils;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableList.Builder;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* select col1 from t1 join t2 using(col1);
|
||||
*/
|
||||
public class UsingJoin<LEFT_CHILD_TYPE extends Plan, RIGHT_CHILD_TYPE extends Plan>
|
||||
extends LogicalBinary<LEFT_CHILD_TYPE, RIGHT_CHILD_TYPE> implements Join {
|
||||
|
||||
private final JoinType joinType;
|
||||
private final ImmutableList<Expression> otherJoinConjuncts;
|
||||
private final ImmutableList<Expression> hashJoinConjuncts;
|
||||
private final JoinHint hint;
|
||||
|
||||
public UsingJoin(JoinType joinType, LEFT_CHILD_TYPE leftChild, RIGHT_CHILD_TYPE rightChild,
|
||||
List<Expression> expressions, List<Expression> hashJoinConjuncts,
|
||||
JoinHint hint) {
|
||||
this(joinType, leftChild, rightChild, expressions,
|
||||
hashJoinConjuncts, Optional.empty(), Optional.empty(), hint);
|
||||
}
|
||||
|
||||
public UsingJoin(JoinType joinType, LEFT_CHILD_TYPE leftChild, RIGHT_CHILD_TYPE rightChild,
|
||||
List<Expression> expressions, List<Expression> hashJoinConjuncts, Optional<GroupExpression> groupExpression,
|
||||
Optional<LogicalProperties> logicalProperties,
|
||||
JoinHint hint) {
|
||||
super(PlanType.LOGICAL_USING_JOIN, groupExpression, logicalProperties, leftChild, rightChild);
|
||||
this.joinType = joinType;
|
||||
this.otherJoinConjuncts = ImmutableList.copyOf(expressions);
|
||||
this.hashJoinConjuncts = ImmutableList.copyOf(hashJoinConjuncts);
|
||||
this.hint = hint;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Slot> computeOutput() {
|
||||
|
||||
List<Slot> newLeftOutput = left().getOutput().stream().map(o -> o.withNullable(true))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
List<Slot> newRightOutput = right().getOutput().stream().map(o -> o.withNullable(true))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
switch (joinType) {
|
||||
case LEFT_SEMI_JOIN:
|
||||
case LEFT_ANTI_JOIN:
|
||||
return ImmutableList.copyOf(left().getOutput());
|
||||
case RIGHT_SEMI_JOIN:
|
||||
case RIGHT_ANTI_JOIN:
|
||||
return ImmutableList.copyOf(right().getOutput());
|
||||
case LEFT_OUTER_JOIN:
|
||||
return ImmutableList.<Slot>builder()
|
||||
.addAll(left().getOutput())
|
||||
.addAll(newRightOutput)
|
||||
.build();
|
||||
case RIGHT_OUTER_JOIN:
|
||||
return ImmutableList.<Slot>builder()
|
||||
.addAll(newLeftOutput)
|
||||
.addAll(right().getOutput())
|
||||
.build();
|
||||
case FULL_OUTER_JOIN:
|
||||
return ImmutableList.<Slot>builder()
|
||||
.addAll(newLeftOutput)
|
||||
.addAll(newRightOutput)
|
||||
.build();
|
||||
default:
|
||||
return ImmutableList.<Slot>builder()
|
||||
.addAll(left().getOutput())
|
||||
.addAll(right().getOutput())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Plan withGroupExpression(Optional<GroupExpression> groupExpression) {
|
||||
return new UsingJoin(joinType, child(0), child(1), otherJoinConjuncts,
|
||||
hashJoinConjuncts, groupExpression, Optional.of(getLogicalProperties()), hint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Plan withLogicalProperties(Optional<LogicalProperties> logicalProperties) {
|
||||
return new UsingJoin(joinType, child(0), child(1), otherJoinConjuncts,
|
||||
hashJoinConjuncts, groupExpression, logicalProperties, hint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Plan withChildren(List<Plan> children) {
|
||||
return new UsingJoin(joinType, children.get(0), children.get(1), otherJoinConjuncts,
|
||||
hashJoinConjuncts, groupExpression, Optional.of(getLogicalProperties()), hint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <R, C> R accept(PlanVisitor<R, C> visitor, C context) {
|
||||
return visitor.visit(this, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<? extends Expression> getExpressions() {
|
||||
return new Builder<Expression>()
|
||||
.addAll(hashJoinConjuncts)
|
||||
.addAll(otherJoinConjuncts)
|
||||
.build();
|
||||
}
|
||||
|
||||
public JoinType getJoinType() {
|
||||
return joinType;
|
||||
}
|
||||
|
||||
public List<Expression> getOtherJoinConjuncts() {
|
||||
return otherJoinConjuncts;
|
||||
}
|
||||
|
||||
public List<Expression> getHashJoinConjuncts() {
|
||||
return hashJoinConjuncts;
|
||||
}
|
||||
|
||||
public JoinHint getHint() {
|
||||
return hint;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Expression> getOnClauseCondition() {
|
||||
return ExpressionUtils.optionalAnd(hashJoinConjuncts, otherJoinConjuncts);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasJoinHint() {
|
||||
return hint != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasJoinCondition() {
|
||||
return !CollectionUtils.isEmpty(hashJoinConjuncts);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user