/* * Copyright 2006 Amazon Technologies, Inc. or its affiliates. * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks * of Amazon Technologies, Inc. or its affiliates. All rights reserved. * * Licensed 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. */ package com.amazon.carbonado.spi; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import org.cojen.util.BeanComparator; import com.amazon.carbonado.Cursor; import com.amazon.carbonado.FetchException; import com.amazon.carbonado.Query; import com.amazon.carbonado.Repository; import com.amazon.carbonado.Storable; import com.amazon.carbonado.Storage; import com.amazon.carbonado.filter.AndFilter; import com.amazon.carbonado.filter.Filter; import com.amazon.carbonado.filter.FilterValues; import com.amazon.carbonado.filter.OrFilter; import com.amazon.carbonado.filter.PropertyFilter; import com.amazon.carbonado.filter.Visitor; import com.amazon.carbonado.info.Direction; import com.amazon.carbonado.info.OrderedProperty; import com.amazon.carbonado.info.StorableIndex; import com.amazon.carbonado.info.StorableInfo; import com.amazon.carbonado.cursor.FilteredCursor; import com.amazon.carbonado.cursor.MergeSortBuffer; import com.amazon.carbonado.cursor.SortBuffer; import com.amazon.carbonado.cursor.SortedCursor; import com.amazon.carbonado.cursor.UnionCursor; import com.amazon.carbonado.qe.BoundaryType; /** * Basis for a rule-based query engine. It takes care of index selection, * filtering, sorting, and unions. * * @author Brian S O'Neill */ public abstract class BaseQueryEngine extends BaseQueryCompiler { private static PropertyFilter[] NO_FILTERS = new PropertyFilter[0]; /** * Compares two objects which are assumed to be Comparable. If one value is * null, it is treated as being higher. This consistent with all other * property value comparisons in carbonado. */ static int compareWithNullHigh(Object a, Object b) { return a == null ? (b == null ? 0 : -1) : (b == null ? 1 : ((Comparable) a).compareTo(b)); } private final Repository mRepository; private final Storage mStorage; private final StorableIndex mPrimaryKeyIndex; private final StorableIndexSet mIndexSet; String mMergeSortTempDir; /** * @param info info for Storable * @param repo repository for entering transactions * @param storage source for queried objects * @param primaryKeyIndex optional parameter representing primary key index * @param indexSet optional parameter representing all available indexes. * Constructor makes a local copy of the set. * @throws IllegalArgumentException if primaryKeyIndex is null and indexSet * is empty */ protected BaseQueryEngine(StorableInfo info, Repository repo, Storage storage, StorableIndex primaryKeyIndex, StorableIndexSet indexSet) { super(info); if (primaryKeyIndex == null && (indexSet == null || indexSet.size() == 0)) { throw new IllegalArgumentException(); } mRepository = repo; mStorage = storage; mPrimaryKeyIndex = primaryKeyIndex; mIndexSet = (indexSet == null || indexSet.size() == 0) ? null : new StorableIndexSet(indexSet); } /** * @param tempDir directory to store temp files for merge sorting, or null * for default */ protected void setMergeSortTempDirectory(String tempDir) { mMergeSortTempDir = tempDir; } @SuppressWarnings("unchecked") protected Query compileQuery(final FilterValues values, final OrderedProperty[] orderings) throws FetchException, UnsupportedOperationException { if (values == null) { // Perform requested full scan. return fullScan(values, orderings); } final Filter originalFilter = values.getFilter(); final Filter dnfFilter = originalFilter.disjunctiveNormalForm(); // Analyze the disjunctive normal form, breaking down the query into // separate queries that can be unioned together. IndexAnalysis analysis = new IndexAnalysis(mPrimaryKeyIndex, mIndexSet, orderings); dnfFilter.accept(analysis, null); if (analysis.noBestIndex()) { // Fallback to full scan for everything if no best index found for // just one query component. return fullScan(values, orderings); } OrderedProperty[] totalOrderings = null; ensureTotalOrdering: if (analysis.getResults().size() > 1) { // Union will be performed, and so a total ordering is required. // TODO: The logic in this section needs to be totally reworked. It // does a terrible job of finding the best total ordering, often // performing full sorts when not needed. Essentially, inefficient // query plans can get generated. // If all selected indexes are unique and have the same effective ordering, then // nothing special needs to be done to ensure total ordering. OrderedProperty[] effectiveOrderings = null; totalOrderCheck: if (orderings == null || orderings.length == 0) { for (IndexSelector.IndexFitness result : analysis.getResults()) { StorableIndex index = result.getIndex(); if (!index.isUnique()) { break totalOrderCheck; } if (effectiveOrderings == null) { effectiveOrderings = result.getEffectiveOrderings(); continue; } if (!Arrays.equals(effectiveOrderings, result.getEffectiveOrderings())) { break totalOrderCheck; } } // All indexes already define a total ordering. totalOrderings = effectiveOrderings; break ensureTotalOrdering; } // Augment the ordering with elements of a unique index. // Count how often an index has been used. Map, Integer> counts = new LinkedHashMap, Integer>(); for (IndexSelector.IndexFitness result : analysis.getResults()) { StorableIndex index = result.getIndex(); counts.put(index, (counts.containsKey(index)) ? (counts.get(index) + 1) : 1); } // Find the unique index that has been selected most often. StorableIndex unique = mPrimaryKeyIndex; int uniqueCount = 0; for (Map.Entry, Integer> entry : counts.entrySet()) { if (entry.getKey().isUnique() && entry.getValue() > uniqueCount) { unique = entry.getKey(); uniqueCount = entry.getValue(); } } if (unique == null) { // Select first found unique index. for (StorableIndex index : mIndexSet) { if (index.isUnique()) { unique = index; break; } } if (unique == null) { throw new UnsupportedOperationException ("Cannot perform union; sort requires at least one unique index"); } } // To avoid full sorts, choose an index which is already being used // for its ordering. It may have a range filter or handled // orderings. StorableIndex best = null; int bestCount = 0; for (IndexSelector.IndexFitness result : analysis.getResults()) { if ((result.getInclusiveRangeStartFilters().length > 0 || result.getExclusiveRangeStartFilters().length > 0 || result.getInclusiveRangeEndFilters().length > 0 || result.getExclusiveRangeEndFilters().length > 0) && (result.getHandledOrderings() != null || result.getRemainderOrderings() == null)) { StorableIndex index = result.getIndex(); int count = counts.get(index); if (count > bestCount) { best = index; bestCount = count; } } } { int newLength = (orderings == null ? 0 : orderings.length) + (best == null ? 0 : best.getPropertyCount()) + unique.getPropertyCount(); totalOrderings = new OrderedProperty[newLength]; int j = 0; if (orderings != null) { for (int i=0; i(mPrimaryKeyIndex, mIndexSet, totalOrderings); dnfFilter.accept(analysis, null); if (analysis.noBestIndex()) { // Fallback to full scan for everything if no best index found for // just one query component. return fullScan(values, orderings); } } // Attempt to reduce the number of separate cursors need to be opened for union. analysis.reduceResults(); List> subFactories = new ArrayList>(); for (IndexSelector.IndexFitness result : analysis.getResults()) { CursorFactory subFactory; // Determine if KeyCursorFactory should be used instead. boolean isKeyFilter = result.isKeyFilter(); if (isKeyFilter) { subFactory = new KeyCursorFactory (this, result.getIndex(), result.getExactFilter()); } else { subFactory = new IndexCursorFactory (this, result.getIndex(), result.shouldReverseOrder(), result.shouldReverseRange(), result.getExactFilter(), result.getInclusiveRangeStartFilters(), result.getExclusiveRangeStartFilters(), result.getInclusiveRangeEndFilters(), result.getExclusiveRangeEndFilters()); } Filter remainderFilter = result.getRemainderFilter(); if (remainderFilter != null) { subFactory = new FilteredCursorFactory(this, subFactory, remainderFilter); } if (!isKeyFilter) { OrderedProperty[] remainderOrderings = result.getRemainderOrderings(); if (remainderOrderings != null && remainderOrderings.length > 0) { subFactory = new SortedCursorFactory (this, subFactory, result.getHandledOrderings(), remainderOrderings); } } subFactories.add(subFactory); } CursorFactory factory = UnionedCursorFactory .createUnion(this, subFactories, totalOrderings); return CompiledQuery.create(mRepository, mStorage, values, orderings, this, factory); } private Query fullScan(FilterValues values, OrderedProperty[] orderings) throws FetchException { // Try to select index that has best ordering. IndexSelector selector = new IndexSelector(null, orderings); StorableIndex best = mPrimaryKeyIndex; if (mIndexSet != null) { for (StorableIndex candidate : mIndexSet) { int cmpResult = selector.compare(best, candidate); if (cmpResult > 0) { best = candidate; } } } IndexSelector.IndexFitness result = selector.examine(best); CursorFactory factory; if (result == null || result.isUseless()) { factory = new FullScanCursorFactory(this, mPrimaryKeyIndex); if (values != null) { factory = new FilteredCursorFactory(this, factory, values.getFilter()); } if (orderings != null && orderings.length > 0) { factory = new SortedCursorFactory(this, factory, null, orderings); } } else { factory = new IndexCursorFactory (this, result.getIndex(), result.shouldReverseOrder(), result.shouldReverseRange(), result.getExactFilter(), result.getInclusiveRangeStartFilters(), result.getExclusiveRangeStartFilters(), result.getInclusiveRangeEndFilters(), result.getExclusiveRangeEndFilters()); Filter remainderFilter = result.getRemainderFilter(); if (remainderFilter != null) { factory = new FilteredCursorFactory(this, factory, remainderFilter); } OrderedProperty[] remainderOrderings = result.getRemainderOrderings(); if (remainderOrderings != null && remainderOrderings.length > 0) { factory = new SortedCursorFactory (this, factory, result.getHandledOrderings(), remainderOrderings); } } return CompiledQuery.create(mRepository, mStorage, values, orderings, this, factory); } /** * Returns the primary Storage object in this object. */ protected final Storage getStorage() { return mStorage; } /** * Returns the storage object that the given index applies to. By default, * this method returns the primary storage. Override if indexes may be * defined in multiple storages. */ protected Storage getStorageFor(StorableIndex index) { return mStorage; } /** * Return a new Cursor instance constrained by the given parameters. The * index values are aligned with the index properties at property index * 0. An optional start or end boundary matches up with the index property * following the last of the index values. * * @param index index to open, which may be the primary key index * @param exactValues optional list of exactly matching values to apply to index * @param rangeStartBoundary start boundary type * @param rangeStartValue value to start at if boundary is not open * @param rangeEndBoundary end boundary type * @param rangeEndValue value to end at if boundary is not open * @param reverseRange indicates that range operates on a property that is * ordered in reverse (this parameter might also be true simply because * reverseOrder is true) * @param reverseOrder when true, iteration is reversed */ protected abstract Cursor openCursor(StorableIndex index, Object[] exactValues, BoundaryType rangeStartBoundary, Object rangeStartValue, BoundaryType rangeEndBoundary, Object rangeEndValue, boolean reverseRange, boolean reverseOrder) throws FetchException; /** * Return a new Cursor instance which is expected to fetch at most one * object. The chosen index is unique, and a primary or alternate key is * contained within it. *

* Subclasses are encouraged to override this method and provide a more * efficient implementation. * * @param index index to open, which may be the primary key index * @param exactValues first values to set for index; length may be smaller * than index property count */ protected Cursor openKeyCursor(StorableIndex index, Object[] exactValues) throws FetchException { return openCursor(index, exactValues, BoundaryType.OPEN, null, BoundaryType.OPEN, null, false, false); } @SuppressWarnings("unchecked") Comparator makeComparator(OrderedProperty[] orderings) { if (orderings == null) { return null; } BeanComparator bc = BeanComparator.forClass(getStorableInfo().getStorableType()); for (OrderedProperty property : orderings) { bc = bc.orderBy(property.getChainedProperty().toString()); bc = bc.caseSensitive(); if (property.getDirection() == Direction.DESCENDING) { bc = bc.reverse(); } } return bc; } private static class CompiledQuery extends BaseQuery { private final BaseQueryEngine mEngine; private final CursorFactory mFactory; static Query create(Repository repo, Storage storage, FilterValues values, OrderedProperty[] orderings, BaseQueryEngine engine, CursorFactory factory) throws FetchException { if (factory == null) { throw new IllegalArgumentException(); } factory = factory.getActualFactory(); return new CompiledQuery(repo, storage, values, orderings, engine, factory); } private CompiledQuery(Repository repo, Storage storage, FilterValues values, OrderedProperty[] orderings, BaseQueryEngine engine, CursorFactory factory) throws FetchException { super(repo, storage, values, orderings); mEngine = engine; mFactory = factory; } private CompiledQuery(Repository repo, Storage storage, FilterValues values, String[] orderings, BaseQueryEngine engine, CursorFactory factory) { super(repo, storage, values, orderings); mEngine = engine; mFactory = factory; } public Query orderBy(String property) throws FetchException, UnsupportedOperationException { return mEngine.getOrderedQuery(getFilterValues(), property); } public Query orderBy(String... properties) throws FetchException, UnsupportedOperationException { return mEngine.getOrderedQuery(getFilterValues(), properties); } public Cursor fetch() throws FetchException { return mFactory.openCursor(getFilterValues()); } public long count() throws FetchException { return mFactory.count(getFilterValues()); } public boolean printNative(Appendable app, int indentLevel) throws IOException { return mFactory.printNative(app, indentLevel, getFilterValues()); } public boolean printPlan(Appendable app, int indentLevel) throws IOException { return mFactory.printPlan(app, indentLevel, getFilterValues()); } protected BaseQuery newInstance(FilterValues values) { return new CompiledQuery (getRepository(), getStorage(), values, getOrderings(), mEngine, mFactory); } } private static interface CursorFactory { Cursor openCursor(FilterValues values) throws FetchException; long count(FilterValues values) throws FetchException; /** * Append filter rules to the given filter. * * @param filter initial filter, might be null. */ Filter buildFilter(Filter filter); /** * Applies an ordering to the given query in a new query. */ Query applyOrderBy(Query query) throws FetchException; /** * Returns the storage object that this factory needs to use. Usually, * this is the same as the primary. If multiple storages are needed, * then null is returned. In either case, if the storage is not the * primary, then this factory cannot be used. Use the factory from * getActualFactory instead. */ Storage getActualStorage(); /** * Returns another instance of this factory that uses the proper * storage. */ CursorFactory getActualFactory() throws FetchException; /** * @param values optional */ boolean printNative(Appendable app, int indentLevel, FilterValues values) throws IOException; /** * @param values optional */ boolean printPlan(Appendable app, int indentLevel, FilterValues values) throws IOException; } private abstract static class AbstractCursorFactory implements CursorFactory { protected final BaseQueryEngine mEngine; AbstractCursorFactory(BaseQueryEngine engine) { mEngine = engine; } public long count(FilterValues values) throws FetchException { Cursor cursor = openCursor(values); try { long count = cursor.skipNext(Integer.MAX_VALUE); if (count == Integer.MAX_VALUE) { int amt; while ((amt = cursor.skipNext(Integer.MAX_VALUE)) > 0) { count += amt; } } return count; } finally { cursor.close(); } } public CursorFactory getActualFactory() throws FetchException { Storage storage = getActualStorage(); if (storage == mEngine.getStorage()) { return this; } return new QueryCursorFactory(this, storage); } public boolean printNative(Appendable app, int indentLevel, FilterValues values) throws IOException { return false; } void indent(Appendable app, int indentLevel) throws IOException { for (int i=0; i extends AbstractCursorFactory { protected final StorableIndex mIndex; private final boolean mReverseOrder; private final boolean mReverseRange; private final Filter mExactFilter; private final PropertyFilter[] mInclusiveRangeStartFilters; private final PropertyFilter[] mExclusiveRangeStartFilters; private final PropertyFilter[] mInclusiveRangeEndFilters; private final PropertyFilter[] mExclusiveRangeEndFilters; IndexCursorFactory(BaseQueryEngine engine, StorableIndex index, boolean reverseOrder, boolean reverseRange, Filter exactFilter, PropertyFilter[] inclusiveRangeStartFilters, PropertyFilter[] exclusiveRangeStartFilters, PropertyFilter[] inclusiveRangeEndFilters, PropertyFilter[] exclusiveRangeEndFilters) { super(engine); mIndex = index; mExactFilter = exactFilter; mReverseOrder = reverseOrder; mReverseRange = reverseRange; mInclusiveRangeStartFilters = inclusiveRangeStartFilters; mExclusiveRangeStartFilters = exclusiveRangeStartFilters; mInclusiveRangeEndFilters = inclusiveRangeEndFilters; mExclusiveRangeEndFilters = exclusiveRangeEndFilters; } public Cursor openCursor(FilterValues values) throws FetchException { Object[] exactValues = null; Object rangeStartValue = null; Object rangeEndValue = null; BoundaryType rangeStartBoundary = BoundaryType.OPEN; BoundaryType rangeEndBoundary = BoundaryType.OPEN; if (values != null) { if (mExactFilter != null) { exactValues = values.getValuesFor(mExactFilter); } // In determining the proper range values and boundary types, // the order in which this code runs is important. The exclusive // filters must be checked before the inclusive filters. for (PropertyFilter p : mExclusiveRangeStartFilters) { Object value = values.getValue(p); if (rangeStartBoundary == BoundaryType.OPEN || compareWithNullHigh(value, rangeStartValue) > 0) { rangeStartValue = value; rangeStartBoundary = BoundaryType.EXCLUSIVE; } } for (PropertyFilter p : mInclusiveRangeStartFilters) { Object value = values.getValue(p); if (rangeStartBoundary == BoundaryType.OPEN || compareWithNullHigh(value, rangeStartValue) > 0) { rangeStartValue = value; rangeStartBoundary = BoundaryType.INCLUSIVE; } } for (PropertyFilter p : mExclusiveRangeEndFilters) { Object value = values.getValue(p); if (rangeEndBoundary == BoundaryType.OPEN || compareWithNullHigh(value, rangeEndValue) < 0) { rangeEndValue = value; rangeEndBoundary = BoundaryType.EXCLUSIVE; } } for (PropertyFilter p : mInclusiveRangeEndFilters) { Object value = values.getValue(p); if (rangeEndBoundary == BoundaryType.OPEN || compareWithNullHigh(value, rangeEndValue) < 0) { rangeEndValue = value; rangeEndBoundary = BoundaryType.INCLUSIVE; } } } return mEngine.openCursor(mIndex, exactValues, rangeStartBoundary, rangeStartValue, rangeEndBoundary, rangeEndValue, mReverseRange, mReverseOrder); } public Filter buildFilter(Filter filter) { if (mExactFilter != null) { filter = filter == null ? mExactFilter : filter.and(mExactFilter); } for (PropertyFilter p : mInclusiveRangeStartFilters) { filter = filter == null ? p : filter.and(p); } for (PropertyFilter p : mExclusiveRangeStartFilters) { filter = filter == null ? p : filter.and(p); } for (PropertyFilter p : mInclusiveRangeEndFilters) { filter = filter == null ? p : filter.and(p); } for (PropertyFilter p : mExclusiveRangeEndFilters) { filter = filter == null ? p : filter.and(p); } return filter; } public Query applyOrderBy(Query query) throws FetchException { if (mIndex == null) { // Index is null if this is a full scan with no ordering specified. return query; } int count = mIndex.getPropertyCount(); String[] orderBy = new String[count]; for (int i=0; i getActualStorage() { return mEngine.getStorageFor(mIndex); } public boolean printPlan(Appendable app, int indentLevel, FilterValues values) throws IOException { indent(app, indentLevel); if (mReverseOrder) { app.append("reverse "); } if (mIndex.isClustered()) { app.append("clustered "); } app.append("index scan: "); app.append(mEngine.getStorableInfo().getStorableType().getName()); app.append('\n'); indent(app, indentLevel); app.append("...index: "); mIndex.appendTo(app); app.append('\n'); if (mExactFilter != null) { indent(app, indentLevel); app.append("...exact filter: "); mExactFilter.appendTo(app, values); app.append('\n'); } if (mInclusiveRangeStartFilters.length > 0 || mExclusiveRangeStartFilters.length > 0 || mInclusiveRangeEndFilters.length > 0 || mExclusiveRangeEndFilters.length > 0) { indent(app, indentLevel); app.append("...range filter: "); int count = 0; for (PropertyFilter p : mExclusiveRangeStartFilters) { if (count++ > 0) { app.append(" & "); } p.appendTo(app, values); } for (PropertyFilter p : mInclusiveRangeStartFilters) { if (count++ > 0) { app.append(" & "); } p.appendTo(app, values); } for (PropertyFilter p : mExclusiveRangeEndFilters) { if (count++ > 0) { app.append(" & "); } p.appendTo(app, values); } for (PropertyFilter p : mInclusiveRangeEndFilters) { if (count++ > 0) { app.append(" & "); } p.appendTo(app, values); } app.append('\n'); } return true; } } private static class FullScanCursorFactory extends IndexCursorFactory { FullScanCursorFactory(BaseQueryEngine engine, StorableIndex index) { super(engine, index, false, false, null, NO_FILTERS, NO_FILTERS, NO_FILTERS, NO_FILTERS); } @Override public Filter buildFilter(Filter filter) { // Full scan doesn't filter anything. return filter; } @Override public boolean printPlan(Appendable app, int indentLevel, FilterValues values) throws IOException { indent(app, indentLevel); app.append("full scan: "); app.append(mEngine.getStorableInfo().getStorableType().getName()); app.append('\n'); return true; } } private static class KeyCursorFactory extends AbstractCursorFactory { private final StorableIndex mIndex; private final Filter mExactFilter; KeyCursorFactory(BaseQueryEngine engine, StorableIndex index, Filter exactFilter) { super(engine); mIndex = index; mExactFilter = exactFilter; } public Cursor openCursor(FilterValues values) throws FetchException { return mEngine.openKeyCursor(mIndex, values.getValuesFor(mExactFilter)); } public Filter buildFilter(Filter filter) { if (mExactFilter != null) { filter = filter == null ? mExactFilter : filter.and(mExactFilter); } return filter; } public Query applyOrderBy(Query query) { return query; } public Storage getActualStorage() { return mEngine.getStorageFor(mIndex); } public boolean printPlan(Appendable app, int indentLevel, FilterValues values) throws IOException { indent(app, indentLevel); app.append("index key: "); app.append(mEngine.getStorableInfo().getStorableType().getName()); app.append('\n'); indent(app, indentLevel); app.append("...index: "); mIndex.appendTo(app); app.append('\n'); indent(app, indentLevel); app.append("...exact filter: "); mExactFilter.appendTo(app, values); app.append('\n'); return true; } } private static class FilteredCursorFactory extends AbstractCursorFactory { private final CursorFactory mFactory; private final Filter mFilter; FilteredCursorFactory(BaseQueryEngine engine, CursorFactory factory, Filter filter) { super(engine); mFactory = factory; mFilter = filter; } public Cursor openCursor(FilterValues values) throws FetchException { return FilteredCursor.applyFilter(mFilter, values, mFactory.openCursor(values)); } public Filter buildFilter(Filter filter) { filter = mFactory.buildFilter(filter); filter = filter == null ? mFilter : filter.and(mFilter); return filter; } public Query applyOrderBy(Query query) throws FetchException { return mFactory.applyOrderBy(query); } public Storage getActualStorage() { return mFactory.getActualStorage(); } public boolean printPlan(Appendable app, int indentLevel, FilterValues values) throws IOException { indent(app, indentLevel); app.append("filter: "); mFilter.appendTo(app, values); app.append('\n'); mFactory.printPlan(app, indentLevel + 2, values); return true; } } private static class SortedCursorFactory extends AbstractCursorFactory { private final CursorFactory mFactory; private final OrderedProperty[] mHandledOrderings; private final OrderedProperty[] mRemainderOrderings; private final Comparator mHandledComparator; private final Comparator mFinisherComparator; SortedCursorFactory(BaseQueryEngine engine, CursorFactory factory, OrderedProperty[] handledOrderings, OrderedProperty[] remainderOrderings) { super(engine); mFactory = factory; if (handledOrderings != null && handledOrderings.length == 0) { handledOrderings = null; } if (remainderOrderings != null && remainderOrderings.length == 0) { remainderOrderings = null; } if (handledOrderings == null && remainderOrderings == null) { throw new IllegalArgumentException(); } mHandledOrderings = handledOrderings; mRemainderOrderings = remainderOrderings; mHandledComparator = engine.makeComparator(handledOrderings); mFinisherComparator = engine.makeComparator(remainderOrderings); } public Cursor openCursor(FilterValues values) throws FetchException { Cursor cursor = mFactory.openCursor(values); SortBuffer buffer = new MergeSortBuffer (getActualStorage(), mEngine.mMergeSortTempDir); return new SortedCursor(cursor, buffer, mHandledComparator, mFinisherComparator); } @Override public long count(FilterValues values) throws FetchException { return mFactory.count(values); } public Filter buildFilter(Filter filter) { return mFactory.buildFilter(filter); } public Query applyOrderBy(Query query) throws FetchException { int handledLength = mHandledOrderings == null ? 0 : mHandledOrderings.length; int remainderLength = mRemainderOrderings == null ? 0 : mRemainderOrderings.length; String[] orderBy = new String[handledLength + remainderLength]; int pos = 0; for (int i=0; i getActualStorage() { return mFactory.getActualStorage(); } public boolean printPlan(Appendable app, int indentLevel, FilterValues values) throws IOException { indent(app, indentLevel); if (mHandledOrderings == null) { app.append("full sort: "); } else { app.append("finish sort: "); } app.append(Arrays.toString(mRemainderOrderings)); app.append('\n'); mFactory.printPlan(app, indentLevel + 2, values); return true; } } private static class UnionedCursorFactory extends AbstractCursorFactory { static CursorFactory createUnion (BaseQueryEngine engine, List> factories, OrderedProperty[] totalOrderings) { Comparator orderComparator = engine.makeComparator(totalOrderings); return createUnion(engine, factories, totalOrderings, orderComparator); } @SuppressWarnings("unchecked") static CursorFactory createUnion (BaseQueryEngine engine, List> factories, OrderedProperty[] totalOrderings, Comparator orderComparator) { if (factories.size() > 1) { CursorFactory[] array = new CursorFactory[factories.size()]; factories.toArray(array); return new UnionedCursorFactory(engine, array, totalOrderings, orderComparator); } return factories.get(0); } private final CursorFactory[] mFactories; private final OrderedProperty[] mTotalOrderings; private final Comparator mOrderComparator; private UnionedCursorFactory(BaseQueryEngine engine, CursorFactory[] factories, OrderedProperty[] totalOrderings, Comparator orderComparator) { super(engine); mFactories = factories; mTotalOrderings = totalOrderings; mOrderComparator = orderComparator; } public Cursor openCursor(FilterValues values) throws FetchException { Cursor cursor = null; for (CursorFactory factory : mFactories) { Cursor subCursor = factory.openCursor(values); cursor = (cursor == null) ? subCursor : new UnionCursor(cursor, subCursor, mOrderComparator); } return cursor; } public Filter buildFilter(Filter filter) { for (CursorFactory factory : mFactories) { Filter subFilter = factory.buildFilter(null); filter = filter == null ? subFilter : filter.or(subFilter); } return filter; } public Query applyOrderBy(Query query) throws FetchException { if (mTotalOrderings == null || mTotalOrderings.length == 0) { return query; } String[] orderBy = new String[mTotalOrderings.length]; for (int i=mTotalOrderings.length; --i>=0; ) { orderBy[i] = mTotalOrderings[i].toString(); } return query.orderBy(orderBy); } public Storage getActualStorage() { Storage storage = null; for (CursorFactory factory : mFactories) { Storage subStorage = factory.getActualStorage(); if (storage == null) { storage = subStorage; } else if (storage != subStorage) { return null; } } return storage; } @Override public CursorFactory getActualFactory() throws FetchException { Storage requiredStorage = getActualStorage(); if (requiredStorage == mEngine.getStorage()) { // Alternate not really needed. return this; } if (requiredStorage != null) { // All components require same external storage, so let // external storage do the union. return new QueryCursorFactory(this, requiredStorage); } // Group factories by required storage instance, and then create a // union of unions. Comparator> comparator = new Comparator>() { public int compare(CursorFactory a, CursorFactory b) { Storage aStorage = a.getActualStorage(); Storage bStorage = b.getActualStorage(); if (aStorage == bStorage) { return 0; } Storage engineStorage = mEngine.getStorage(); if (aStorage == engineStorage) { return -1; } else if (bStorage == engineStorage) { return 1; } int aHash = System.identityHashCode(a); int bHash = System.identityHashCode(b); if (aHash < bHash) { return -1; } else if (aHash > bHash) { return 1; } return 0; } }; Arrays.sort(mFactories, comparator); List> masterList = new ArrayList>(); List> subList = new ArrayList>(); Storage group = null; for (CursorFactory factory : mFactories) { Storage storage = factory.getActualStorage(); if (group != storage) { if (subList.size() > 0) { masterList.add(createUnion (mEngine, subList, mTotalOrderings, mOrderComparator)); subList.clear(); } group = storage; } CursorFactory subFactory = new QueryCursorFactory(factory, storage); subList.add(subFactory); } if (subList.size() > 0) { masterList.add(createUnion(mEngine, subList, mTotalOrderings, mOrderComparator)); subList.clear(); } return createUnion(mEngine, masterList, mTotalOrderings, mOrderComparator); } public boolean printPlan(Appendable app, int indentLevel, FilterValues values) throws IOException { indent(app, indentLevel); app.append("union"); app.append('\n'); for (CursorFactory factory : mFactories) { factory.printPlan(app, indentLevel + 2, values); } return true; } } /** * CursorFactory implementation that reconstructs and calls an external * Query. */ private static class QueryCursorFactory implements CursorFactory { private final CursorFactory mFactory; private final Storage mStorage; private final Query mQuery; /** * @param factory factory to derive this factory from * @param storage actual storage to query against */ QueryCursorFactory(CursorFactory factory, Storage storage) throws FetchException { mFactory = factory; mStorage = storage; Filter filter = factory.buildFilter(null); Query query; if (filter == null) { query = storage.query(); } else { query = storage.query(filter); } mQuery = factory.applyOrderBy(query); } public Cursor openCursor(FilterValues values) throws FetchException { return applyFilterValues(values).fetch(); } public long count(FilterValues values) throws FetchException { return applyFilterValues(values).count(); } public Filter buildFilter(Filter filter) { return mFactory.buildFilter(filter); } public Query applyOrderBy(Query query) throws FetchException { return mFactory.applyOrderBy(query); } public Storage getActualStorage() { return mStorage; } public CursorFactory getActualFactory() { return this; } public boolean printNative(Appendable app, int indentLevel, FilterValues values) throws IOException { return applyFilterValues(values).printNative(app, indentLevel); } public boolean printPlan(Appendable app, int indentLevel, FilterValues values) throws IOException { Query query; try { query = applyFilterValues(values); } catch (IllegalStateException e) { query = mQuery; } return query.printPlan(app, indentLevel); } private Query applyFilterValues(FilterValues values) { // FIXME: figure out how to transfer values directly to query. Query query = mQuery; Filter filter = query.getFilter(); // FIXME: this code can get confused if filter has constants. if (values != null && filter != null && query.getBlankParameterCount() != 0) { query = query.withValues(values.getValuesFor(filter)); } return query; } } private static class IndexAnalysis extends Visitor implements Comparable> { private final StorableIndex mPrimaryKeyIndex; private final StorableIndexSet mIndexSet; private final OrderedProperty[] mOrderings; private List> mResults; IndexAnalysis(StorableIndex primaryKeyIndex, StorableIndexSet indexSet, OrderedProperty[] orderings) { mPrimaryKeyIndex = primaryKeyIndex; mIndexSet = indexSet; mOrderings = orderings; mResults = new ArrayList>(); } public Object visit(OrFilter filter, Object param) { Filter left = filter.getLeftFilter(); if (!(left instanceof OrFilter)) { selectIndex(left); } else { left.accept(this, param); } Filter right = filter.getRightFilter(); if (!(right instanceof OrFilter)) { selectIndex(right); } else { right.accept(this, param); } return null; } // This method should only be called if root filter has no 'or' operators. public Object visit(AndFilter filter, Object param) { selectIndex(filter); return null; } // This method should only be called if root filter has no logical operators. public Object visit(PropertyFilter filter, Object param) { selectIndex(filter); return null; } /** * Compares this analysis to another which belongs to a different * Storable type. Filters that reference a joined property may be best * served by an index defined in the joined type, and this method aids * in that selection. * * @return <0 if these results are better, 0 if equal, or >0 if other is better */ public int compareTo(IndexAnalysis otherAnalysis) { if (noBestIndex()) { if (otherAnalysis.noBestIndex()) { return 0; } return 1; } else if (otherAnalysis.noBestIndex()) { return -1; } else { return IndexSelector.listCompare(mResults, otherAnalysis.mResults); } } /** * If more than one result returned, then a union must be performed. * This is because there exist 'or' operators in the full filter. */ List> getResults() { return mResults; } /** * If more than one result, then a union must be performed. Attempt to * reduce the result list by performing unions at the index layer. This * reduces the number of cursors that need to be opened for a query, * eliminating duplicate work. */ void reduceResults() { if (mResults.size() <= 1) { return; } List> reduced = new ArrayList>(mResults.size()); gather: for (int i=0; i result : mResults) { if (result.isUseless()) { return true; } } return false; } private void selectIndex(Filter filter) { IndexSelector selector = new IndexSelector(filter, mOrderings); StorableIndex best = mPrimaryKeyIndex; if (mIndexSet != null) { for (StorableIndex candidate : mIndexSet) { int result = selector.compare(best, candidate); if (result > 0) { best = candidate; } } } mResults.add(selector.examine(best)); } } }