/** * 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.tajo.plan.joinorder; import org.apache.tajo.algebra.JoinType; import org.apache.tajo.catalog.Column; import org.apache.tajo.plan.expr.EvalNode; import org.apache.tajo.plan.expr.EvalTreeUtil; import org.apache.tajo.plan.expr.EvalType; import org.apache.tajo.plan.util.PlannerUtil; import java.util.HashSet; import java.util.List; import java.util.Set; public class JoinOrderingUtil { /** * Find join conditions which can be evaluated at the given join edge. The result depends on the isOnPredicate flag * which represents the given set of predicates is from the on clause of not. * * @param candidates set of predicates * @param edge join edge * @param isOnPredicates flag to represent the candidates from the on clause of not * @return predicates which can be evaluated at the given join edge */ public static Set<EvalNode> findJoinConditionForJoinVertex(Set<EvalNode> candidates, JoinEdge edge, boolean isOnPredicates) { Set<EvalNode> conditionsForThisJoin = new HashSet<>(); for (EvalNode predicate : candidates) { if (EvalTreeUtil.isJoinQual(predicate, false) && checkIfEvaluatedAtEdge(predicate, edge, isOnPredicates)) { conditionsForThisJoin.add(predicate); } } return conditionsForThisJoin; } /** * Check whether the given predicate can be evaluated at the given join edge or not. The result depends on * the isOnPredicate flag which represents the given predicate is from the on clause of not. * * @param evalNode predicate * @param edge join edge * @param isOnPredicate flag to represent the candidates from the on clause of not * @return true if the predicate can be evaluated at the given join edge */ public static boolean checkIfEvaluatedAtEdge(EvalNode evalNode, JoinEdge edge, boolean isOnPredicate) { Set<Column> columnRefs = EvalTreeUtil.findUniqueColumns(evalNode); if (EvalTreeUtil.findDistinctAggFunction(evalNode).size() > 0) { return false; } if (EvalTreeUtil.findEvalsByType(evalNode, EvalType.WINDOW_FUNCTION).size() > 0) { return false; } if (columnRefs.size() > 0 && !edge.getSchema().containsAll(columnRefs)) { return false; } // Currently, join filters cannot be evaluated at joins if (PlannerUtil.isOuterJoinType(edge.getJoinType()) && !isOnPredicate) { return false; } return true; } /** * Check the associativity between the given two join edges. * For the associativity rules, please refer to the below isAssociativeJoinType() function. * * @param context join graph context * @param leftEdge left join edge * @param rightEdge right join edge * @return true if two given join edges are associative. */ public static boolean isAssociativeJoin(JoinGraphContext context, JoinEdge leftEdge, JoinEdge rightEdge) { if (isAssociativeJoinType(leftEdge.getJoinType(), rightEdge.getJoinType())) { // NOTE: There will be more predicates which are able to be evaluated at input join edges. // In this case, the input edges are not considered as associative joins to evaluate those predicates at proper // join edges, even though they have the associative relationship. // Create a temporal left-deep join node to find the potentially evaluatable quals. JoinedRelationsVertex tempLeftChild = new JoinedRelationsVertex(leftEdge); JoinEdge tempEdge = context.getCachedOrNewJoinEdge(rightEdge.getJoinSpec(), tempLeftChild, rightEdge.getRightVertex()); if ((rightEdge.getJoinType() != JoinType.INNER && rightEdge.getJoinType() != JoinType.CROSS && rightEdge.getJoinType() != JoinType.LEFT_SEMI && rightEdge.getJoinType() != JoinType.LEFT_ANTI) || (leftEdge.getJoinType() != JoinType.INNER && leftEdge.getJoinType() != JoinType.CROSS && leftEdge.getJoinType() != JoinType.LEFT_SEMI && leftEdge.getJoinType() != JoinType.LEFT_ANTI)) { if (!findJoinConditionForJoinVertex(context.getCandidateJoinConditions(), tempEdge, true).isEmpty()) { return false; } if (!findJoinConditionForJoinVertex(context.getCandidateJoinFilters(), tempEdge, true).isEmpty()) { return false; } } return true; } return false; } /** * Check two join types are associative according to the following rule. * * <h3>Associativity rules</h3> * * ============================================================== * Left-Hand Bracketed | Right-Hand Bracketed | Equivalence * ============================================================== * (A inner B) inner C | A inner (B inner C) | Equivalent * (A left B) inner C | A left (B inner C) | Not equivalent * (A right B) inner C | A right (B inner C) | Equivalent * (A full B) inner C | A full (B inner C) | Not equivalent * (A inner B) left C | A inner (B left C) | Equivalent * (A left B) left C | A left (B left C) | Equivalent * (A right B) left C | A right (B left C) | Equivalent * (A full B) left C | A full (B left C) | Equivalent * (A inner B) right C | A inner (B right C) | Not equivalent * (A left B) right C | A left (B right C) | Not equivalent * (A right B) right C | A right (B right C) | Equivalent * (A full B) right C | A full (B right C) | Not equivalent * (A inner B) full C | A inner (B full C) | Not equivalent * (A left B) full C | A left (B full C) | Not equivalent * (A right B) full C | A right (B full C) | Equivalent * (A full B) full C | A full (B full C) | Equivalent * ============================================================== * * Cross, Semi and Anti joins follow the rule of the Inner join. * * @param leftType * @param rightType * @return true if two join types are associative. */ public static boolean isAssociativeJoinType(JoinType leftType, JoinType rightType) { if (leftType == rightType) { return true; } boolean isLeftInner = leftType == JoinType.INNER || leftType == JoinType.CROSS || leftType == JoinType.LEFT_SEMI || leftType == JoinType.LEFT_ANTI; boolean isRightInner = rightType == JoinType.INNER || rightType == JoinType.CROSS || rightType == JoinType.LEFT_SEMI || rightType == JoinType.LEFT_ANTI; if (isLeftInner && isRightInner) { return true; } if (leftType == JoinType.RIGHT_OUTER) { return true; } if (leftType == JoinType.LEFT_OUTER) { // When the left type is the left outer join, input join types are associative // if the right type is also the left outer join. // This case is already checked above. return false; } if (isLeftInner) { if (rightType == JoinType.LEFT_OUTER) { return true; } else { return false; } } if (leftType == JoinType.FULL_OUTER) { if (rightType == JoinType.LEFT_OUTER) { return true; } else { return false; } } return false; } /** * Find all interchangeable vertexes from the given vertex. * Join edges between relations are found at runtime. * * <H3>Vertex interchange rules</H3> * - A vertex is interchangeable with the start vertex if it is reachable. * - A vertex is reachable from the start vertex if it is able to replace another vertex which is connected to the start vertex. * - A vertex is able to replace another vertex if they are connected without violating join commutativity or join associativity. * * @param context join graph context * @param from start vertex * @return */ public static Set<JoinVertex> getAllInterchangeableVertexes(JoinGraphContext context, JoinVertex from) { Set<JoinVertex> founds = new HashSet<>(); getAllInterchangeableVertexes(founds, context, from); return founds; } public static void getAllInterchangeableVertexes(Set<JoinVertex> founds, JoinGraphContext context, JoinVertex vertex) { founds.add(vertex); Set<JoinVertex> foundAtThis = new HashSet<>(); List<JoinEdge> candidateEdges = context.getJoinGraph().getOutgoingEdges(vertex); if (candidateEdges != null) { for (JoinEdge candidateEdge : candidateEdges) { // Evaluatable quals must be added to check the associativity of join edges. candidateEdge = updateQualIfNecessary(context, candidateEdge); if (!founds.contains(candidateEdge.getRightVertex())) { List<JoinEdge> rightEdgesOfCandidate = context.getJoinGraph().getOutgoingEdges(candidateEdge.getRightVertex()); boolean reacheable = false; if (rightEdgesOfCandidate != null) { reacheable = true; for (JoinEdge rightEdgeOfCandidate : rightEdgesOfCandidate) { // Evaluatable quals must be added to check the associativity of join edges. rightEdgeOfCandidate = updateQualIfNecessary(context, rightEdgeOfCandidate); if (!isAssociativeJoin(context, candidateEdge, rightEdgeOfCandidate)) { reacheable = false; break; } } } if (reacheable) { foundAtThis.add(candidateEdge.getRightVertex()); } } } for (JoinVertex v : foundAtThis) { getAllInterchangeableVertexes(founds, context, v); } } } /** * Check the given two join edges are equal or symmetric. * * @param edge1 * @param edge2 * @return True if two join edges are equal or symmetric. */ public static boolean isEqualsOrSymmetric(JoinEdge edge1, JoinEdge edge2) { if (edge1.equals(edge2) || isSymmetric(edge1, edge2)) { return true; } return false; } /** * Given two join edges e1 and e2, they are <b>symmetric</b> whey they satisfy the follwing conditions. * * <ul> * <li>e1 and e2 have the same commutative join type.</li> * <li>e1 and e2 have the same join condition.</li> * <li>The left and right vertexes of e1 are the right and left vertexes of e2, respectively.</li> * </ul> * * @param edge1 * @param edge2 * @return True if two join edges are symmetric. */ public static boolean isSymmetric(JoinEdge edge1, JoinEdge edge2) { if (edge1.getLeftVertex().equals(edge2.getRightVertex()) && edge1.getRightVertex().equals(edge2.getLeftVertex()) && edge1.getJoinSpec().equals(edge2.getJoinSpec()) && PlannerUtil.isCommutativeJoinType(edge1.getJoinType())) { return true; } return false; } /** * If there are predicates which can be evaluated at the given join edge, push those predicates to the join edge. * * @param context * @param edge * @return */ public static JoinEdge updateQualIfNecessary(JoinGraphContext context, JoinEdge edge) { Set<EvalNode> additionalPredicates = findJoinConditionForJoinVertex( context.getCandidateJoinConditions(), edge, true); additionalPredicates.addAll(findJoinConditionForJoinVertex( context.getCandidateJoinFilters(), edge, false)); edge.addJoinPredicates(additionalPredicates); return edge; } /** * Find all edges that are associative with the given edge. * * @param context * @param edge * @return */ public static Set<JoinEdge> getAllAssociativeEdges(JoinGraphContext context, JoinEdge edge) { Set<JoinEdge> associativeEdges = new HashSet<>(); JoinVertex start = edge.getRightVertex(); List<JoinEdge> candidateEdges = context.getJoinGraph().getOutgoingEdges(start); if (candidateEdges != null) { for (JoinEdge candidateEdge : candidateEdges) { candidateEdge = updateQualIfNecessary(context, candidateEdge); if (!isEqualsOrSymmetric(edge, candidateEdge) && isAssociativeJoin(context, edge, candidateEdge)) { associativeEdges.add(candidateEdge); } } } return associativeEdges; } }