/** * Copyright (C) 2009-2013 FoundationDB, LLC * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.foundationdb.sql.optimizer.plan; import com.foundationdb.server.error.AkibanInternalException; /** A join between two tables / subjoins. */ public class JoinNode extends BaseJoinable implements PlanWithInput { public static enum JoinType { INNER, LEFT, RIGHT, FULL_OUTER, // These are beyond what flatten supports, used to represent EXISTS or IN (sometimes). /** * Select all rows in the left/outer source for which at least one row in the right/inner source matches. */ SEMI, /** * Select all rows in the left/outer source for which no row in the right/inner source matches. */ ANTI, // These are intermediate to represent when a semi-join can be // turned into a regular join. SEMI_INNER_ALREADY_DISTINCT, SEMI_INNER_IF_DISTINCT, INNER_NEED_DISTINCT; public final boolean isInner() { return ((this == INNER) || (this == INNER_NEED_DISTINCT)); } public final boolean isOuter() { return ((this == LEFT) || (this == RIGHT) || (this == FULL_OUTER)); } public final boolean isSemi() { return ((this == SEMI) || (this == SEMI_INNER_ALREADY_DISTINCT) || (this == SEMI_INNER_IF_DISTINCT)); } /** * From dphyper.pdf * Definition 5 (linear). Let ◦ be a binary operator on relations. * If for all relations S and T the following two conditions hold, * then ◦ is called left linear: * 1. ∅ ◦ T = ∅ and * 2. (S1 ∪S2)◦T =(S1 ◦T)∪(S2 ◦T) for all relations S1 and S2. * * Similarly, ◦ is called right linear if * 1. S ◦ ∅ = ∅ and * 2. S ◦ (T1 ∪ T2) = (S ◦ T1) ∪ (S ◦ T2) for all relations T1 and T2. * * Practically, what this means is that, if this join type is left linear, * conditions on the left hand side are equivalent to being on the outside, * and conditions on the right hand side are equivalent to being on the join itself. * * Corrollary: * If you have only joins that are both left & right linear, * the conditions can be placed on any join. * * @return true if this join type is left linear, false otherwise. */ public final boolean isLeftLinear() { return this != RIGHT && this != FULL_OUTER; } /** * @see #isLeftLinear * * Practically, what this means is that, if this join type is right linear, * conditions on the right hand side are equivalent to being on the outside, * and conditions on the left hand side are equivalent to being on the join itself. * @return true if this join type is right linear, false otherwise */ public final boolean isRightLinear() { return this == RIGHT || this == INNER; } /** * @see #isLeftLinear * @see #isRightLinear * @return isLeftLinear() && isRightLinear() */ public final boolean isFullyLinear() { return isRightLinear() && isLeftLinear(); } } public static enum Implementation { GROUP, NESTED_LOOPS, BLOOM_FILTER, HASH_TABLE, MERGE // TODO: Not implemented. Probably needs thought. } private Joinable left, right; private JoinType joinType; private Implementation implementation; private ConditionList joinConditions; private TableGroupJoin groupJoin; private TableFKJoin fkJoin; public JoinNode(Joinable left, Joinable right, JoinType joinType) { this.left = left; left.setOutput(this); this.right = right; right.setOutput(this); this.joinType = joinType; } public Joinable getLeft() { return left; } public void setLeft(Joinable left) { this.left = left; left.setOutput(this); } public Joinable getRight() { return right; } public void setRight(Joinable right) { this.right = right; right.setOutput(this); } public JoinType getJoinType() { return joinType; } public void setJoinType(JoinType joinType) { this.joinType = joinType; } @Override public boolean isJoin() { return true; } @Override public boolean isInnerJoin() { return (joinType == JoinType.INNER); } public ConditionList getJoinConditions() { return joinConditions; } public void setJoinConditions(ConditionList joinConditions) { this.joinConditions = joinConditions; } public boolean hasJoinConditions() { return ((joinConditions != null) && !joinConditions.isEmpty()); } public TableGroupJoin getGroupJoin() { return groupJoin; } public void setGroupJoin(TableGroupJoin groupJoin) { this.groupJoin = groupJoin; if (groupJoin != null) this.implementation = Implementation.GROUP; } public TableFKJoin getFKJoin() { return fkJoin; } public void setFKJoin (TableFKJoin fkJoin) { this.fkJoin = fkJoin; } /** Get the condition that implements groupJoin. */ public ConditionExpression getGroupJoinCondition() { for (ConditionExpression condition : joinConditions) { if (condition.getImplementation() == ConditionExpression.Implementation.GROUP_JOIN) return condition; } return null; } public Implementation getImplementation() { return implementation; } public void setImplementation(Implementation implementation) { this.implementation = implementation; } /** Reverse operands and outer join direction if necessary. */ public void reverse() { switch (joinType) { case INNER: case FULL_OUTER: break; case LEFT: joinType = JoinType.RIGHT; break; case RIGHT: joinType = JoinType.LEFT; break; default: throw new AkibanInternalException("Cannot reverse " + joinType + " join"); } Joinable temp = left; left = right; right = temp; } @Override public void replaceInput(PlanNode oldInput, PlanNode newInput) { if (left == oldInput) { left = (Joinable)newInput; left.setOutput(this); } if (right == oldInput) { right = (Joinable)newInput; right.setOutput(this); } } @Override public boolean accept(PlanVisitor v) { if (v.visitEnter(this)) { if (acceptPlans(v) && (joinConditions != null)) { acceptConditions(v); } } return v.visitLeave(this); } protected boolean acceptPlans(PlanVisitor v) { return (left.accept(v) && right.accept(v)); } protected void acceptConditions(PlanVisitor v) { if (v instanceof ExpressionRewriteVisitor) { joinConditions.accept((ExpressionRewriteVisitor)v); } else if (v instanceof ExpressionVisitor) { joinConditions.accept((ExpressionVisitor)v); } } @Override public String summaryString(SummaryConfiguration configuration) { StringBuilder str = new StringBuilder(super.summaryString(configuration)); str.append("("); summarizeJoins(str); str.append(")"); return str.toString(); } protected void summarizeJoins(StringBuilder str) { str.append(joinType); if (implementation != null) { str.append("/"); str.append(implementation); } if (joinConditions != null) str.append(joinConditions.toString()); if (groupJoin != null) { str.append(" - "); str.append(groupJoin); } if (fkJoin != null) { str.append(" - "); str.append(fkJoin); } } @Override protected void deepCopy(DuplicateMap map) { super.deepCopy(map); left = (Joinable)left.duplicate(map); right = (Joinable)right.duplicate(map); if (joinConditions != null) joinConditions = joinConditions.duplicate(map); } }