/** * 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.sql.optimizer.plan.JoinNode.JoinType; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; /** Joins within a single {@link TableGroup} represented as a tree * whose structure mirrors that of the group. * This is an intermediate form between the original tree of joins based on the * original SQL syntax and the <code>Scan</code> and <code>Lookup</code> form * once an access path has been chosen. */ public class TableGroupJoinTree extends BaseJoinable implements Iterable<TableGroupJoinTree.TableGroupJoinNode>, PlanWithInput { public static class TableGroupJoinNode implements Iterable<TableGroupJoinNode> { TableSource table; TableGroupJoinNode parent, nextSibling, firstChild; JoinType parentJoinType; ConditionList joinConditions; long state; public TableGroupJoinNode(TableSource table) { this.table = table; } public TableSource getTable() { return table; } public TableGroupJoinNode getParent() { return parent; } public void setParent(TableGroupJoinNode parent) { this.parent = parent; } public TableGroupJoinNode getNextSibling() { return nextSibling; } public void setNextSibling(TableGroupJoinNode nextSibling) { this.nextSibling = nextSibling; } public TableGroupJoinNode getFirstChild() { return firstChild; } public void setFirstChild(TableGroupJoinNode firstChild) { this.firstChild = firstChild; } public JoinType getParentJoinType() { return parentJoinType; } public void setParentJoinType(JoinType parentJoinType) { this.parentJoinType = parentJoinType; } public ConditionList getJoinConditions() { return joinConditions; } public void setJoinConditions(ConditionList joinConditions) { this.joinConditions = joinConditions; } /** Integer state managed by some rule. */ public long getState() { return state; } public void setState(long state) { this.state = state; } /** Find the given table in this (sub-)tree. */ public TableGroupJoinNode findTable(TableSource table) { for (TableGroupJoinNode node : this) { if (node.getTable() == table) { return node; } } return null; } @Override public Iterator<TableGroupJoinNode> iterator() { return new TableGroupJoinIterator(this); } @Override public String toString() { return getClass().getSimpleName() + "@" + Integer.toString(hashCode(), 16) + "(" + table + ", " + state + ")"; } } static class TableGroupJoinIterator implements Iterator<TableGroupJoinNode> { TableGroupJoinNode root, next; TableGroupJoinIterator(TableGroupJoinNode root) { this.root = this.next = root; } @Override public boolean hasNext() { return (next != null); } @Override public TableGroupJoinNode next() { if (next == null) throw new NoSuchElementException(); TableGroupJoinNode node = next; advance(); return node; } protected void advance() { TableGroupJoinNode node = next.getFirstChild(); if (node != null) { next = node; return; } while (true) { if (next == root) { next = null; return; } node = next.getNextSibling(); if (node != null) { next = node; return; } next = next.getParent(); } } @Override public void remove() { throw new UnsupportedOperationException(); } } public interface LeafFinderPredicate<V> { boolean includeAndContinue(TableGroupJoinNode node, TableGroupJoinTree tree); V mapToValue(TableGroupJoinNode node); } private TableGroup group; private TableGroupJoinNode root; private Set<TableSource> required; private BaseScan scan; public TableGroupJoinTree(TableGroupJoinNode root) { this.group = root.getTable().getGroup(); this.root = root; } public TableGroup getGroup() { return group; } public TableGroupJoinNode getRoot() { return root; } public Set<TableSource> getRequired() { return required; } public void setRequired(Set<TableSource> required) { this.required = required; } public BaseScan getScan() { return scan; } public void setScan(BaseScan scan) { this.scan = scan; } public boolean containsTable(TableSource table) { return (root.findTable(table) != null); } public <V> Map<TableGroupJoinNode,V> findLeaves(LeafFinderPredicate<V> predicate) { Map<TableGroupJoinNode,V> results = new HashMap<>(); if (predicate.includeAndContinue(root, this)) buildLeaves(root, predicate, results); return results; } @Override public Iterator<TableGroupJoinNode> iterator() { return new TableGroupJoinIterator(root); } public boolean accept(PlanVisitor v) { if (v.visitEnter(this)) { if (scan != null) { // Could just do scan.accept(v), but then it'd explain // on a separate line. if (v instanceof ExpressionRewriteVisitor) { scan.visitComparands((ExpressionRewriteVisitor)v); } else if (v instanceof ExpressionVisitor) { scan.visitComparands((ExpressionVisitor)v); } } TableGroupJoinNode next = root; top: while (true) { if (v.visitEnter(next.getTable())) { TableGroupJoinNode node = next.getFirstChild(); if (node != null) { next = node; continue; } } while (true) { if (v.visitLeave(next.getTable())) { TableGroupJoinNode node = next.getNextSibling(); if (node != null) { next = node; break; } } if (next == root) break top; next = next.getParent(); } } } return v.visitLeave(this); } public String summaryString(SummaryConfiguration configuration) { StringBuilder str = new StringBuilder(super.summaryString(configuration)); str.append("("); str.append(group); str.append(", "); summarizeJoins(str); if (scan != null) { str.append(" - "); str.append(scan.summaryString(configuration)); } str.append(")"); return str.toString(); } private void summarizeJoins(StringBuilder str) { for (TableGroupJoinNode node : this) { if (node != root) { str.append(" "); str.append(node.getParentJoinType()); str.append(" "); } str.append(node.getTable().getName()); if (node.getJoinConditions() != null) { boolean first = true; for (ConditionExpression joinCondition : node.getJoinConditions()) { if (joinCondition.getImplementation() == ConditionExpression.Implementation.GROUP_JOIN) continue; if (first) { str.append(" ON "); first = false; } else str.append(" AND "); str.append(joinCondition); } } } } private <V> void buildLeaves(TableGroupJoinNode root, LeafFinderPredicate<V> predicate, Map<TableGroupJoinNode,V> out) { // parent caller is responsible for checking that this root satisfies the predicate TableGroupJoinNode child = root.getFirstChild(); boolean hitLeaf; if (child == null) { hitLeaf = true; } else { boolean anyChildMatched = false; for (; child != null; child = child.getNextSibling()) { if (predicate.includeAndContinue(child, this)) { buildLeaves(child, predicate, out); anyChildMatched = true; // this frame's root is not the leaf of the predicate-matching tree } } hitLeaf = !anyChildMatched; } if (hitLeaf) { V value = predicate.mapToValue(root); out.put(root, value); } } @Override public void replaceInput(PlanNode oldInput, PlanNode newInput) { throw new UnsupportedOperationException(); } }