/*
* Licensed 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 com.facebook.presto.sql.planner.optimizations;
import com.facebook.presto.Session;
import com.facebook.presto.spi.type.Type;
import com.facebook.presto.sql.planner.PlanNodeIdAllocator;
import com.facebook.presto.sql.planner.Symbol;
import com.facebook.presto.sql.planner.SymbolAllocator;
import com.facebook.presto.sql.planner.plan.DeleteNode;
import com.facebook.presto.sql.planner.plan.JoinNode;
import com.facebook.presto.sql.planner.plan.PlanNode;
import com.facebook.presto.sql.planner.plan.SemiJoinNode;
import com.facebook.presto.sql.planner.plan.SimplePlanRewriter;
import java.util.Map;
import java.util.Optional;
import static com.facebook.presto.SystemSessionProperties.isDistributedJoinEnabled;
import static com.facebook.presto.sql.planner.optimizations.ScalarQueryUtil.isScalar;
import static com.facebook.presto.sql.planner.plan.JoinNode.Type.FULL;
import static com.facebook.presto.sql.planner.plan.JoinNode.Type.INNER;
import static com.facebook.presto.sql.planner.plan.JoinNode.Type.RIGHT;
import static java.util.Objects.requireNonNull;
public class DetermineJoinDistributionType
implements PlanOptimizer
{
@Override
public PlanNode optimize(PlanNode plan, Session session, Map<Symbol, Type> types, SymbolAllocator symbolAllocator, PlanNodeIdAllocator idAllocator)
{
requireNonNull(plan, "plan is null");
requireNonNull(session, "session is null");
return SimplePlanRewriter.rewriteWith(new Rewriter(session), plan);
}
private static class Rewriter
extends SimplePlanRewriter<Void>
{
private final Session session;
private boolean isDeleteQuery;
public Rewriter(Session session)
{
this.session = session;
}
@Override
public PlanNode visitJoin(JoinNode node, RewriteContext<Void> context)
{
PlanNode leftRewritten = context.rewrite(node.getLeft(), context.get());
PlanNode rightRewritten = context.rewrite(node.getRight(), context.get());
JoinNode.DistributionType targetJoinDistributionType = getTargetJoinDistributionType(node);
return new JoinNode(
node.getId(),
node.getType(),
leftRewritten,
rightRewritten,
node.getCriteria(),
node.getOutputSymbols(),
node.getFilter(),
node.getLeftHashSymbol(),
node.getRightHashSymbol(),
Optional.of(targetJoinDistributionType));
}
@Override
public PlanNode visitSemiJoin(SemiJoinNode node, RewriteContext<Void> context)
{
PlanNode sourceRewritten = context.rewrite(node.getSource(), context.get());
PlanNode filteringSourceRewritten = context.rewrite(node.getFilteringSource(), context.get());
SemiJoinNode.DistributionType targetJoinDistributionType = getTargetSemiJoinDistributionType(isDeleteQuery);
return new SemiJoinNode(
node.getId(),
sourceRewritten,
filteringSourceRewritten,
node.getSourceJoinSymbol(),
node.getFilteringSourceJoinSymbol(),
node.getSemiJoinOutput(),
node.getSourceHashSymbol(),
node.getFilteringSourceHashSymbol(),
Optional.of(targetJoinDistributionType));
}
@Override
public PlanNode visitDelete(DeleteNode node, RewriteContext<Void> context)
{
// For delete queries, the TableScan node that corresponds to the table being deleted must be collocated with the Delete node,
// so you can't do a distributed semi-join
isDeleteQuery = true;
PlanNode rewrittenSource = context.rewrite(node.getSource());
return new DeleteNode(
node.getId(),
rewrittenSource,
node.getTarget(),
node.getRowId(),
node.getOutputSymbols());
}
private JoinNode.DistributionType getTargetJoinDistributionType(JoinNode node)
{
// The implementation of full outer join only works if the data is hash partitioned. See LookupJoinOperators#buildSideOuterJoinUnvisitedPositions
JoinNode.Type type = node.getType();
if (type == RIGHT || type == FULL || (isDistributedJoinEnabled(session) && !mustBroadcastJoin(node))) {
return JoinNode.DistributionType.PARTITIONED;
}
return JoinNode.DistributionType.REPLICATED;
}
private static boolean mustBroadcastJoin(JoinNode node)
{
return isScalar(node.getRight()) || isCrossJoin(node);
}
private static boolean isCrossJoin(JoinNode node)
{
return node.getType() == INNER && node.getCriteria().isEmpty();
}
private SemiJoinNode.DistributionType getTargetSemiJoinDistributionType(boolean isDeleteQuery)
{
if (isDistributedJoinEnabled(session) && !isDeleteQuery) {
return SemiJoinNode.DistributionType.PARTITIONED;
}
return SemiJoinNode.DistributionType.REPLICATED;
}
}
}