/* * 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.assertions; import com.facebook.presto.Session; import com.facebook.presto.metadata.Metadata; import com.facebook.presto.sql.planner.Symbol; import com.facebook.presto.sql.planner.plan.Assignments; import com.facebook.presto.sql.planner.plan.ExchangeNode; import com.facebook.presto.sql.planner.plan.PlanNode; import com.facebook.presto.sql.planner.plan.PlanVisitor; import com.facebook.presto.sql.planner.plan.ProjectNode; import java.util.List; import static com.facebook.presto.sql.planner.assertions.MatchResult.NO_MATCH; import static com.facebook.presto.sql.planner.assertions.MatchResult.match; import static com.google.common.base.Preconditions.checkState; import static java.lang.String.format; import static java.util.Objects.requireNonNull; final class PlanMatchingVisitor extends PlanVisitor<PlanMatchPattern, MatchResult> { private final Metadata metadata; private final Session session; PlanMatchingVisitor(Session session, Metadata metadata) { this.session = requireNonNull(session, "session is null"); this.metadata = requireNonNull(metadata, "metadata is null"); } @Override public MatchResult visitExchange(ExchangeNode node, PlanMatchPattern pattern) { List<List<Symbol>> allInputs = node.getInputs(); List<Symbol> outputs = node.getOutputSymbols(); MatchResult result = super.visitExchange(node, pattern); if (!result.isMatch()) { return result; } SymbolAliases newAliases = result.getAliases(); for (List<Symbol> inputs : allInputs) { Assignments.Builder assignments = Assignments.builder(); for (int i = 0; i < inputs.size(); ++i) { assignments.put(outputs.get(i), inputs.get(i).toSymbolReference()); } newAliases = newAliases.updateAssignments(assignments.build()); } return match(newAliases); } @Override public MatchResult visitProject(ProjectNode node, PlanMatchPattern pattern) { MatchResult result = super.visitProject(node, pattern); if (!result.isMatch()) { return result; } return match(result.getAliases().replaceAssignments(node.getAssignments())); } @Override protected MatchResult visitPlan(PlanNode node, PlanMatchPattern pattern) { List<PlanMatchingState> states = pattern.shapeMatches(node); // No shape match; don't need to check the internals of any of the nodes. if (states.isEmpty()) { return NO_MATCH; } // Leaf node in the plan. if (node.getSources().isEmpty()) { return matchLeaf(node, pattern, states); } MatchResult result = NO_MATCH; for (PlanMatchingState state : states) { // Traverse down the tree, checking to see if the sources match the source patterns in state. MatchResult sourcesMatch = matchSources(node, state); if (!sourcesMatch.isMatch()) { continue; } // Try upMatching this node with the the aliases gathered from the source nodes. SymbolAliases allSourceAliases = sourcesMatch.getAliases(); MatchResult matchResult = pattern.detailMatches(node, session, metadata, allSourceAliases); if (matchResult.isMatch()) { checkState(result == NO_MATCH, format("Ambiguous match on node %s", node)); result = match(allSourceAliases.withNewAliases(matchResult.getAliases())); } } return result; } private MatchResult matchLeaf(PlanNode node, PlanMatchPattern pattern, List<PlanMatchingState> states) { MatchResult result = NO_MATCH; for (PlanMatchingState state : states) { // Don't consider un-terminated PlanMatchingStates. if (!state.isTerminated()) { continue; } /* * We have to call detailMatches for two reasons: * 1) Make sure there aren't any mismatches checking the internals of a leaf node. * 2) Collect the aliases from the source nodes so we can add them to * SymbolAliases. They'll be needed further up. */ MatchResult matchResult = pattern.detailMatches(node, session, metadata, new SymbolAliases()); if (matchResult.isMatch()) { checkState(result == NO_MATCH, format("Ambiguous match on leaf node %s", node)); result = matchResult; } } return result; } /* * This is a little counter-intuitive. Calling matchSources calls * source.accept, which (eventually) ends up calling into visitPlan * recursively. Assuming the plan and pattern currently being matched * actually match each other, eventually you hit the leaf nodes. At that * point, visitPlan starts by returning the match result for the leaf nodes * containing the symbol aliases needed by further up. * * For the non-leaf nodes, an invocation of matchSources returns a match * result for a successful match containing the union of all of the symbol * aliases added by the sources of the node currently being visited. * * Visiting that node proceeds by trying to apply the current pattern's * detailMatches() method to the node being visited. When a match is found, * visitPlan returns a match result containing the aliases for all of the * current node's sources, and the aliases for the current node. */ private MatchResult matchSources(PlanNode node, PlanMatchingState state) { List<PlanMatchPattern> sourcePatterns = state.getPatterns(); checkState(node.getSources().size() == sourcePatterns.size(), "Matchers count does not match count of sources"); int i = 0; SymbolAliases.Builder allSourceAliases = SymbolAliases.builder(); for (PlanNode source : node.getSources()) { // Match sources to patterns 1:1 MatchResult matchResult = source.accept(this, sourcePatterns.get(i++)); if (!matchResult.isMatch()) { return NO_MATCH; } // Add the per-source aliases to the per-state aliases. allSourceAliases.putAll(matchResult.getAliases()); } return match(allSourceAliases.build()); } }