/** * 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.qp.operator; import com.foundationdb.ais.model.GroupIndex; import com.foundationdb.ais.model.Index; import com.foundationdb.ais.model.TableIndex; import com.foundationdb.ais.model.Table; import java.util.ArrayList; import java.util.Collection; import java.util.List; // IndexScanSelector reflects USAGE of the index in a query. // leftJoinAfter(Index, Table): query has inner joins down to the table then left (further down) // rightJoinUntil(Index, Table): query has right joins down to the table then inner (further down) public abstract class IndexScanSelector { public abstract boolean matchesAll(); public abstract boolean matches(long map); /** * For testing. * @return the bitmask that will be compared against */ abstract long getBitMask(); public static IndexScanSelector leftJoinAfter(Index index, final Table leafmostRequired) { final int leafmostRequiredDepth = leafmostRequired.getDepth(); return create(index, new SelectorCreationPolicy() { @Override public boolean include(Table table) { if (table.equals(leafmostRequired)) sawTable = true; return table.getDepth() <= leafmostRequiredDepth; } @Override public String description(GroupIndex index) { return index.leafMostTable().equals(leafmostRequired) ? "" : " INNER JOIN thru " + leafmostRequired.getName().getTableName() + ", then LEFT"; } @Override public void validate(GroupIndex index) { if (!sawTable) complain(index, leafmostRequired); } @Override public void validate(TableIndex index) { if (!index.getTable().equals(leafmostRequired)) complain(index, leafmostRequired); } private boolean sawTable = false; }); } public static IndexScanSelector rightJoinUntil(Index index, final Table rootmostRequired) { final int leafmostRequiredDepth = rootmostRequired.getDepth(); return create(index, new SelectorCreationPolicy() { @Override public boolean include(Table table) { if (table.equals(rootmostRequired)) sawTable = true; return table.getDepth() >= leafmostRequiredDepth; } @Override public String description(GroupIndex index) { return index.rootMostTable().equals(rootmostRequired) ? "" : " RIGHT JOIN thru " + rootmostRequired.getName().getTableName() + ", then INNER"; } @Override public void validate(GroupIndex index) { if (!sawTable) complain(index, rootmostRequired); } @Override public void validate(TableIndex index) { if (!index.getTable().equals(rootmostRequired)) complain(index, rootmostRequired); } private boolean sawTable = false; }); } private static void complain(Index index, Table rootmostRequired) { throw new IllegalArgumentException(rootmostRequired + " not in " + index); } public static IndexScanSelector inner(Index index) { return create(index, new SelectorCreationPolicy() { @Override public boolean include(Table table) { return true; } @Override public String description(GroupIndex index) { return ""; } @Override public void validate(GroupIndex index) { } @Override public void validate(TableIndex index) { } }); } private static IndexScanSelector create(Index index, SelectorCreationPolicy policy) { if (index.isTableIndex()) { policy.validate((TableIndex)index); return ALLOW_ALL; } return create((GroupIndex)index, policy); } private static IndexScanSelector create(GroupIndex index, SelectorCreationPolicy policy) { Table giLeaf = index.leafMostTable(); List<Table> requiredTables = new ArrayList<>(giLeaf.getDepth()); for(Table table = giLeaf, end = index.rootMostTable().getParentTable(); table != null && !table.equals(end); table = table.getParentTable() ) { if (policy.include(table)) requiredTables.add(table); } policy.validate(index); return new SelectiveGiSelector(index, requiredTables, policy.description(index)); } /** * A policy which tells which tables are required and which aren't. */ private interface SelectorCreationPolicy { boolean include(Table table); String description(GroupIndex index); /** * Invoked <em>after all calls to {@linkplain #include}</em> to perform any final validations. Specifically, * lets policies make sure that they saw tables they expected to see. * @param index the index that triggered this policy */ void validate(GroupIndex index); /** * Invoked if the index that triggered this policy was a table index * @param index the index that triggered this policy */ void validate(TableIndex index); } private IndexScanSelector() {} private static final IndexScanSelector ALLOW_ALL = new AllSelector(); public abstract String describe(); private static class SelectiveGiSelector extends IndexScanSelector { @Override public boolean matchesAll() { return false; } @Override public boolean matches(long map) { return (map & requiredMap) == requiredMap; } @Override public String describe() { return description; } @Override long getBitMask() { return requiredMap; } private SelectiveGiSelector(GroupIndex index, Collection<? extends Table> tables, String description) { long tmpMap = 0; for (Table table : tables) { tmpMap |= (1 << table.getDepth()); } requiredMap = tmpMap; this.description = description; } private final long requiredMap; private final String description; } private static class AllSelector extends IndexScanSelector { @Override public boolean matchesAll() { return true; } @Override public boolean matches(long map) { return true; } @Override public String describe() { return ""; } @Override long getBitMask() { throw new UnsupportedOperationException(); } } }