/** * 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.ais.model.Column; import com.foundationdb.ais.model.HKeyColumn; import com.foundationdb.ais.model.HKeySegment; import com.foundationdb.ais.model.Table; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.NoSuchElementException; import java.util.Set; public abstract class MultiIndexEnumerator<C,N extends IndexIntersectionNode<C,N>,L extends N> implements Iterable<N> { protected abstract Collection<? extends C> getLeafConditions(L node); protected abstract N intersect(N first, N second, int comparisonCount); protected abstract List<Column> getComparisonColumns(N first, N second); // becomes null when we start enumerating private List<L> leaves = new ArrayList<>(); private Set<C> conditions = new HashSet<>(); public void addLeaf(L leaf) { leaves.add(leaf); } public Iterator<L> leavesIterator() { return leaves.iterator(); } private class ComboIterator implements Iterator<N> { private boolean done = false; private List<N> current = new ArrayList<>(); private Iterator<N> currentIter; // These are only used in advancePhase, but we cache them to save on allocations private List<N> previous = new ArrayList<>(); private ConditionsCounter<C> outerCounter = new ConditionsCounter<>(conditions.size()); private ConditionsCounter<C> innerCounter = new ConditionsCounter<>(conditions.size()); private ConditionsCount<C> bothCount = new OverlayedConditionsCount<>(outerCounter, innerCounter); private ComboIterator() { current.addAll(leaves); advancePhase(); } @Override public boolean hasNext() { if (done) return false; if (currentIter.hasNext()) return true; advancePhase(); return !done; } @Override public N next() { if (done) throw new NoSuchElementException(); if (!currentIter.hasNext()) advancePhase(); return currentIter.next(); } @Override public void remove() { currentIter.remove(); } private void advancePhase() { assert (currentIter == null) || (!currentIter.hasNext()) : "internal iterator not exhausted"; if (current.isEmpty()) { done = true; return; } previous.clear(); previous.addAll(current); current.clear(); int conditionsCount = conditions.size(); for (N outer : previous) { outer.incrementConditionsCounter(outerCounter); int counted = outerCounter.conditionsCounted(); // only try the leaves if the outer counted some conditions, but not all of them. if (counted > 0 && counted < conditionsCount) { // at this point, "outer" satisfies some conditions, and more conditions are left for (L inner : leaves) { if (inner == outer) continue; // fast path, we know there's full overlap inner.incrementConditionsCounter(innerCounter); if (inner.isUseful(bothCount) && outer.isUseful(bothCount)) { emit(outer, inner, current); } innerCounter.clear(); } } outerCounter.clear(); } if (current.isEmpty()) { done = true; currentIter = null; } else { currentIter = current.iterator(); } } } @Override public Iterator<N> iterator() { filterLeaves(); return new ComboIterator(); } private void filterLeaves() { for (Iterator<L> iter = leaves.iterator(); iter.hasNext(); ) { L leaf = iter.next(); Collection<? extends C> nodeConditions = getLeafConditions(leaf); if ( (nodeConditions != null) && (!nodeConditions.isEmpty()) ) { conditions.addAll(nodeConditions); } else { iter.remove(); } } } private void emit(N first, N second, Collection<N> output) { Table firstTable = first.getLeafMostAisTable(); Table secondTable = second.getLeafMostAisTable(); List<Column> comparisonCols = getComparisonColumns(first, second); if (comparisonCols.isEmpty()) return; int comparisonsLen = comparisonCols.size(); // find the Table associated with the common N. This works for multi- as well as single-branch Table commonAncestor = first.findCommonAncestor(second); assert commonAncestor == second.findCommonAncestor(first) : first + "'s ancestor not reflexive with " + second; boolean isMultiBranch = true; if (firstTable != secondTable) { if (commonAncestor == firstTable) { isMultiBranch = false; if (includesHKey(firstTable, comparisonCols)) output.add(intersect(second, first, comparisonsLen)); } else { // in single-branch cases, we only want to output the leafmost's index isMultiBranch = (commonAncestor != secondTable); } } if (isMultiBranch && includesHKey(commonAncestor, comparisonCols)) { output.add(intersect(first, second, comparisonsLen)); } } private boolean includesHKey(Table table, List<Column> columns) { // TODO this seems horridly inefficient, but the data set is going to be quite small for (HKeySegment segment : table.hKey().segments()) { for (HKeyColumn hKeyCol : segment.columns()) { boolean found = false; for (Column equiv : hKeyCol.equivalentColumns()) { if (columns.contains(equiv)) { found = true; break; } } if (!found) return false; } } return true; } }