/* * Licensed to Crate under one or more contributor license agreements. * See the NOTICE file distributed with this work for additional * information regarding copyright ownership. Crate 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. * * However, if you have executed another commercial license agreement * with Crate these terms will supersede the license and you may use the * software solely pursuant to the terms of the relevant commercial * agreement. */ package io.crate.analyze.relations; import com.google.common.collect.ImmutableList; import io.crate.analyze.QuerySpec; import io.crate.analyze.symbol.Field; import io.crate.analyze.symbol.FieldsVisitor; import io.crate.analyze.symbol.Symbol; import io.crate.operation.operator.AndOperator; import io.crate.planner.node.dql.join.JoinType; import io.crate.sql.tree.QualifiedName; import javax.annotation.Nullable; import java.util.*; import java.util.function.Function; public final class JoinPairs { /** * Find and return a {@link JoinPair} for the given relation names, also check for reversed names. * If the {@link JoinType} is INNER and a pair is found for the reversed names, merge the join conditions. * Found join pairs will be removed from the list if consume is true. */ public static JoinPair ofRelationsWithMergedConditions(QualifiedName left, QualifiedName right, List<JoinPair> joinPairs, boolean consume) { JoinPair joinPair = ofRelations(left, right, joinPairs, true); if (joinPair == null) { // default to cross join (or inner, doesn't matter) return new JoinPair(left, right, JoinType.CROSS); } assert !(joinPairs instanceof ImmutableList) : "joinPairs list must be mutable if it contains items"; // if it's an INNER, lets check for reverse relations and merge conditions if (joinPair.joinType() == JoinType.INNER) { JoinPair joinPairReverse = ofRelations(right, left, joinPairs, false); if (joinPairReverse != null) { joinPair.condition(AndOperator.join(Arrays.asList(joinPair.condition(), joinPairReverse.condition()))); if (consume) { joinPairs.remove(joinPairReverse); } } } if (consume) { joinPairs.remove(joinPair); } return joinPair; } /** * Returns the {@link JoinPair} of a pair of relation names based on the given list of join pairs. * If <p>checkReversePairs</p> is true, also check for any {@link JoinPair} with switched names. */ @Nullable public static JoinPair ofRelations(QualifiedName left, QualifiedName right, List<JoinPair> joinPairs, boolean checkReversePair) { for (JoinPair joinPair : joinPairs) { if (joinPair.equalsNames(left, right)) { return joinPair; } } // check if relations were switched due to some optimization if (checkReversePair) { for (JoinPair joinPair : joinPairs) { if (joinPair.equalsNames(right, left)) { JoinPair reverseJoinPair = new JoinPair( joinPair.right(), joinPair.left(), joinPair.joinType().invert(), joinPair.condition()); joinPairs.add(reverseJoinPair); return reverseJoinPair; } } } return null; } /** * Returns all relation names which are part of an outer join. */ public static Set<QualifiedName> outerJoinRelations(List<JoinPair> joinPairs) { Set<QualifiedName> outerJoinRelations = new HashSet<>(); for (JoinPair joinPair : joinPairs) { if (joinPair.joinType().isOuter()) { outerJoinRelations.add(joinPair.left()); outerJoinRelations.add(joinPair.right()); } } return outerJoinRelations; } /** * Rewrite names of matching join pair relations and inside the condition function */ public static void rewriteNames(QualifiedName left, QualifiedName right, QualifiedName newName, Function<? super Symbol, ? extends Symbol> replaceFunction, List<JoinPair> joinPairs) { for (JoinPair joinPair : joinPairs) { joinPair.replaceNames(left, right, newName); joinPair.replaceCondition(replaceFunction); } } /** * Returns true if relation name is part of an outer join and on the outer side. */ static boolean isOuterRelation(QualifiedName name, List<JoinPair> joinPairs) { for (JoinPair joinPair : joinPairs) { if (joinPair.isOuterRelation(name)) { return true; } } return false; } /** * Removes order by on the outer relation of an outer join because it must be applied after the join anyway */ public static void removeOrderByOnOuterRelation(QualifiedName left, QualifiedName right, QuerySpec leftQuerySpec, QuerySpec rightQuerySpec, JoinPair joinPair) { if (joinPair.isOuterRelation(left)) { leftQuerySpec.orderBy(null); } if (joinPair.isOuterRelation(right)) { rightQuerySpec.orderBy(null); } } /** * Extracts the fields from the given join conditions and returns a list. */ public static List<Field> extractFieldsFromJoinConditions(Iterable<JoinPair> joinPairs) { List<Field> outputs = new ArrayList<>(); for (JoinPair pair : joinPairs) { Symbol condition = pair.condition(); if (condition != null) { FieldsVisitor.visitFields(condition, outputs::add); } } return outputs; } }