/* * 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; import com.facebook.presto.Session; import com.facebook.presto.split.SampledSplitSource; import com.facebook.presto.split.SplitManager; import com.facebook.presto.split.SplitSource; import com.facebook.presto.sql.planner.plan.AggregationNode; import com.facebook.presto.sql.planner.plan.AssignUniqueId; import com.facebook.presto.sql.planner.plan.DeleteNode; import com.facebook.presto.sql.planner.plan.DistinctLimitNode; import com.facebook.presto.sql.planner.plan.EnforceSingleRowNode; import com.facebook.presto.sql.planner.plan.ExchangeNode; import com.facebook.presto.sql.planner.plan.ExplainAnalyzeNode; import com.facebook.presto.sql.planner.plan.FilterNode; import com.facebook.presto.sql.planner.plan.GroupIdNode; import com.facebook.presto.sql.planner.plan.IndexJoinNode; import com.facebook.presto.sql.planner.plan.JoinNode; import com.facebook.presto.sql.planner.plan.LimitNode; import com.facebook.presto.sql.planner.plan.MarkDistinctNode; import com.facebook.presto.sql.planner.plan.MetadataDeleteNode; import com.facebook.presto.sql.planner.plan.OutputNode; import com.facebook.presto.sql.planner.plan.PlanNode; import com.facebook.presto.sql.planner.plan.PlanNodeId; import com.facebook.presto.sql.planner.plan.PlanVisitor; import com.facebook.presto.sql.planner.plan.ProjectNode; import com.facebook.presto.sql.planner.plan.RemoteSourceNode; import com.facebook.presto.sql.planner.plan.RowNumberNode; import com.facebook.presto.sql.planner.plan.SampleNode; import com.facebook.presto.sql.planner.plan.SemiJoinNode; import com.facebook.presto.sql.planner.plan.SortNode; import com.facebook.presto.sql.planner.plan.TableFinishNode; import com.facebook.presto.sql.planner.plan.TableScanNode; import com.facebook.presto.sql.planner.plan.TableWriterNode; import com.facebook.presto.sql.planner.plan.TopNNode; import com.facebook.presto.sql.planner.plan.TopNRowNumberNode; import com.facebook.presto.sql.planner.plan.UnionNode; import com.facebook.presto.sql.planner.plan.UnnestNode; import com.facebook.presto.sql.planner.plan.ValuesNode; import com.facebook.presto.sql.planner.plan.WindowNode; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import io.airlift.log.Logger; import javax.inject.Inject; import java.util.ArrayList; import java.util.List; import java.util.Map; import static com.google.common.collect.Iterables.getOnlyElement; import static java.util.Objects.requireNonNull; public class DistributedExecutionPlanner { private static final Logger log = Logger.get(DistributedExecutionPlanner.class); private final SplitManager splitManager; @Inject public DistributedExecutionPlanner(SplitManager splitManager) { this.splitManager = requireNonNull(splitManager, "splitManager is null"); } public StageExecutionPlan plan(SubPlan root, Session session) { Visitor visitor = new Visitor(session); try { return plan(root, visitor); } catch (Throwable t) { visitor.getSplitSources().forEach(DistributedExecutionPlanner::closeSplitSource); throw t; } } private static void closeSplitSource(SplitSource source) { try { source.close(); } catch (Throwable t) { log.warn(t, "Error closing split source"); } } private StageExecutionPlan plan(SubPlan root, Visitor visitor) { PlanFragment currentFragment = root.getFragment(); // get splits for this fragment, this is lazy so split assignments aren't actually calculated here Map<PlanNodeId, SplitSource> splitSources = currentFragment.getRoot().accept(visitor, null); // create child stages ImmutableList.Builder<StageExecutionPlan> dependencies = ImmutableList.builder(); for (SubPlan childPlan : root.getChildren()) { dependencies.add(plan(childPlan, visitor)); } return new StageExecutionPlan( currentFragment, splitSources, dependencies.build()); } private final class Visitor extends PlanVisitor<Void, Map<PlanNodeId, SplitSource>> { private final Session session; private final List<SplitSource> splitSources = new ArrayList<>(); private Visitor(Session session) { this.session = session; } public List<SplitSource> getSplitSources() { return splitSources; } @Override public Map<PlanNodeId, SplitSource> visitExplainAnalyze(ExplainAnalyzeNode node, Void context) { return node.getSource().accept(this, context); } @Override public Map<PlanNodeId, SplitSource> visitTableScan(TableScanNode node, Void context) { // get dataSource for table SplitSource splitSource = splitManager.getSplits(session, node.getLayout().get()); splitSources.add(splitSource); return ImmutableMap.of(node.getId(), splitSource); } @Override public Map<PlanNodeId, SplitSource> visitJoin(JoinNode node, Void context) { Map<PlanNodeId, SplitSource> leftSplits = node.getLeft().accept(this, context); Map<PlanNodeId, SplitSource> rightSplits = node.getRight().accept(this, context); return ImmutableMap.<PlanNodeId, SplitSource>builder() .putAll(leftSplits) .putAll(rightSplits) .build(); } @Override public Map<PlanNodeId, SplitSource> visitSemiJoin(SemiJoinNode node, Void context) { Map<PlanNodeId, SplitSource> sourceSplits = node.getSource().accept(this, context); Map<PlanNodeId, SplitSource> filteringSourceSplits = node.getFilteringSource().accept(this, context); return ImmutableMap.<PlanNodeId, SplitSource>builder() .putAll(sourceSplits) .putAll(filteringSourceSplits) .build(); } @Override public Map<PlanNodeId, SplitSource> visitIndexJoin(IndexJoinNode node, Void context) { return node.getProbeSource().accept(this, context); } @Override public Map<PlanNodeId, SplitSource> visitRemoteSource(RemoteSourceNode node, Void context) { // remote source node does not have splits return ImmutableMap.of(); } @Override public Map<PlanNodeId, SplitSource> visitValues(ValuesNode node, Void context) { // values node does not have splits return ImmutableMap.of(); } @Override public Map<PlanNodeId, SplitSource> visitFilter(FilterNode node, Void context) { return node.getSource().accept(this, context); } @Override public Map<PlanNodeId, SplitSource> visitSample(SampleNode node, Void context) { switch (node.getSampleType()) { case BERNOULLI: return node.getSource().accept(this, context); case SYSTEM: Map<PlanNodeId, SplitSource> nodeSplits = node.getSource().accept(this, context); // TODO: when this happens we should switch to either BERNOULLI or page sampling if (nodeSplits.size() == 1) { PlanNodeId planNodeId = getOnlyElement(nodeSplits.keySet()); SplitSource sampledSplitSource = new SampledSplitSource(nodeSplits.get(planNodeId), node.getSampleRatio()); return ImmutableMap.of(planNodeId, sampledSplitSource); } // table sampling on a sub query without splits is meaningless return nodeSplits; default: throw new UnsupportedOperationException("Sampling is not supported for type " + node.getSampleType()); } } @Override public Map<PlanNodeId, SplitSource> visitAggregation(AggregationNode node, Void context) { return node.getSource().accept(this, context); } @Override public Map<PlanNodeId, SplitSource> visitGroupId(GroupIdNode node, Void context) { return node.getSource().accept(this, context); } @Override public Map<PlanNodeId, SplitSource> visitMarkDistinct(MarkDistinctNode node, Void context) { return node.getSource().accept(this, context); } @Override public Map<PlanNodeId, SplitSource> visitWindow(WindowNode node, Void context) { return node.getSource().accept(this, context); } @Override public Map<PlanNodeId, SplitSource> visitRowNumber(RowNumberNode node, Void context) { return node.getSource().accept(this, context); } @Override public Map<PlanNodeId, SplitSource> visitTopNRowNumber(TopNRowNumberNode node, Void context) { return node.getSource().accept(this, context); } @Override public Map<PlanNodeId, SplitSource> visitProject(ProjectNode node, Void context) { return node.getSource().accept(this, context); } @Override public Map<PlanNodeId, SplitSource> visitUnnest(UnnestNode node, Void context) { return node.getSource().accept(this, context); } @Override public Map<PlanNodeId, SplitSource> visitTopN(TopNNode node, Void context) { return node.getSource().accept(this, context); } @Override public Map<PlanNodeId, SplitSource> visitOutput(OutputNode node, Void context) { return node.getSource().accept(this, context); } @Override public Map<PlanNodeId, SplitSource> visitEnforceSingleRow(EnforceSingleRowNode node, Void context) { return node.getSource().accept(this, context); } @Override public Map<PlanNodeId, SplitSource> visitAssignUniqueId(AssignUniqueId node, Void context) { return node.getSource().accept(this, context); } @Override public Map<PlanNodeId, SplitSource> visitLimit(LimitNode node, Void context) { return node.getSource().accept(this, context); } @Override public Map<PlanNodeId, SplitSource> visitDistinctLimit(DistinctLimitNode node, Void context) { return node.getSource().accept(this, context); } @Override public Map<PlanNodeId, SplitSource> visitSort(SortNode node, Void context) { return node.getSource().accept(this, context); } @Override public Map<PlanNodeId, SplitSource> visitTableWriter(TableWriterNode node, Void context) { return node.getSource().accept(this, context); } @Override public Map<PlanNodeId, SplitSource> visitTableFinish(TableFinishNode node, Void context) { return node.getSource().accept(this, context); } @Override public Map<PlanNodeId, SplitSource> visitDelete(DeleteNode node, Void context) { return node.getSource().accept(this, context); } @Override public Map<PlanNodeId, SplitSource> visitMetadataDelete(MetadataDeleteNode node, Void context) { // MetadataDelete node does not have splits return ImmutableMap.of(); } @Override public Map<PlanNodeId, SplitSource> visitUnion(UnionNode node, Void context) { return processSources(node.getSources(), context); } @Override public Map<PlanNodeId, SplitSource> visitExchange(ExchangeNode node, Void context) { return processSources(node.getSources(), context); } private Map<PlanNodeId, SplitSource> processSources(List<PlanNode> sources, Void context) { ImmutableMap.Builder<PlanNodeId, SplitSource> result = ImmutableMap.builder(); for (PlanNode child : sources) { result.putAll(child.accept(this, context)); } return result.build(); } @Override protected Map<PlanNodeId, SplitSource> visitPlan(PlanNode node, Void context) { throw new UnsupportedOperationException("not yet implemented: " + node.getClass().getName()); } } }