summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/main/java/com/amazon/carbonado/qe/EmptyQuery.java41
-rw-r--r--src/main/java/com/amazon/carbonado/qe/FilteringScore.java6
-rw-r--r--src/main/java/com/amazon/carbonado/qe/FullScanIndexedQueryExecutor.java61
-rw-r--r--src/main/java/com/amazon/carbonado/qe/FullScanQueryExecutor.java34
-rw-r--r--src/main/java/com/amazon/carbonado/qe/IndexedQueryAnalyzer.java182
-rw-r--r--src/main/java/com/amazon/carbonado/qe/IndexedQueryExecutor.java84
-rw-r--r--src/main/java/com/amazon/carbonado/qe/IterableQueryExecutor.java39
-rw-r--r--src/main/java/com/amazon/carbonado/qe/KeyQueryExecutor.java35
-rw-r--r--src/main/java/com/amazon/carbonado/qe/OrderingList.java25
-rw-r--r--src/main/java/com/amazon/carbonado/qe/PropertyFilterList.java4
-rw-r--r--src/main/java/com/amazon/carbonado/qe/QueryEngine.java67
-rw-r--r--src/main/java/com/amazon/carbonado/qe/QueryExecutor.java1
-rw-r--r--src/main/java/com/amazon/carbonado/qe/QueryExecutorCache.java86
-rw-r--r--src/main/java/com/amazon/carbonado/qe/QueryExecutorFactory.java (renamed from src/main/java/com/amazon/carbonado/qe/IndexProvider.java)21
-rw-r--r--src/main/java/com/amazon/carbonado/qe/QueryFactory.java (renamed from src/main/java/com/amazon/carbonado/qe/ArraySortedQueryExecutor.java)32
-rw-r--r--src/main/java/com/amazon/carbonado/qe/RepositoryAccess.java44
-rw-r--r--src/main/java/com/amazon/carbonado/qe/SortedQueryExecutor.java28
-rw-r--r--src/main/java/com/amazon/carbonado/qe/StandardQuery.java153
-rw-r--r--src/main/java/com/amazon/carbonado/qe/StandardQueryFactory.java227
-rw-r--r--src/main/java/com/amazon/carbonado/qe/StorageAccess.java63
-rw-r--r--src/main/java/com/amazon/carbonado/qe/UnionQueryAnalyzer.java110
-rw-r--r--src/main/java/com/amazon/carbonado/spi/BaseQuery.java4
-rw-r--r--src/test/java/com/amazon/carbonado/qe/TestIndexedQueryAnalyzer.java106
-rw-r--r--src/test/java/com/amazon/carbonado/qe/TestIndexedQueryExecutor.java44
-rw-r--r--src/test/java/com/amazon/carbonado/qe/TestJoinedQueryExecutor.java19
-rw-r--r--src/test/java/com/amazon/carbonado/qe/TestSortedQueryExecutor.java4
-rw-r--r--src/test/java/com/amazon/carbonado/qe/TestUnionQueryAnalyzer.java20
-rw-r--r--src/test/java/com/amazon/carbonado/qe/TestUnionQueryExecutor.java4
-rw-r--r--src/test/java/com/amazon/carbonado/repo/toy/ToyStorage.java62
29 files changed, 1240 insertions, 366 deletions
diff --git a/src/main/java/com/amazon/carbonado/qe/EmptyQuery.java b/src/main/java/com/amazon/carbonado/qe/EmptyQuery.java
index 5da585a..f07e7ff 100644
--- a/src/main/java/com/amazon/carbonado/qe/EmptyQuery.java
+++ b/src/main/java/com/amazon/carbonado/qe/EmptyQuery.java
@@ -24,7 +24,6 @@ import com.amazon.carbonado.Cursor;
import com.amazon.carbonado.FetchException;
import com.amazon.carbonado.IsolationLevel;
import com.amazon.carbonado.PersistNoneException;
-import com.amazon.carbonado.Storage;
import com.amazon.carbonado.Storable;
import com.amazon.carbonado.Query;
@@ -41,20 +40,20 @@ import com.amazon.carbonado.info.OrderedProperty;
* @author Brian S O'Neill
*/
public final class EmptyQuery<S extends Storable> extends AbstractQuery<S> {
- private final Storage<S> mRootStorage;
+ private final QueryFactory<S> mFactory;
// Properties that this query is ordered by.
private final OrderingList<S> mOrdering;
/**
- * @param rootStorage required root storage object, used by 'or' and 'not' methods
+ * @param factory required query factory, used by 'or' and 'not' methods
* @param ordering optional order-by properties
*/
- public EmptyQuery(Storage<S> rootStorage, OrderingList<S> ordering) {
- if (rootStorage == null) {
+ public EmptyQuery(QueryFactory<S> factory, OrderingList<S> ordering) {
+ if (factory == null) {
throw new IllegalArgumentException();
}
- mRootStorage = rootStorage;
+ mFactory = factory;
if (ordering == null) {
ordering = OrderingList.emptyList();
}
@@ -62,23 +61,23 @@ public final class EmptyQuery<S extends Storable> extends AbstractQuery<S> {
}
/**
- * @param rootStorage required root storage object, used by 'or' and 'not' methods
+ * @param factory required query factory, used by 'or' and 'not' methods
* @param ordering optional order-by property
*/
- public EmptyQuery(Storage<S> rootStorage, String ordering) {
- this(rootStorage, OrderingList.get(rootStorage.getStorableType(), ordering));
+ public EmptyQuery(QueryFactory<S> factory, String ordering) {
+ this(factory, OrderingList.get(factory.getStorableType(), ordering));
}
/**
- * @param rootStorage required root storage object, used by 'or' and 'not' methods
+ * @param factory required query factory, used by 'or' and 'not' methods
* @param orderings optional order-by properties
*/
- public EmptyQuery(Storage<S> rootStorage, String... orderings) {
- this(rootStorage, OrderingList.get(rootStorage.getStorableType(), orderings));
+ public EmptyQuery(QueryFactory<S> factory, String... orderings) {
+ this(factory, OrderingList.get(factory.getStorableType(), orderings));
}
public Class<S> getStorableType() {
- return mRootStorage.getStorableType();
+ return mFactory.getStorableType();
}
/**
@@ -182,30 +181,24 @@ public final class EmptyQuery<S extends Storable> extends AbstractQuery<S> {
throw new IllegalStateException("Query is already guaranteed to fetch nothing");
}
- /**
- * Returns a query that fetches only what's specified by the given filter.
- */
public Query<S> or(Filter<S> filter) throws FetchException {
- return mRootStorage.query(filter);
+ FilterValues<S> values = filter == null ? null : filter.initialFilterValues();
+ return mFactory.query(values, mOrdering);
}
/**
* Returns a query that fetches everything, possibly in a specified order.
*/
public Query<S> not() throws FetchException {
- Query<S> query = mRootStorage.query();
- if (mOrdering.size() > 0) {
- query = query.orderBy(mOrdering.asStringArray());
- }
- return query;
+ return mFactory.query(null, mOrdering);
}
public Query<S> orderBy(String property) throws FetchException {
- return new EmptyQuery<S>(mRootStorage, property);
+ return new EmptyQuery<S>(mFactory, property);
}
public Query<S> orderBy(String... properties) throws FetchException {
- return new EmptyQuery<S>(mRootStorage, properties);
+ return new EmptyQuery<S>(mFactory, properties);
}
/**
diff --git a/src/main/java/com/amazon/carbonado/qe/FilteringScore.java b/src/main/java/com/amazon/carbonado/qe/FilteringScore.java
index 5ca9d04..c9178a1 100644
--- a/src/main/java/com/amazon/carbonado/qe/FilteringScore.java
+++ b/src/main/java/com/amazon/carbonado/qe/FilteringScore.java
@@ -521,11 +521,7 @@ public class FilteringScore<S extends Storable> {
Filter<S> otherRemainderFilter = other.getRemainderFilter();
if (thisRemainderFilter == null) {
- if (otherRemainderFilter == null) {
- return null;
- } else {
- return otherRemainderFilter;
- }
+ return otherRemainderFilter;
} else if (otherRemainderFilter == null) {
return thisRemainderFilter;
} else if (thisRemainderFilter.equals(otherRemainderFilter)) {
diff --git a/src/main/java/com/amazon/carbonado/qe/FullScanIndexedQueryExecutor.java b/src/main/java/com/amazon/carbonado/qe/FullScanIndexedQueryExecutor.java
index e7c6f2b..cfd012b 100644
--- a/src/main/java/com/amazon/carbonado/qe/FullScanIndexedQueryExecutor.java
+++ b/src/main/java/com/amazon/carbonado/qe/FullScanIndexedQueryExecutor.java
@@ -35,53 +35,62 @@ import com.amazon.carbonado.info.OrderedProperty;
import com.amazon.carbonado.info.StorableIndex;
/**
- * Abstract QueryExecutor which fully scans all Storables of a given type,
- * referencing an index.
+ * QueryExecutor which fully scans all Storables of a given type, referencing
+ * an index.
*
* @author Brian S O'Neill
*/
-public abstract class FullScanIndexedQueryExecutor<S extends Storable>
- extends FullScanQueryExecutor<S>
-{
- private static <S extends Storable> Class<S> getType(StorableIndex<S> index) {
- if (index == null) {
- throw new IllegalArgumentException();
- }
- return index.getStorableType();
- }
-
+public class FullScanIndexedQueryExecutor<S extends Storable> extends AbstractQueryExecutor<S> {
+ private final Support<S> mSupport;
private final StorableIndex<S> mIndex;
/**
+ * @param support support for full scan
* @param index index to use, which may be a primary key index
- * @throws IllegalArgumentException if index is null
+ * @throws IllegalArgumentException if support or index is null
*/
- public FullScanIndexedQueryExecutor(StorableIndex<S> index) {
- super(getType(index));
+ public FullScanIndexedQueryExecutor(Support<S> support, StorableIndex<S> index) {
+ if (support == null || index == null) {
+ throw new IllegalArgumentException();
+ }
+ mSupport = support;
mIndex = index;
}
- @Override
+ /**
+ * Returns an open filter.
+ */
+ public Filter<S> getFilter() {
+ return Filter.getOpenFilter(mIndex.getStorableType());
+ }
+
public Cursor<S> fetch(FilterValues<S> values) throws FetchException {
- return fetch(mIndex);
+ return mSupport.fetch(mIndex);
}
/**
* Returns the natural order of the index.
*/
- @Override
public OrderingList<S> getOrdering() {
return OrderingList.get(mIndex.getOrderedProperties());
}
- protected Cursor<S> fetch() throws FetchException {
- return fetch(mIndex);
+ public boolean printPlan(Appendable app, int indentLevel, FilterValues<S> values)
+ throws IOException
+ {
+ indent(app, indentLevel);
+ app.append("full index scan: ");
+ app.append(mIndex.getStorableType().getName());
+ newline(app);
+ return true;
}
- /**
- * Return a new Cursor instance referenced by the given index.
- *
- * @param index index to open, which may be a primary key index
- */
- protected abstract Cursor<S> fetch(StorableIndex<S> index) throws FetchException;
+ public static interface Support<S extends Storable> {
+ /**
+ * Perform a full scan of all Storables referenced by an index.
+ *
+ * @param index index to scan, which may be a primary key index
+ */
+ Cursor<S> fetch(StorableIndex<S> index) throws FetchException;
+ }
}
diff --git a/src/main/java/com/amazon/carbonado/qe/FullScanQueryExecutor.java b/src/main/java/com/amazon/carbonado/qe/FullScanQueryExecutor.java
index fc29983..2875542 100644
--- a/src/main/java/com/amazon/carbonado/qe/FullScanQueryExecutor.java
+++ b/src/main/java/com/amazon/carbonado/qe/FullScanQueryExecutor.java
@@ -33,33 +33,33 @@ import com.amazon.carbonado.filter.FilterValues;
import com.amazon.carbonado.info.OrderedProperty;
/**
- * Abstract QueryExecutor which fully scans all Storables of a given type.
+ * QueryExecutor which fully scans all Storables of a given type.
*
* @author Brian S O'Neill
*/
-public abstract class FullScanQueryExecutor<S extends Storable> extends AbstractQueryExecutor<S> {
- private final Class<S> mType;
+public class FullScanQueryExecutor<S extends Storable> extends AbstractQueryExecutor<S> {
+ private final Support<S> mSupport;
/**
- * @param type type of Storable
- * @throws IllegalArgumentException if type is null
+ * @param support support for full scan
+ * @throws IllegalArgumentException if support is null
*/
- public FullScanQueryExecutor(Class<S> type) {
- if (type == null) {
+ public FullScanQueryExecutor(Support<S> support) {
+ if (support == null) {
throw new IllegalArgumentException();
}
- mType = type;
+ mSupport = support;
}
/**
* Returns an open filter.
*/
public Filter<S> getFilter() {
- return Filter.getOpenFilter(mType);
+ return Filter.getOpenFilter(mSupport.getStorableType());
}
public Cursor<S> fetch(FilterValues<S> values) throws FetchException {
- return fetch();
+ return mSupport.fetch();
}
/**
@@ -74,13 +74,17 @@ public abstract class FullScanQueryExecutor<S extends Storable> extends Abstract
{
indent(app, indentLevel);
app.append("full scan: ");
- app.append(mType.getName());
+ app.append(mSupport.getStorableType().getName());
newline(app);
return true;
}
- /**
- * Return a new Cursor instance.
- */
- protected abstract Cursor<S> fetch() throws FetchException;
+ public static interface Support<S extends Storable> {
+ Class<S> getStorableType();
+
+ /**
+ * Perform a full scan of all Storables.
+ */
+ Cursor<S> fetch() throws FetchException;
+ }
}
diff --git a/src/main/java/com/amazon/carbonado/qe/IndexedQueryAnalyzer.java b/src/main/java/com/amazon/carbonado/qe/IndexedQueryAnalyzer.java
index 594fcc1..9b46227 100644
--- a/src/main/java/com/amazon/carbonado/qe/IndexedQueryAnalyzer.java
+++ b/src/main/java/com/amazon/carbonado/qe/IndexedQueryAnalyzer.java
@@ -26,7 +26,11 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import com.amazon.carbonado.FetchException;
+import com.amazon.carbonado.RepositoryException;
import com.amazon.carbonado.Storable;
+import com.amazon.carbonado.Storage;
+import com.amazon.carbonado.SupportException;
import com.amazon.carbonado.filter.Filter;
import com.amazon.carbonado.filter.PropertyFilter;
@@ -50,23 +54,23 @@ import com.amazon.carbonado.info.StorableProperty;
* @see UnionQueryAnalyzer
*/
public class IndexedQueryAnalyzer<S extends Storable> {
- private final Class<S> mType;
- private final IndexProvider mIndexProvider;
+ final Class<S> mType;
+ final RepositoryAccess mRepoAccess;
// Growable cache which maps join properties to lists of usable foreign indexes.
private Map<ChainedProperty<S>, ForeignIndexes<S>> mForeignIndexCache;
/**
* @param type type of storable being queried
- * @param indexProvider
+ * @param access repository access for examing available indexes
* @throws IllegalArgumentException if type or indexProvider is null
*/
- public IndexedQueryAnalyzer(Class<S> type, IndexProvider indexProvider) {
- if (type == null || indexProvider == null) {
+ public IndexedQueryAnalyzer(Class<S> type, RepositoryAccess access) {
+ if (type == null || access == null) {
throw new IllegalArgumentException();
}
mType = type;
- mIndexProvider = indexProvider;
+ mRepoAccess = access;
}
public Class<S> getStorableType() {
@@ -81,8 +85,6 @@ public class IndexedQueryAnalyzer<S extends Storable> {
*/
public Result analyze(Filter<S> filter, OrderingList<S> ordering) {
if (!filter.isBound()) {
- // Strictly speaking, this is not required, but it detects the
- // mistake of not properly calling initialFilterValues.
throw new IllegalArgumentException("Filter must be bound");
}
@@ -92,7 +94,7 @@ public class IndexedQueryAnalyzer<S extends Storable> {
CompositeScore<S> bestScore = null;
StorableIndex<S> bestLocalIndex = null;
- Collection<StorableIndex<S>> localIndexes = mIndexProvider.indexesFor(mType);
+ Collection<StorableIndex<S>> localIndexes = indexesFor(getStorableType());
if (localIndexes != null) {
for (StorableIndex<S> index : localIndexes) {
CompositeScore<S> candidateScore =
@@ -177,7 +179,7 @@ public class IndexedQueryAnalyzer<S extends Storable> {
// All foreign indexes are available for use.
Class foreignType = chainedProp.getLastProperty().getType();
- Collection<StorableIndex<?>> indexes = mIndexProvider.indexesFor(foreignType);
+ Collection<StorableIndex<?>> indexes = indexesFor(foreignType);
foreignIndexes = new ForeignIndexes<S>(chainedProp, indexes);
}
@@ -211,7 +213,7 @@ public class IndexedQueryAnalyzer<S extends Storable> {
// Java generics are letting me down. I cannot use proper specification
// because compiler gets confused with all the wildcards.
- Collection indexes = mIndexProvider.indexesFor(filter.getStorableType());
+ Collection indexes = indexesFor(filter.getStorableType());
if (indexes != null) {
for (Object index : indexes) {
@@ -226,7 +228,7 @@ public class IndexedQueryAnalyzer<S extends Storable> {
}
private <F extends Storable> boolean simpleAnalyze(Filter<F> filter) {
- Collection<StorableIndex<F>> indexes = mIndexProvider.indexesFor(filter.getStorableType());
+ Collection<StorableIndex<F>> indexes = indexesFor(filter.getStorableType());
if (indexes != null) {
for (StorableIndex<F> index : indexes) {
@@ -240,6 +242,10 @@ public class IndexedQueryAnalyzer<S extends Storable> {
return false;
}
+ private <T extends Storable> Collection<StorableIndex<T>> indexesFor(Class<T> type) {
+ return mRepoAccess.storageAccessFor(type).getAllIndexes();
+ }
+
public class Result {
private final Filter<S> mFilter;
@@ -257,27 +263,24 @@ public class IndexedQueryAnalyzer<S extends Storable> {
StorableIndex<?> foreignIndex,
ChainedProperty<S> foreignProperty)
{
- mFilter = filter;
- mScore = score;
- mLocalIndex = localIndex;
- mForeignIndex = foreignIndex;
- mForeignProperty = foreignProperty;
- mRemainderFilter = score.getFilteringScore().getRemainderFilter();
- mRemainderOrdering = score.getOrderingScore().getRemainderOrdering();
+ this(filter, score, localIndex, foreignIndex, foreignProperty,
+ score.getFilteringScore().getRemainderFilter(),
+ score.getOrderingScore().getRemainderOrdering());
}
- // Called by mergeRemainder.
- private Result(Result result,
+ private Result(Filter<S> filter,
+ CompositeScore<S> score,
+ StorableIndex<S> localIndex,
+ StorableIndex<?> foreignIndex,
+ ChainedProperty<S> foreignProperty,
Filter<S> remainderFilter,
OrderingList<S> remainderOrdering)
{
- mFilter = result.mFilter == null ? remainderFilter
- : (remainderFilter == null ? result.mFilter : result.mFilter.or(remainderFilter));
-
- mScore = result.mScore;
- mLocalIndex = result.mLocalIndex;
- mForeignIndex = result.mForeignIndex;
- mForeignProperty = result.mForeignProperty;
+ mFilter = filter;
+ mScore = score;
+ mLocalIndex = localIndex;
+ mForeignIndex = foreignIndex;
+ mForeignProperty = foreignProperty;
mRemainderFilter = remainderFilter;
mRemainderOrdering = remainderOrdering;
}
@@ -387,10 +390,17 @@ public class IndexedQueryAnalyzer<S extends Storable> {
return this;
}
+ Filter<S> remainderFilter =
+ mergeFilters(getRemainderFilter(), other.getRemainderFilter());
+
+ OrderingList<S> remainderOrdering =
+ getRemainderOrdering().concat(other.getRemainderOrdering()).reduce();
+
+ Filter<S> filter = mergeFilters(getFilter(), remainderFilter);
+
return new Result
- (this,
- getCompositeScore().mergeRemainderFilter(other.getCompositeScore()),
- getCompositeScore().mergeRemainderOrdering(other.getCompositeScore()));
+ (filter, mScore, mLocalIndex, mForeignIndex, mForeignProperty,
+ remainderFilter, remainderOrdering);
}
/**
@@ -399,20 +409,114 @@ public class IndexedQueryAnalyzer<S extends Storable> {
* doesn't usually make sense to call this method.
*/
public Result mergeRemainderFilter(Filter<S> filter) {
- Filter<S> remainderFilter = getRemainderFilter();
- if (remainderFilter == null) {
- remainderFilter = filter;
- } else if (filter != null) {
- remainderFilter = remainderFilter.or(filter);
+ return setRemainderFilter(mergeFilters(getRemainderFilter(), filter));
+ }
+
+ private Filter<S> mergeFilters(Filter<S> a, Filter<S> b) {
+ if (a == null) {
+ return b;
}
- return setRemainderFilter(remainderFilter);
+ if (b == null) {
+ return a;
+ }
+ return a.or(b).reduce();
}
/**
* Returns a new result with the remainder filter replaced.
*/
- public Result setRemainderFilter(Filter<S> filter) {
- return new Result(this, filter, getRemainderOrdering());
+ public Result setRemainderFilter(Filter<S> remainderFilter) {
+ return new Result
+ (mFilter, mScore, mLocalIndex, mForeignIndex, mForeignProperty,
+ remainderFilter, mRemainderOrdering);
+ }
+
+ /**
+ * Returns a new result with the remainder ordering replaced.
+ */
+ public Result setRemainderOrdering(OrderingList<S> remainderOrdering) {
+ return new Result
+ (mFilter, mScore, mLocalIndex, mForeignIndex, mForeignProperty,
+ mRemainderFilter, remainderOrdering);
+ }
+
+ /**
+ * Creates a QueryExecutor based on this result.
+ */
+ public QueryExecutor<S> createExecutor()
+ throws SupportException, FetchException, RepositoryException
+ {
+ StorableIndex<S> localIndex = getLocalIndex();
+ StorageAccess<S> localAccess = mRepoAccess.storageAccessFor(getStorableType());
+
+ if (localIndex != null) {
+ Storage<S> delegate = localAccess.storageDelegate(localIndex);
+ if (delegate != null) {
+ return new DelegatedQueryExecutor<S>(delegate, getFilter(), getOrdering());
+ }
+ }
+
+ QueryExecutor<S> executor = baseExecutor(localAccess);
+
+ Filter<S> remainderFilter = getRemainderFilter();
+ if (remainderFilter != null) {
+ executor = new FilteredQueryExecutor<S>(executor, remainderFilter);
+ }
+
+ OrderingList<S> remainderOrdering = getRemainderOrdering();
+ if (remainderOrdering.size() > 0) {
+ executor = new SortedQueryExecutor<S>
+ (localAccess,
+ executor,
+ getCompositeScore().getOrderingScore().getHandledOrdering(),
+ remainderOrdering);
+ }
+
+ return executor;
+ }
+
+ private QueryExecutor<S> baseExecutor(StorageAccess<S> localAccess)
+ throws SupportException, FetchException, RepositoryException
+ {
+ if (!handlesAnything()) {
+ return new FullScanQueryExecutor<S>(localAccess);
+ }
+
+ StorableIndex<S> localIndex = getLocalIndex();
+
+ if (localIndex != null) {
+ return indexExecutor(localAccess, localIndex);
+ }
+
+ StorableIndex foreignIndex = getForeignIndex();
+ StorageAccess foreignAccess = mRepoAccess
+ .storageAccessFor(foreignIndex.getStorableType());
+
+ QueryExecutor foreignExecutor;
+ Storage delegate = foreignAccess.storageDelegate(foreignIndex);
+ if (delegate != null) {
+ foreignExecutor = new DelegatedQueryExecutor(delegate, getFilter(), getOrdering());
+ } else {
+ foreignExecutor = indexExecutor(foreignAccess, foreignIndex);
+ }
+
+ return new JoinedQueryExecutor
+ (mRepoAccess.getRootRepository(), getForeignProperty(), foreignExecutor);
+ }
+
+ private <T extends Storable> QueryExecutor<T> indexExecutor(StorageAccess<T> access,
+ StorableIndex<T> index)
+ {
+ CompositeScore score = getCompositeScore();
+ FilteringScore fScore = score.getFilteringScore();
+
+ if (!fScore.hasAnyMatches()) {
+ return new FullScanIndexedQueryExecutor<T>(access, index);
+ }
+ if (fScore.isKeyMatch()) {
+ return new KeyQueryExecutor<T>(access, index, fScore);
+ }
+ return new IndexedQueryExecutor<T>(access, index, score);
}
public String toString() {
diff --git a/src/main/java/com/amazon/carbonado/qe/IndexedQueryExecutor.java b/src/main/java/com/amazon/carbonado/qe/IndexedQueryExecutor.java
index a306db1..f8d2b23 100644
--- a/src/main/java/com/amazon/carbonado/qe/IndexedQueryExecutor.java
+++ b/src/main/java/com/amazon/carbonado/qe/IndexedQueryExecutor.java
@@ -36,11 +36,11 @@ import com.amazon.carbonado.info.StorableIndex;
import com.amazon.carbonado.info.OrderedProperty;
/**
- * Abstract QueryExecutor which utilizes an index.
+ * QueryExecutor which utilizes an index.
*
* @author Brian S O'Neill
*/
-public abstract class IndexedQueryExecutor<S extends Storable> extends AbstractQueryExecutor<S> {
+public class IndexedQueryExecutor<S extends Storable> extends AbstractQueryExecutor<S> {
/**
* 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
@@ -50,6 +50,7 @@ public abstract class IndexedQueryExecutor<S extends Storable> extends AbstractQ
return a == null ? (b == null ? 0 : -1) : (b == null ? 1 : ((Comparable) a).compareTo(b));
}
+ private final Support<S> mSupport;
private final StorableIndex<S> mIndex;
private final Filter<S> mIdentityFilter;
private final List<PropertyFilter<S>> mExclusiveRangeStartFilters;
@@ -62,13 +63,17 @@ public abstract class IndexedQueryExecutor<S extends Storable> extends AbstractQ
/**
* @param index index to use, which may be a primary key index
* @param score score determines how best to utilize the index
- * @throws IllegalArgumentException if index or score is null
+ * @throws IllegalArgumentException if any parameter is null
*/
- public IndexedQueryExecutor(StorableIndex<S> index, CompositeScore<S> score) {
- if (index == null || score == null) {
+ public IndexedQueryExecutor(Support<S> support,
+ StorableIndex<S> index,
+ CompositeScore<S> score)
+ {
+ if (support == null || index == null || score == null) {
throw new IllegalArgumentException();
}
+ mSupport = support;
mIndex = index;
FilteringScore<S> fScore = score.getFilteringScore();
@@ -147,11 +152,11 @@ public abstract class IndexedQueryExecutor<S extends Storable> extends AbstractQ
}
}
- return fetch(mIndex, identityValues,
- rangeStartBoundary, rangeStartValue,
- rangeEndBoundary, rangeEndValue,
- mReverseRange,
- mReverseOrder);
+ return mSupport.fetch(mIndex, identityValues,
+ rangeStartBoundary, rangeStartValue,
+ rangeEndBoundary, rangeEndValue,
+ mReverseRange,
+ mReverseOrder);
}
public Filter<S> getFilter() {
@@ -235,32 +240,35 @@ public abstract class IndexedQueryExecutor<S extends Storable> extends AbstractQ
return true;
}
- /**
- * 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 a primary key index
- * @param identityValues 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 whose
- * natural order is descending. Only the code that opens the physical
- * cursor should examine this parameter. If true, then the range start and
- * end parameter pairs need to be swapped.
- * @param reverseOrder when true, iteration should be reversed from its
- * natural order
- */
- protected abstract Cursor<S> fetch(StorableIndex<S> index,
- Object[] identityValues,
- BoundaryType rangeStartBoundary,
- Object rangeStartValue,
- BoundaryType rangeEndBoundary,
- Object rangeEndValue,
- boolean reverseRange,
- boolean reverseOrder)
- throws FetchException;
+ public static interface Support<S extends Storable> {
+ /**
+ * Perform an index scan of a subset of Storables referenced by an
+ * index. The identity values are aligned with the index properties at
+ * property 0. An optional range start or range end aligns with the index
+ * property following the last of the identity values.
+ *
+ *
+ * @param index index to open, which may be a primary key index
+ * @param identityValues 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 whose
+ * natural order is descending. Only the code that opens the physical
+ * cursor should examine this parameter. If true, then the range start and
+ * end parameter pairs need to be swapped.
+ * @param reverseOrder when true, iteration should be reversed from its
+ * natural order
+ */
+ Cursor<S> fetch(StorableIndex<S> index,
+ Object[] identityValues,
+ BoundaryType rangeStartBoundary,
+ Object rangeStartValue,
+ BoundaryType rangeEndBoundary,
+ Object rangeEndValue,
+ boolean reverseRange,
+ boolean reverseOrder)
+ throws FetchException;
+ }
}
diff --git a/src/main/java/com/amazon/carbonado/qe/IterableQueryExecutor.java b/src/main/java/com/amazon/carbonado/qe/IterableQueryExecutor.java
index b80066e..de0864f 100644
--- a/src/main/java/com/amazon/carbonado/qe/IterableQueryExecutor.java
+++ b/src/main/java/com/amazon/carbonado/qe/IterableQueryExecutor.java
@@ -18,11 +18,16 @@
package com.amazon.carbonado.qe;
+import java.io.IOException;
+
import java.util.concurrent.locks.Lock;
import com.amazon.carbonado.Cursor;
import com.amazon.carbonado.Storable;
+import com.amazon.carbonado.filter.Filter;
+import com.amazon.carbonado.filter.FilterValues;
+
import com.amazon.carbonado.cursor.IteratorCursor;
/**
@@ -31,7 +36,8 @@ import com.amazon.carbonado.cursor.IteratorCursor;
* @author Brian S O'Neill
* @see IteratorCursor
*/
-public class IterableQueryExecutor<S extends Storable> extends FullScanQueryExecutor<S> {
+public class IterableQueryExecutor<S extends Storable> extends AbstractQueryExecutor<S> {
+ private final Class<S> mType;
private final Iterable<S> mIterable;
private final Lock mLock;
@@ -51,12 +57,39 @@ public class IterableQueryExecutor<S extends Storable> extends FullScanQueryExec
* @throws IllegalArgumentException if type is null
*/
public IterableQueryExecutor(Class<S> type, Iterable<S> iterable, Lock lock) {
- super(type);
+ if (type == null) {
+ throw new IllegalArgumentException();
+ }
+ mType = type;
mIterable = iterable;
mLock = lock;
}
- protected Cursor<S> fetch() {
+ /**
+ * Returns an open filter.
+ */
+ public Filter<S> getFilter() {
+ return Filter.getOpenFilter(mType);
+ }
+
+ public Cursor<S> fetch(FilterValues<S> values) {
return new IteratorCursor<S>(mIterable, mLock);
}
+
+ /**
+ * Returns an empty list.
+ */
+ public OrderingList<S> getOrdering() {
+ return OrderingList.emptyList();
+ }
+
+ public boolean printPlan(Appendable app, int indentLevel, FilterValues<S> values)
+ throws IOException
+ {
+ indent(app, indentLevel);
+ app.append("iterable: ");
+ app.append(mType.getName());
+ newline(app);
+ return true;
+ }
}
diff --git a/src/main/java/com/amazon/carbonado/qe/KeyQueryExecutor.java b/src/main/java/com/amazon/carbonado/qe/KeyQueryExecutor.java
index 8209be7..acde1a8 100644
--- a/src/main/java/com/amazon/carbonado/qe/KeyQueryExecutor.java
+++ b/src/main/java/com/amazon/carbonado/qe/KeyQueryExecutor.java
@@ -34,12 +34,13 @@ import com.amazon.carbonado.info.OrderedProperty;
import com.amazon.carbonado.info.StorableIndex;
/**
- * Abstract QueryExecutor which has a fully specified key, and so cursors
- * produce at most one result.
+ * QueryExecutor which has a fully specified key, and so cursors produce at
+ * most one result.
*
* @author Brian S O'Neill
*/
-public abstract class KeyQueryExecutor<S extends Storable> extends AbstractQueryExecutor<S> {
+public class KeyQueryExecutor<S extends Storable> extends AbstractQueryExecutor<S> {
+ private final Support<S> mSupport;
private final StorableIndex<S> mIndex;
private final Filter<S> mKeyFilter;
@@ -49,18 +50,18 @@ public abstract class KeyQueryExecutor<S extends Storable> extends AbstractQuery
* @throws IllegalArgumentException if any parameter is null or if index is
* not unique or if score is not a key match
*/
- public KeyQueryExecutor(StorableIndex<S> index, FilteringScore<S> score) {
- if (index == null || score == null) {
+ public KeyQueryExecutor(Support<S> support, StorableIndex<S> index, FilteringScore<S> score) {
+ if (support == null || index == null || score == null) {
throw new IllegalArgumentException();
}
if (!index.isUnique() || !score.isKeyMatch()) {
throw new IllegalArgumentException();
}
+ mSupport = support;
mIndex = index;
mKeyFilter = score.getIdentityFilter();
}
- @Override
public Class<S> getStorableType() {
// Storable type of filter may differ if index is used along with a
// join. The type of the index is the correct storable type.
@@ -68,7 +69,7 @@ public abstract class KeyQueryExecutor<S extends Storable> extends AbstractQuery
}
public Cursor<S> fetch(FilterValues<S> values) throws FetchException {
- return fetch(mIndex, values.getValuesFor(mKeyFilter));
+ return mSupport.fetch(mIndex, values.getValuesFor(mKeyFilter));
}
public Filter<S> getFilter() {
@@ -100,12 +101,16 @@ public abstract class KeyQueryExecutor<S extends Storable> extends AbstractQuery
return true;
}
- /**
- * Return a new Cursor instance referenced by the given index.
- *
- * @param index index to open, which may be a primary key index
- * @param keyValues list of exactly matching values to apply to index
- */
- protected abstract Cursor<S> fetch(StorableIndex<S> index, Object[] keyValues)
- throws FetchException;
+ public static interface Support<S extends Storable> {
+ /**
+ * Select at most one Storable referenced by an index. The identity
+ * values fully specify all elements of the index, and the index is
+ * unique.
+ *
+ * @param index index to open, which may be a primary key index
+ * @param identityValues of exactly matching values to apply to index
+ */
+ Cursor<S> fetch(StorableIndex<S> index, Object[] identityValues)
+ throws FetchException;
+ }
}
diff --git a/src/main/java/com/amazon/carbonado/qe/OrderingList.java b/src/main/java/com/amazon/carbonado/qe/OrderingList.java
index b32f430..08a39f5 100644
--- a/src/main/java/com/amazon/carbonado/qe/OrderingList.java
+++ b/src/main/java/com/amazon/carbonado/qe/OrderingList.java
@@ -20,13 +20,16 @@ package com.amazon.carbonado.qe;
import java.util.AbstractList;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import org.cojen.util.SoftValuedHashMap;
import com.amazon.carbonado.Storable;
+import com.amazon.carbonado.info.ChainedProperty;
import com.amazon.carbonado.info.OrderedProperty;
import com.amazon.carbonado.info.StorableIntrospector;
@@ -180,6 +183,28 @@ public class OrderingList<S extends Storable> extends AbstractList<OrderedProper
}
/**
+ * Eliminates redundant ordering properties.
+ */
+ public OrderingList<S> reduce() {
+ if (size() == 0) {
+ return this;
+ }
+
+ Set<ChainedProperty<S>> seen = new HashSet<ChainedProperty<S>>();
+ OrderingList<S> newList = emptyList();
+
+ for (OrderedProperty<S> property : this) {
+ ChainedProperty<S> chained = property.getChainedProperty();
+ if (!seen.contains(chained)) {
+ newList = newList.concat(property);
+ seen.add(chained);
+ }
+ }
+
+ return newList;
+ }
+
+ /**
* Returns this list with all orderings in reverse.
*/
public OrderingList<S> reverseDirections() {
diff --git a/src/main/java/com/amazon/carbonado/qe/PropertyFilterList.java b/src/main/java/com/amazon/carbonado/qe/PropertyFilterList.java
index 5842ef2..f17a2df 100644
--- a/src/main/java/com/amazon/carbonado/qe/PropertyFilterList.java
+++ b/src/main/java/com/amazon/carbonado/qe/PropertyFilterList.java
@@ -83,7 +83,7 @@ class PropertyFilterList {
}
}, null);
- Collections.sort(list, new PropertyFilterComparator<S>());
+ Collections.sort(list, new PFComparator<S>());
((ArrayList) list).trimToSize();
list = Collections.unmodifiableList(list);
@@ -96,7 +96,7 @@ class PropertyFilterList {
return list;
}
- private static class PropertyFilterComparator<S extends Storable>
+ private static class PFComparator<S extends Storable>
implements Comparator<PropertyFilter<S>>
{
public int compare(PropertyFilter<S> a, PropertyFilter<S> b) {
diff --git a/src/main/java/com/amazon/carbonado/qe/QueryEngine.java b/src/main/java/com/amazon/carbonado/qe/QueryEngine.java
new file mode 100644
index 0000000..1362045
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/qe/QueryEngine.java
@@ -0,0 +1,67 @@
+/*
+ * 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.qe;
+
+import com.amazon.carbonado.IsolationLevel;
+import com.amazon.carbonado.Storable;
+import com.amazon.carbonado.Transaction;
+
+import com.amazon.carbonado.filter.FilterValues;
+
+/**
+ * Complete rule-based query engine implementation.
+ *
+ * @author Brian S O'Neill
+ */
+public class QueryEngine<S extends Storable> extends StandardQueryFactory<S> {
+ final RepositoryAccess mRepoAccess;
+ final QueryExecutorFactory<S> mExecutorFactory;
+
+ public QueryEngine(Class<S> type, RepositoryAccess access) {
+ super(type);
+ mRepoAccess = access;
+ mExecutorFactory = new QueryExecutorCache<S>(new UnionQueryAnalyzer<S>(type, access));
+ }
+
+ protected StandardQuery<S> createQuery(FilterValues<S> values, OrderingList<S> ordering) {
+ return new Query(values, ordering);
+ }
+
+ private class Query extends StandardQuery<S> {
+ Query(FilterValues<S> values, OrderingList<S> ordering) {
+ super(values, ordering);
+ }
+
+ protected Transaction enterTransaction(IsolationLevel level) {
+ return mRepoAccess.getRootRepository().enterTransaction(level);
+ }
+
+ protected QueryFactory<S> queryFactory() {
+ return QueryEngine.this;
+ }
+
+ protected QueryExecutorFactory<S> executorFactory() {
+ return mExecutorFactory;
+ }
+
+ protected StandardQuery<S> newInstance(FilterValues<S> values, OrderingList<S> ordering) {
+ return new Query(values, ordering);
+ }
+ }
+}
diff --git a/src/main/java/com/amazon/carbonado/qe/QueryExecutor.java b/src/main/java/com/amazon/carbonado/qe/QueryExecutor.java
index c81077f..6348fb9 100644
--- a/src/main/java/com/amazon/carbonado/qe/QueryExecutor.java
+++ b/src/main/java/com/amazon/carbonado/qe/QueryExecutor.java
@@ -36,6 +36,7 @@ import com.amazon.carbonado.info.OrderedProperty;
* together forming a <i>query plan</i>.
*
* @author Brian S O'Neill
+ * @see QueryExecutorFactory
*/
public interface QueryExecutor<S extends Storable> {
/**
diff --git a/src/main/java/com/amazon/carbonado/qe/QueryExecutorCache.java b/src/main/java/com/amazon/carbonado/qe/QueryExecutorCache.java
new file mode 100644
index 0000000..da944fa
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/qe/QueryExecutorCache.java
@@ -0,0 +1,86 @@
+/*
+ * 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.qe;
+
+import java.util.Map;
+
+import org.cojen.util.SoftValuedHashMap;
+import org.cojen.util.WeakIdentityMap;
+
+import com.amazon.carbonado.RepositoryException;
+import com.amazon.carbonado.Storable;
+
+import com.amazon.carbonado.filter.Filter;
+
+import com.amazon.carbonado.info.OrderedProperty;
+
+/**
+ * QueryExecutors should be cached since expensive analysis is often required
+ * to build them.
+ *
+ * @author Brian S O'Neill
+ */
+public class QueryExecutorCache<S extends Storable> implements QueryExecutorFactory<S> {
+ private final QueryExecutorFactory<S> mFactory;
+
+ // Maps filters to maps which map ordering lists to executors.
+ private final Map<Filter<S>, Map<OrderingList<S>, QueryExecutor<S>>> mFilterToExecutor;
+
+ protected QueryExecutorCache(QueryExecutorFactory<S> factory) {
+ if (factory == null) {
+ throw new IllegalArgumentException();
+ }
+ mFactory = factory;
+ mFilterToExecutor = new WeakIdentityMap(7);
+ }
+
+ public Class<S> getStorableType() {
+ return mFactory.getStorableType();
+ }
+
+ /**
+ * Returns an executor from the cache.
+ *
+ * @param filter optional filter
+ * @param ordering optional order-by properties
+ */
+ public QueryExecutor<S> executor(Filter<S> filter, OrderingList<S> ordering)
+ throws RepositoryException
+ {
+ Map<OrderingList<S>, QueryExecutor<S>> map;
+ synchronized (mFilterToExecutor) {
+ map = mFilterToExecutor.get(filter);
+ if (map == null) {
+ map = new SoftValuedHashMap(7);
+ mFilterToExecutor.put(filter, map);
+ }
+ }
+
+ QueryExecutor<S> executor;
+ synchronized (map) {
+ executor = map.get(ordering);
+ if (executor == null) {
+ executor = mFactory.executor(filter, ordering);
+ map.put(ordering, executor);
+ }
+ }
+
+ return executor;
+ }
+}
diff --git a/src/main/java/com/amazon/carbonado/qe/IndexProvider.java b/src/main/java/com/amazon/carbonado/qe/QueryExecutorFactory.java
index 69277e4..1b1e680 100644
--- a/src/main/java/com/amazon/carbonado/qe/IndexProvider.java
+++ b/src/main/java/com/amazon/carbonado/qe/QueryExecutorFactory.java
@@ -18,20 +18,27 @@
package com.amazon.carbonado.qe;
-import java.util.Collection;
-
+import com.amazon.carbonado.RepositoryException;
import com.amazon.carbonado.Storable;
-import com.amazon.carbonado.info.StorableIndex;
+import com.amazon.carbonado.filter.Filter;
+
+import com.amazon.carbonado.info.OrderedProperty;
/**
- * An index provider is typically a repository implementation.
+ * Produces {@link QueryExecutor} instances from a query specification.
*
* @author Brian S O'Neill
*/
-public interface IndexProvider {
+public interface QueryExecutorFactory<S extends Storable> {
+ Class<S> getStorableType();
+
/**
- * Returns all the available indexes for the given type.
+ * Returns an executor that handles the given query specification.
+ *
+ * @param filter optional filter
+ * @param ordering optional order-by properties
*/
- <S extends Storable> Collection<StorableIndex<S>> indexesFor(Class<S> type);
+ QueryExecutor<S> executor(Filter<S> filter, OrderingList<S> ordering)
+ throws RepositoryException;
}
diff --git a/src/main/java/com/amazon/carbonado/qe/ArraySortedQueryExecutor.java b/src/main/java/com/amazon/carbonado/qe/QueryFactory.java
index 85b6c2e..738d18a 100644
--- a/src/main/java/com/amazon/carbonado/qe/ArraySortedQueryExecutor.java
+++ b/src/main/java/com/amazon/carbonado/qe/QueryFactory.java
@@ -18,35 +18,27 @@
package com.amazon.carbonado.qe;
-import java.util.List;
-
+import com.amazon.carbonado.FetchException;
+import com.amazon.carbonado.Query;
import com.amazon.carbonado.Storable;
-import com.amazon.carbonado.cursor.ArraySortBuffer;
-import com.amazon.carbonado.cursor.SortBuffer;
+import com.amazon.carbonado.filter.FilterValues;
import com.amazon.carbonado.info.OrderedProperty;
/**
- * QueryExecutor which wraps another and sorts the results within an array.
+ * Produces {@link Query} instances from a query specification.
*
* @author Brian S O'Neill
- * @see ArraySortBuffer
*/
-public class ArraySortedQueryExecutor<S extends Storable> extends SortedQueryExecutor<S> {
+public interface QueryFactory<S extends Storable> {
+ Class<S> getStorableType();
+
/**
- * @param executor executor to wrap
- * @throws IllegalArgumentException if executor is null or if remainder
- * ordering is empty
+ * Returns a query that handles the given query specification.
+ *
+ * @param values optional values object, defaults to open filter if null
+ * @param ordering optional order-by properties
*/
- public ArraySortedQueryExecutor(QueryExecutor<S> executor,
- OrderingList<S> handledOrdering,
- OrderingList<S> remainderOrdering)
- {
- super(executor, handledOrdering, remainderOrdering);
- }
-
- protected SortBuffer<S> createSortBuffer() {
- return new ArraySortBuffer<S>();
- }
+ Query<S> query(FilterValues<S> values, OrderingList<S> ordering) throws FetchException;
}
diff --git a/src/main/java/com/amazon/carbonado/qe/RepositoryAccess.java b/src/main/java/com/amazon/carbonado/qe/RepositoryAccess.java
new file mode 100644
index 0000000..6e3c3de
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/qe/RepositoryAccess.java
@@ -0,0 +1,44 @@
+/*
+ * 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.qe;
+
+import com.amazon.carbonado.MalformedTypeException;
+import com.amazon.carbonado.Repository;
+import com.amazon.carbonado.Storable;
+import com.amazon.carbonado.SupportException;
+
+/**
+ * Provides internal access to a {@link Repository}, necessary for query
+ * execution.
+ *
+ * @author Brian S O'Neill
+ */
+public interface RepositoryAccess {
+ Repository getRootRepository();
+
+ /**
+ * Returns a StorageAccess instance for the given user defined Storable
+ * class or interface.
+ *
+ * @return specific type of StorageAccess instance
+ * @throws IllegalArgumentException if specified type is null
+ * @throws MalformedTypeException if specified type is not suitable
+ */
+ <S extends Storable> StorageAccess<S> storageAccessFor(Class<S> type);
+}
diff --git a/src/main/java/com/amazon/carbonado/qe/SortedQueryExecutor.java b/src/main/java/com/amazon/carbonado/qe/SortedQueryExecutor.java
index 05474ed..7b12e43 100644
--- a/src/main/java/com/amazon/carbonado/qe/SortedQueryExecutor.java
+++ b/src/main/java/com/amazon/carbonado/qe/SortedQueryExecutor.java
@@ -26,6 +26,7 @@ import com.amazon.carbonado.Cursor;
import com.amazon.carbonado.FetchException;
import com.amazon.carbonado.Storable;
+import com.amazon.carbonado.cursor.ArraySortBuffer;
import com.amazon.carbonado.cursor.SortBuffer;
import com.amazon.carbonado.cursor.SortedCursor;
@@ -35,12 +36,13 @@ import com.amazon.carbonado.filter.FilterValues;
import com.amazon.carbonado.info.OrderedProperty;
/**
- * Abstract QueryExecutor which wraps another and sorts the results.
+ * QueryExecutor which wraps another and sorts the results.
*
* @author Brian S O'Neill
* @see SortedCursor
*/
-public abstract class SortedQueryExecutor<S extends Storable> extends AbstractQueryExecutor<S> {
+public class SortedQueryExecutor<S extends Storable> extends AbstractQueryExecutor<S> {
+ private final Support<S> mSupport;
private final QueryExecutor<S> mExecutor;
private final Comparator<S> mHandledComparator;
@@ -50,19 +52,22 @@ public abstract class SortedQueryExecutor<S extends Storable> extends AbstractQu
private final OrderingList<S> mRemainderOrdering;
/**
+ * @param support optional support to control sort buffer; if null, array is used
* @param executor executor to wrap
* @param handledOrdering optional handled ordering
* @param remainderOrdering required remainder ordering
- * @throws IllegalArgumentException if executor is null or if remainder
- * ordering is empty
+ * @throws IllegalArgumentException if executor is null or if
+ * remainder ordering is empty
*/
- public SortedQueryExecutor(QueryExecutor<S> executor,
+ public SortedQueryExecutor(Support<S> support,
+ QueryExecutor<S> executor,
OrderingList<S> handledOrdering,
OrderingList<S> remainderOrdering)
{
if (executor == null) {
throw new IllegalArgumentException();
}
+ mSupport = support;
mExecutor = executor;
if (handledOrdering != null && handledOrdering.size() == 0) {
@@ -90,7 +95,8 @@ public abstract class SortedQueryExecutor<S extends Storable> extends AbstractQu
public Cursor<S> fetch(FilterValues<S> values) throws FetchException {
Cursor<S> cursor = mExecutor.fetch(values);
- SortBuffer<S> buffer = createSortBuffer();
+ SortBuffer<S> buffer =
+ mSupport == null ? new ArraySortBuffer<S>() : mSupport.createSortBuffer();
return new SortedCursor<S>(cursor, buffer, mHandledComparator, mFinisherComparator);
}
@@ -122,8 +128,10 @@ public abstract class SortedQueryExecutor<S extends Storable> extends AbstractQu
return true;
}
- /**
- * Implementation must return an empty buffer for sorting.
- */
- protected abstract SortBuffer<S> createSortBuffer();
+ public static interface Support<S extends Storable> {
+ /**
+ * Implementation must return an empty buffer for sorting.
+ */
+ SortBuffer<S> createSortBuffer();
+ }
}
diff --git a/src/main/java/com/amazon/carbonado/qe/StandardQuery.java b/src/main/java/com/amazon/carbonado/qe/StandardQuery.java
index 7304828..bb1dcc3 100644
--- a/src/main/java/com/amazon/carbonado/qe/StandardQuery.java
+++ b/src/main/java/com/amazon/carbonado/qe/StandardQuery.java
@@ -27,6 +27,7 @@ import com.amazon.carbonado.FetchException;
import com.amazon.carbonado.IsolationLevel;
import com.amazon.carbonado.PersistException;
import com.amazon.carbonado.PersistMultipleException;
+import com.amazon.carbonado.RepositoryException;
import com.amazon.carbonado.Storage;
import com.amazon.carbonado.Storable;
import com.amazon.carbonado.Transaction;
@@ -60,13 +61,6 @@ public abstract class StandardQuery<S extends Storable> extends AbstractQuery<S>
/**
* @param values optional values object, defaults to open filter if null
- */
- protected StandardQuery(FilterValues<S> values) {
- this(values, null);
- }
-
- /**
- * @param values optional values object, defaults to open filter if null
* @param ordering optional order-by properties
*/
protected StandardQuery(FilterValues<S> values, OrderingList<S> ordering) {
@@ -78,7 +72,7 @@ public abstract class StandardQuery<S extends Storable> extends AbstractQuery<S>
}
public Class<S> getStorableType() {
- return getRootStorage().getStorableType();
+ return queryFactory().getStorableType();
}
public Filter<S> getFilter() {
@@ -141,47 +135,50 @@ public abstract class StandardQuery<S extends Storable> extends AbstractQuery<S>
}
public Query<S> and(Filter<S> filter) throws FetchException {
- FilterValues<S> values = mValues;
- Query<S> newQuery;
- if (values == null) {
- newQuery = getRootStorage().query(filter);
+ FilterValues<S> newValues;
+ if (mValues == null) {
+ newValues = filter.initialFilterValues();
} else {
- newQuery = getRootStorage().query(values.getFilter().and(filter));
- newQuery = newQuery.withValues(values.getValues());
+ newValues = mValues.getFilter().and(filter)
+ .initialFilterValues().withValues(mValues.getValues());
}
- return mOrdering.size() == 0 ? newQuery : newQuery.orderBy(mOrdering.asStringArray());
+ return createQuery(newValues, mOrdering);
}
public Query<S> or(Filter<S> filter) throws FetchException {
- FilterValues<S> values = mValues;
- if (values == null) {
+ if (mValues == null) {
throw new IllegalStateException("Query is already guaranteed to fetch everything");
}
- Query<S> newQuery = getRootStorage().query(values.getFilter().or(filter));
- newQuery = newQuery.withValues(values.getValues());
- return mOrdering.size() == 0 ? newQuery : newQuery.orderBy(mOrdering.asStringArray());
+ FilterValues<S> newValues = mValues.getFilter().or(filter)
+ .initialFilterValues().withValues(mValues.getValues());
+ return createQuery(newValues, mOrdering);
}
public Query<S> not() throws FetchException {
- FilterValues<S> values = mValues;
- if (values == null) {
- return new EmptyQuery<S>(getRootStorage(), mOrdering);
+ if (mValues == null) {
+ return new EmptyQuery<S>(queryFactory(), mOrdering);
}
- Query<S> newQuery = getRootStorage().query(values.getFilter().not());
- newQuery = newQuery.withValues(values.getSuppliedValues());
- return mOrdering.size() == 0 ? newQuery : newQuery.orderBy(mOrdering.asStringArray());
+ // FIXME: Just like with DelegatedQueryExecutor, need a better way of
+ // transfering values.
+ FilterValues<S> newValues = mValues.getFilter().not()
+ .initialFilterValues().withValues(mValues.getSuppliedValues());
+ return createQuery(newValues, mOrdering);
}
public Query<S> orderBy(String property) throws FetchException {
- return newInstance(mValues, OrderingList.get(getStorableType(), property));
+ return createQuery(mValues, OrderingList.get(getStorableType(), property));
}
public Query<S> orderBy(String... properties) throws FetchException {
- return newInstance(mValues, OrderingList.get(getStorableType(), properties));
+ return createQuery(mValues, OrderingList.get(getStorableType(), properties));
}
public Cursor<S> fetch() throws FetchException {
- return executor().fetch(mValues);
+ try {
+ return executor().fetch(mValues);
+ } catch (RepositoryException e) {
+ throw e.toFetchException();
+ }
}
public Cursor<S> fetchAfter(S start) throws FetchException {
@@ -216,19 +213,19 @@ public abstract class StandardQuery<S extends Storable> extends AbstractQuery<S>
lastSubFilter = lastSubFilter.and(propertyName, RelOp.EQ);
}
- Query<S> newQuery = this.and(orderFilter);
+ Query<S> query = this.and(orderFilter);
for (int i=0; i<values.length; i++) {
for (int j=0; j<=i; j++) {
- newQuery = newQuery.with(values[j]);
+ query = query.with(values[j]);
}
}
- return newQuery.fetch();
+ return query.fetch();
}
public boolean tryDeleteOne() throws PersistException {
- Transaction txn = enterTransactionForDelete(IsolationLevel.READ_COMMITTED);
+ Transaction txn = enterTransaction(IsolationLevel.READ_COMMITTED);
try {
Cursor<S> cursor = fetch();
boolean result;
@@ -259,7 +256,7 @@ public abstract class StandardQuery<S extends Storable> extends AbstractQuery<S>
}
public void deleteAll() throws PersistException {
- Transaction txn = enterTransactionForDelete(IsolationLevel.READ_COMMITTED);
+ Transaction txn = enterTransaction(IsolationLevel.READ_COMMITTED);
try {
Cursor<S> cursor = fetch();
try {
@@ -282,23 +279,37 @@ public abstract class StandardQuery<S extends Storable> extends AbstractQuery<S>
}
public long count() throws FetchException {
- return executor().count(mValues);
+ try {
+ return executor().count(mValues);
+ } catch (RepositoryException e) {
+ throw e.toFetchException();
+ }
}
public boolean printNative(Appendable app, int indentLevel) throws IOException {
- return executor().printNative(app, indentLevel, mValues);
+ try {
+ return executor().printNative(app, indentLevel, mValues);
+ } catch (RepositoryException e) {
+ return false;
+ }
}
public boolean printPlan(Appendable app, int indentLevel) throws IOException {
- return executor().printPlan(app, indentLevel, mValues);
+ try {
+ return executor().printPlan(app, indentLevel, mValues);
+ } catch (RepositoryException e) {
+ return false;
+ }
}
@Override
public int hashCode() {
- int hash = getRootStorage().hashCode() * 31;
+ int hash = queryFactory().hashCode();
+ hash = hash * 31 + executorFactory().hashCode();
if (mValues != null) {
- hash += mValues.hashCode();
+ hash = hash * 31 + mValues.hashCode();
}
+ hash = hash * 31 + mOrdering.hashCode();
return hash;
}
@@ -309,8 +320,10 @@ public abstract class StandardQuery<S extends Storable> extends AbstractQuery<S>
}
if (obj instanceof StandardQuery) {
StandardQuery<?> other = (StandardQuery<?>) obj;
- return getRootStorage().equals(other.getRootStorage()) &&
- (mValues == null ? (other.mValues == null) : (mValues.equals(other.mValues)));
+ return queryFactory().equals(other.queryFactory())
+ && executorFactory().equals(other.executorFactory())
+ && (mValues == null ? (other.mValues == null) : (mValues.equals(other.mValues)))
+ && mOrdering.equals(other.mOrdering);
}
return false;
}
@@ -351,18 +364,32 @@ public abstract class StandardQuery<S extends Storable> extends AbstractQuery<S>
}
/**
- * Clears any cached reference to a query executor. The next time this
- * Query is used, it will get an executor from getExecutor and cache a
- * reference to it.
+ * Ensures that a cached query executor reference is available. If not, the
+ * query executor factory is called and the executor is cached.
*/
- protected void clearExecutorReference() {
- mExecutor = null;
+ protected void setExecutorReference() throws RepositoryException {
+ executor();
}
/**
- * Return the root Storage object that the query is operating on.
+ * Resets any cached reference to a query executor. If a reference is
+ * available, it is replaced, but a clear reference is not set.
*/
- protected abstract Storage<S> getRootStorage();
+ protected void resetExecutorReference() throws RepositoryException {
+ if (mExecutor != null) {
+ Filter<S> filter = mValues == null ? null : mValues.getFilter();
+ mExecutor = executorFactory().executor(filter, mOrdering);
+ }
+ }
+
+ /**
+ * Clears any cached reference to a query executor. The next time this
+ * Query is used, it will get an executor from the query executor factory
+ * and cache a reference to it.
+ */
+ protected void clearExecutorReference() {
+ mExecutor = null;
+ }
/**
* Enter a transaction as needed by the standard delete operation, or null
@@ -370,16 +397,17 @@ public abstract class StandardQuery<S extends Storable> extends AbstractQuery<S>
*
* @param level minimum desired isolation level
*/
- protected abstract Transaction enterTransactionForDelete(IsolationLevel level);
+ protected abstract Transaction enterTransaction(IsolationLevel level);
/**
- * Return a new or cached query executor.
- *
- * @param values optional values object, defaults to open filter if null
- * @param orderings order-by properties, never null
+ * Return a QueryFactory which is used to form new queries from this one.
*/
- protected abstract QueryExecutor<S> getExecutor(FilterValues<S> values,
- OrderingList<S> orderings);
+ protected abstract QueryFactory<S> queryFactory();
+
+ /**
+ * Return a QueryExecutorFactory which is used to get an executor.
+ */
+ protected abstract QueryExecutorFactory<S> executorFactory();
/**
* Return a new or cached instance of StandardQuery implementation, using
@@ -396,10 +424,21 @@ public abstract class StandardQuery<S extends Storable> extends AbstractQuery<S>
return newInstance(values, mOrdering);
}
- private QueryExecutor<S> executor() {
+ private Query<S> createQuery(FilterValues<S> values) throws FetchException {
+ return queryFactory().query(values, mOrdering);
+ }
+
+ private Query<S> createQuery(FilterValues<S> values, OrderingList<S> ordering)
+ throws FetchException
+ {
+ return queryFactory().query(values, ordering);
+ }
+
+ private QueryExecutor<S> executor() throws RepositoryException {
QueryExecutor<S> executor = mExecutor;
if (executor == null) {
- mExecutor = executor = getExecutor(mValues, mOrdering);
+ Filter<S> filter = mValues == null ? null : mValues.getFilter();
+ mExecutor = executor = executorFactory().executor(filter, mOrdering);
}
return executor;
}
diff --git a/src/main/java/com/amazon/carbonado/qe/StandardQueryFactory.java b/src/main/java/com/amazon/carbonado/qe/StandardQueryFactory.java
new file mode 100644
index 0000000..8b9a3a9
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/qe/StandardQueryFactory.java
@@ -0,0 +1,227 @@
+/*
+ * 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.qe;
+
+import java.util.ArrayList;
+import java.util.Map;
+
+import org.cojen.util.SoftValuedHashMap;
+import org.cojen.util.WeakIdentityMap;
+
+import com.amazon.carbonado.FetchException;
+import com.amazon.carbonado.IsolationLevel;
+import com.amazon.carbonado.Query;
+import com.amazon.carbonado.RepositoryException;
+import com.amazon.carbonado.Storable;
+import com.amazon.carbonado.Transaction;
+
+import com.amazon.carbonado.filter.ClosedFilter;
+import com.amazon.carbonado.filter.Filter;
+import com.amazon.carbonado.filter.FilterValues;
+
+/**
+ * Builds and caches StandardQuery instances.
+ *
+ * @author Brian S O'Neill
+ */
+public abstract class StandardQueryFactory<S extends Storable> implements QueryFactory<S> {
+ private final Class<S> mType;
+ private final boolean mLazySetExecutor;
+
+ private final Map<String, Query<S>> mStringToQuery;
+
+ // Maps filters to maps which map ordering lists to queries.
+ private final Map<Filter<S>, Map<OrderingList<S>, Query<S>>> mFilterToQuery;
+
+ protected StandardQueryFactory(Class<S> type) {
+ this(type, false);
+ }
+
+ /**
+ * @param lazySetExecutor by default, query executors are built and set
+ * eagerly. Pass true to build and set executor on first query use.
+ */
+ protected StandardQueryFactory(Class<S> type, boolean lazySetExecutor) {
+ if (type == null) {
+ throw new IllegalArgumentException();
+ }
+ mType = type;
+ mLazySetExecutor = lazySetExecutor;
+ mStringToQuery = new SoftValuedHashMap(7);
+ mFilterToQuery = new WeakIdentityMap(7);
+ }
+
+ public Class<S> getStorableType() {
+ return mType;
+ }
+
+ /**
+ * Returns a new or cached query that fetches everything.
+ */
+ public Query<S> query() throws FetchException {
+ return query(Filter.getOpenFilter(mType), null);
+ }
+
+ /**
+ * Returns a new or cached query for the given filter.
+ *
+ * @throws IllegalArgumentException if filter is null
+ */
+ public Query<S> query(String filter) throws FetchException {
+ synchronized (mStringToQuery) {
+ Query<S> query = mStringToQuery.get(filter);
+ if (query == null) {
+ if (filter == null) {
+ throw new IllegalArgumentException("Query filter must not be null");
+ }
+ query = query(Filter.filterFor(mType, filter), null);
+ mStringToQuery.put(filter, query);
+ }
+ return query;
+ }
+ }
+
+ /**
+ * Returns a new or cached query for the given filter.
+ *
+ * @throws IllegalArgumentException if filter is null
+ */
+ public Query<S> query(Filter<S> filter) throws FetchException {
+ return query(filter, null);
+ }
+
+ /**
+ * Returns a new or cached query for the given query specification.
+ *
+ * @throws IllegalArgumentException if filter is null
+ */
+ public Query<S> query(Filter<S> filter, OrderingList<S> ordering) throws FetchException {
+ Map<OrderingList<S>, Query<S>> map;
+ synchronized (mFilterToQuery) {
+ map = mFilterToQuery.get(filter);
+ if (map == null) {
+ if (filter == null) {
+ throw new IllegalArgumentException("Query filter must not be null");
+ }
+ map = new SoftValuedHashMap(7);
+ mFilterToQuery.put(filter, map);
+ }
+ }
+
+ Query<S> query;
+ synchronized (map) {
+ query = map.get(ordering);
+ if (query == null) {
+ FilterValues<S> values = filter.initialFilterValues();
+ if (values == null && filter instanceof ClosedFilter) {
+ query = new EmptyQuery<S>(this, ordering);
+ } else {
+ StandardQuery<S> standardQuery = createQuery(values, ordering);
+ if (!mLazySetExecutor) {
+ try {
+ standardQuery.setExecutorReference();
+ } catch (RepositoryException e) {
+ throw e.toFetchException();
+ }
+ }
+ query = standardQuery;
+ }
+ map.put(ordering, query);
+ }
+ }
+
+ return query;
+ }
+
+ /**
+ * Returns a new or cached query for the given query specification.
+ *
+ * @param values optional values object, defaults to open filter if null
+ * @param ordering optional order-by properties
+ */
+ public Query<S> query(FilterValues<S> values, OrderingList<S> ordering) throws FetchException {
+ Query<S> query;
+ if (values == null) {
+ query = query(Filter.getOpenFilter(mType), ordering);
+ } else {
+ // FIXME: Just like with DelegatedQueryExecutor, need a better way
+ // of transfering values.
+ query = query(values.getFilter(), ordering).withValues(values.getSuppliedValues());
+ }
+ return query;
+ }
+
+ /**
+ * For each cached query, calls {@link StandardQuery#setExecutorReference}.
+ */
+ public void setExecutorReferences() throws RepositoryException {
+ for (StandardQuery<S> query : gatherQueries()) {
+ query.setExecutorReference();
+ }
+ }
+
+ /**
+ * For each cached query, calls {@link StandardQuery#resetExecutorReference}.
+ * This call can be used to rebuild all cached query plans after the set of
+ * available indexes has changed.
+ */
+ public void resetExecutorReferences() throws RepositoryException {
+ for (StandardQuery<S> query : gatherQueries()) {
+ query.resetExecutorReference();
+ }
+ }
+
+ /**
+ * For each cached query, calls {@link StandardQuery#clearExecutorReference}.
+ * This call can be used to clear all cached query plans after the set of
+ * available indexes has changed.
+ */
+ public void clearExecutorReferences() {
+ for (StandardQuery<S> query : gatherQueries()) {
+ query.clearExecutorReference();
+ }
+ }
+
+ /**
+ * Implement this method to return query implementations.
+ *
+ * @param values optional values object, defaults to open filter if null
+ * @param ordering optional order-by properties
+ */
+ protected abstract StandardQuery<S> createQuery(FilterValues<S> values,
+ OrderingList<S> ordering);
+
+ private ArrayList<StandardQuery<S>> gatherQueries() {
+ // Copy all queries and operate on the copy instead of holding lock for
+ // potentially a long time.
+ ArrayList<StandardQuery<S>> queries = new ArrayList<StandardQuery<S>>();
+
+ synchronized (mFilterToQuery) {
+ for (Map<OrderingList<S>, Query<S>> map : mFilterToQuery.values()) {
+ for (Query<S> query : map.values()) {
+ if (query instanceof StandardQuery) {
+ queries.add((StandardQuery<S>) query);
+ }
+ }
+ }
+ }
+
+ return queries;
+ }
+}
diff --git a/src/main/java/com/amazon/carbonado/qe/StorageAccess.java b/src/main/java/com/amazon/carbonado/qe/StorageAccess.java
new file mode 100644
index 0000000..5f93952
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/qe/StorageAccess.java
@@ -0,0 +1,63 @@
+/*
+ * 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.qe;
+
+import java.util.Collection;
+
+import com.amazon.carbonado.Cursor;
+import com.amazon.carbonado.FetchException;
+import com.amazon.carbonado.Storable;
+import com.amazon.carbonado.Storage;
+
+import com.amazon.carbonado.cursor.SortBuffer;
+
+import com.amazon.carbonado.info.StorableIndex;
+
+/**
+ * Provides internal access to a {@link Storage}, necessary for query
+ * execution.
+ *
+ * @author Brian S O'Neill
+ */
+public interface StorageAccess<S extends Storable>
+ extends FullScanQueryExecutor.Support<S>,
+ FullScanIndexedQueryExecutor.Support<S>,
+ KeyQueryExecutor.Support<S>,
+ IndexedQueryExecutor.Support<S>,
+ SortedQueryExecutor.Support<S>
+{
+ /**
+ * Returns the specific type of Storable managed by this object.
+ */
+ Class<S> getStorableType();
+
+ /**
+ * Returns all the available indexes.
+ */
+ Collection<StorableIndex<S>> getAllIndexes();
+
+ /**
+ * If the given index is not directly supported by storage, queries should
+ * be delegated. Return the storage to delegate to or null if index should
+ * not be delegated.
+ *
+ * @throws IllegalArgumentException if index is unknown
+ */
+ Storage<S> storageDelegate(StorableIndex<S> index);
+}
diff --git a/src/main/java/com/amazon/carbonado/qe/UnionQueryAnalyzer.java b/src/main/java/com/amazon/carbonado/qe/UnionQueryAnalyzer.java
index 039aea4..e0d49c2 100644
--- a/src/main/java/com/amazon/carbonado/qe/UnionQueryAnalyzer.java
+++ b/src/main/java/com/amazon/carbonado/qe/UnionQueryAnalyzer.java
@@ -26,7 +26,11 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
+import com.amazon.carbonado.FetchException;
+import com.amazon.carbonado.RepositoryException;
import com.amazon.carbonado.Storable;
+import com.amazon.carbonado.Storage;
+import com.amazon.carbonado.SupportException;
import com.amazon.carbonado.filter.AndFilter;
import com.amazon.carbonado.filter.Filter;
@@ -54,16 +58,22 @@ import com.amazon.carbonado.info.StorableKey;
*
* @author Brian S O'Neill
*/
-public class UnionQueryAnalyzer<S extends Storable> {
+public class UnionQueryAnalyzer<S extends Storable> implements QueryExecutorFactory<S> {
final IndexedQueryAnalyzer<S> mIndexAnalyzer;
+ final RepositoryAccess mRepoAccess;
/**
* @param type type of storable being queried
- * @param indexProvider
+ * @param access repository access for examing available indexes
* @throws IllegalArgumentException if type or indexProvider is null
*/
- public UnionQueryAnalyzer(Class<S> type, IndexProvider indexProvider) {
- mIndexAnalyzer = new IndexedQueryAnalyzer<S>(type, indexProvider);
+ public UnionQueryAnalyzer(Class<S> type, RepositoryAccess access) {
+ mIndexAnalyzer = new IndexedQueryAnalyzer<S>(type, access);
+ mRepoAccess = access;
+ }
+
+ public Class<S> getStorableType() {
+ return mIndexAnalyzer.getStorableType();
}
/**
@@ -72,8 +82,6 @@ public class UnionQueryAnalyzer<S extends Storable> {
*/
public Result analyze(Filter<S> filter, OrderingList<S> ordering) {
if (!filter.isBound()) {
- // Strictly speaking, this is not required, but it detects the
- // mistake of not properly calling initialFilterValues.
throw new IllegalArgumentException("Filter must be bound");
}
@@ -81,11 +89,33 @@ public class UnionQueryAnalyzer<S extends Storable> {
ordering = OrderingList.emptyList();
}
+ return new Result(buildSubResults(filter, ordering));
+ }
+
+ /**
+ * Returns an executor that handles the given query specification.
+ *
+ * @param filter optional filter which must be {@link Filter#isBound bound}
+ * @param ordering optional properties which define desired ordering
+ */
+ public QueryExecutor<S> executor(Filter<S> filter, OrderingList<S> ordering)
+ throws RepositoryException
+ {
+ return analyze(filter, ordering).createExecutor();
+ }
+
+ /**
+ * Splits the filter into sub-results, merges sub-results, and possibly
+ * imposes a total ordering.
+ */
+ private List<IndexedQueryAnalyzer<S>.Result>
+ buildSubResults(Filter<S> filter, OrderingList<S> ordering)
+ {
List<IndexedQueryAnalyzer<S>.Result> subResults = splitIntoSubResults(filter, ordering);
if (subResults.size() <= 1) {
// Total ordering not required.
- return new Result(subResults);
+ return subResults;
}
// If any orderings have an unspecified direction, switch to ASCENDING
@@ -111,7 +141,7 @@ public class UnionQueryAnalyzer<S extends Storable> {
if (subResults.size() <= 1) {
// Total ordering no longer required.
- return new Result(subResults);
+ return subResults;
}
}
@@ -125,7 +155,7 @@ public class UnionQueryAnalyzer<S extends Storable> {
ChainedProperty<S> property = op.getChainedProperty();
if (pruneKeys(keys, property)) {
// Found a key which is fully covered, indicating total ordering.
- return new Result(subResults);
+ return subResults;
}
}
@@ -194,7 +224,7 @@ public class UnionQueryAnalyzer<S extends Storable> {
}
}
- return new Result(subResults);
+ return subResults;
}
/**
@@ -263,6 +293,9 @@ public class UnionQueryAnalyzer<S extends Storable> {
return Direction.UNSPECIFIED;
}
+ /**
+ * Splits the filter into sub-results and possibly merges them.
+ */
private List<IndexedQueryAnalyzer<S>.Result>
splitIntoSubResults(Filter<S> filter, OrderingList<S> ordering)
{
@@ -340,16 +373,28 @@ public class UnionQueryAnalyzer<S extends Storable> {
return mergedResults;
}
- public class Result {
- // FIXME: User of QueryAnalyzer results needs to identify what actual
- // storage is used by an index. It is also responsible for grouping
- // unions together if storage differs. If foreign index is selected,
- // then join is needed.
+ Storage storageDelegate(IndexedQueryAnalyzer<S>.Result result) {
+ StorableIndex<S> localIndex = result.getLocalIndex();
+ StorageAccess<S> localAccess = mRepoAccess.storageAccessFor(getStorableType());
+ if (localIndex != null) {
+ return localAccess.storageDelegate(localIndex);
+ }
+
+ StorableIndex foreignIndex = result.getForeignIndex();
+ StorageAccess foreignAccess = mRepoAccess.storageAccessFor(foreignIndex.getStorableType());
+
+ return foreignAccess.storageDelegate(foreignIndex);
+ }
+
+ public class Result {
private final List<IndexedQueryAnalyzer<S>.Result> mSubResults;
Result(List<IndexedQueryAnalyzer<S>.Result> subResults) {
- mSubResults = subResults;
+ if (subResults.size() < 1) {
+ throw new IllegalArgumentException();
+ }
+ mSubResults = Collections.unmodifiableList(subResults);
}
/**
@@ -359,6 +404,27 @@ public class UnionQueryAnalyzer<S extends Storable> {
public List<IndexedQueryAnalyzer<S>.Result> getSubResults() {
return mSubResults;
}
+
+ /**
+ * Creates a QueryExecutor based on this result.
+ */
+ public QueryExecutor<S> createExecutor()
+ throws SupportException, FetchException, RepositoryException
+ {
+ List<IndexedQueryAnalyzer<S>.Result> subResults = getSubResults();
+ int size = subResults.size();
+
+ if (size == 1) {
+ return subResults.get(0).createExecutor();
+ }
+
+ List<QueryExecutor<S>> executors = new ArrayList<QueryExecutor<S>>(size);
+ for (int i=0; i<size; i++) {
+ executors.add(subResults.get(i).createExecutor());
+ }
+
+ return new UnionQueryExecutor<S>(executors);
+ }
}
/**
@@ -482,6 +548,8 @@ public class UnionQueryAnalyzer<S extends Storable> {
IndexedQueryAnalyzer<S>.Result subResult =
mIndexAnalyzer.analyze(subFilter, mOrdering);
+ Storage subResultStorage = storageDelegate(subResult);
+
// Rather than blindly add to mSubResults, try to merge with
// another result. This in turn reduces the number of cursors
// needed by the union.
@@ -489,7 +557,15 @@ public class UnionQueryAnalyzer<S extends Storable> {
int size = mSubResults.size();
for (int i=0; i<size; i++) {
IndexedQueryAnalyzer<S>.Result existing = mSubResults.get(i);
- if (existing.canMergeRemainder(subResult)) {
+ boolean canMerge = existing.canMergeRemainder(subResult);
+ if (!canMerge) {
+ Storage existingStorage = storageDelegate(existing);
+ if (existingStorage != null && existingStorage == subResultStorage) {
+ // Merge common delegates together.
+ canMerge = true;
+ }
+ }
+ if (canMerge) {
mSubResults.set(i, existing.mergeRemainder(subResult));
return;
}
diff --git a/src/main/java/com/amazon/carbonado/spi/BaseQuery.java b/src/main/java/com/amazon/carbonado/spi/BaseQuery.java
index b1a1d45..e5fc3b0 100644
--- a/src/main/java/com/amazon/carbonado/spi/BaseQuery.java
+++ b/src/main/java/com/amazon/carbonado/spi/BaseQuery.java
@@ -211,7 +211,9 @@ public abstract class BaseQuery<S extends Storable> extends AbstractQuery<S> imp
public Query<S> not() throws FetchException {
FilterValues<S> values = getFilterValues();
if (values == null) {
- return new EmptyQuery<S>(mStorage, mOrderings);
+ // FIXME: fix this or remove BaseQuery class.
+ throw new UnsupportedOperationException();
+ //return new EmptyQuery<S>(mStorage, mOrderings);
}
Query<S> newQuery = mStorage.query(values.getFilter().not());
newQuery = newQuery.withValues(values.getSuppliedValues());
diff --git a/src/test/java/com/amazon/carbonado/qe/TestIndexedQueryAnalyzer.java b/src/test/java/com/amazon/carbonado/qe/TestIndexedQueryAnalyzer.java
index dcc07ee..29e094d 100644
--- a/src/test/java/com/amazon/carbonado/qe/TestIndexedQueryAnalyzer.java
+++ b/src/test/java/com/amazon/carbonado/qe/TestIndexedQueryAnalyzer.java
@@ -26,8 +26,12 @@ import java.util.List;
import junit.framework.TestCase;
import junit.framework.TestSuite;
+import com.amazon.carbonado.Cursor;
import com.amazon.carbonado.Repository;
import com.amazon.carbonado.Storable;
+import com.amazon.carbonado.Storage;
+
+import com.amazon.carbonado.cursor.SortBuffer;
import com.amazon.carbonado.info.StorableIndex;
@@ -70,7 +74,7 @@ public class TestIndexedQueryAnalyzer extends TestCase {
// are performed by TestFilteringScore and TestOrderingScore.
public void testFullScan() throws Exception {
- IndexedQueryAnalyzer iqa = new IndexedQueryAnalyzer(Address.class, IxProvider.INSTANCE);
+ IndexedQueryAnalyzer iqa = new IndexedQueryAnalyzer(Address.class, RepoAccess.INSTANCE);
Filter<Address> filter = Filter.filterFor(Address.class, "addressZip = ?");
filter = filter.bind();
IndexedQueryAnalyzer.Result result = iqa.analyze(filter, null);
@@ -83,7 +87,7 @@ public class TestIndexedQueryAnalyzer extends TestCase {
}
public void testIndexScan() throws Exception {
- IndexedQueryAnalyzer iqa = new IndexedQueryAnalyzer(Address.class, IxProvider.INSTANCE);
+ IndexedQueryAnalyzer iqa = new IndexedQueryAnalyzer(Address.class, RepoAccess.INSTANCE);
Filter<Address> filter = Filter.filterFor(Address.class, "addressID = ?");
filter = filter.bind();
IndexedQueryAnalyzer.Result result = iqa.analyze(filter, null);
@@ -96,7 +100,7 @@ public class TestIndexedQueryAnalyzer extends TestCase {
}
public void testBasic() throws Exception {
- IndexedQueryAnalyzer iqa = new IndexedQueryAnalyzer(Shipment.class, IxProvider.INSTANCE);
+ IndexedQueryAnalyzer iqa = new IndexedQueryAnalyzer(Shipment.class, RepoAccess.INSTANCE);
Filter<Shipment> filter = Filter.filterFor(Shipment.class, "shipmentID = ?");
filter = filter.bind();
IndexedQueryAnalyzer.Result result = iqa.analyze(filter, null);
@@ -134,7 +138,7 @@ public class TestIndexedQueryAnalyzer extends TestCase {
}
public void testSimpleJoin() throws Exception {
- IndexedQueryAnalyzer iqa = new IndexedQueryAnalyzer(Shipment.class, IxProvider.INSTANCE);
+ IndexedQueryAnalyzer iqa = new IndexedQueryAnalyzer(Shipment.class, RepoAccess.INSTANCE);
Filter<Shipment> filter = Filter.filterFor(Shipment.class, "order.orderTotal >= ?");
filter = filter.bind();
IndexedQueryAnalyzer.Result result = iqa.analyze(filter, null);
@@ -149,7 +153,7 @@ public class TestIndexedQueryAnalyzer extends TestCase {
public void testJoinPriority() throws Exception {
// Selects foreign index because filter score is better.
- IndexedQueryAnalyzer iqa = new IndexedQueryAnalyzer(Shipment.class, IxProvider.INSTANCE);
+ IndexedQueryAnalyzer iqa = new IndexedQueryAnalyzer(Shipment.class, RepoAccess.INSTANCE);
Filter<Shipment> filter = Filter.filterFor
(Shipment.class, "shipmentNotes = ? & order.orderTotal >= ?");
filter = filter.bind();
@@ -168,7 +172,7 @@ public class TestIndexedQueryAnalyzer extends TestCase {
// Selects local index because filter score is just as good and local
// indexes are preferred.
- IndexedQueryAnalyzer iqa = new IndexedQueryAnalyzer(Shipment.class, IxProvider.INSTANCE);
+ IndexedQueryAnalyzer iqa = new IndexedQueryAnalyzer(Shipment.class, RepoAccess.INSTANCE);
Filter<Shipment> filter = Filter.filterFor
(Shipment.class, "orderID >= ? & order.orderTotal >= ?");
filter = filter.bind();
@@ -184,7 +188,7 @@ public class TestIndexedQueryAnalyzer extends TestCase {
}
public void testChainedJoin() throws Exception {
- IndexedQueryAnalyzer iqa = new IndexedQueryAnalyzer(Shipment.class, IxProvider.INSTANCE);
+ IndexedQueryAnalyzer iqa = new IndexedQueryAnalyzer(Shipment.class, RepoAccess.INSTANCE);
Filter<Shipment> filter = Filter.filterFor
(Shipment.class, "order.address.addressState = ?");
filter = filter.bind();
@@ -201,7 +205,7 @@ public class TestIndexedQueryAnalyzer extends TestCase {
Repository repo = new ToyRepository();
IndexedQueryAnalyzer<Shipment> iqa =
- new IndexedQueryAnalyzer<Shipment>(Shipment.class, IxProvider.INSTANCE);
+ new IndexedQueryAnalyzer<Shipment>(Shipment.class, RepoAccess.INSTANCE);
Filter<Shipment> filter = Filter.filterFor
(Shipment.class, "order.address.addressState = ? & order.address.addressZip = ?");
FilterValues<Shipment> values = filter.initialFilterValues();
@@ -240,31 +244,55 @@ public class TestIndexedQueryAnalyzer extends TestCase {
assertFalse(ixExec.mReverseOrder);
}
- static class IxProvider implements IndexProvider {
- static final IxProvider INSTANCE = new IxProvider();
+ static class RepoAccess implements RepositoryAccess {
+ static final RepoAccess INSTANCE = new RepoAccess();
+
+ public Repository getRootRepository() {
+ throw new UnsupportedOperationException();
+ }
+
+ public <S extends Storable> StorageAccess<S> storageAccessFor(Class<S> type) {
+ return new StoreAccess<S>(type);
+ }
+ }
+
+ /**
+ * Partially implemented StorageAccess which only supplies information
+ * about indexes.
+ */
+ static class StoreAccess<S extends Storable> implements StorageAccess<S> {
+ private final Class<S> mType;
+
+ StoreAccess(Class<S> type) {
+ mType = type;
+ }
+
+ public Class<S> getStorableType() {
+ return mType;
+ }
- public <S extends Storable> Collection<StorableIndex<S>> indexesFor(Class<S> type) {
+ public Collection<StorableIndex<S>> getAllIndexes() {
StorableIndex<S>[] indexes;
- if (Address.class.isAssignableFrom(type)) {
+ if (Address.class.isAssignableFrom(mType)) {
indexes = new StorableIndex[] {
- makeIndex(type, "addressID"),
- makeIndex(type, "addressState")
+ makeIndex(mType, "addressID"),
+ makeIndex(mType, "addressState")
};
- } else if (Order.class.isAssignableFrom(type)) {
+ } else if (Order.class.isAssignableFrom(mType)) {
indexes = new StorableIndex[] {
- makeIndex(type, "orderID"),
- makeIndex(type, "orderTotal"),
- makeIndex(type, "addressID")
+ makeIndex(mType, "orderID"),
+ makeIndex(mType, "orderTotal"),
+ makeIndex(mType, "addressID")
};
- } else if (Shipment.class.isAssignableFrom(type)) {
+ } else if (Shipment.class.isAssignableFrom(mType)) {
indexes = new StorableIndex[] {
- makeIndex(type, "shipmentID"),
- makeIndex(type, "orderID"),
+ makeIndex(mType, "shipmentID"),
+ makeIndex(mType, "orderID"),
};
- } else if (Shipper.class.isAssignableFrom(type)) {
+ } else if (Shipper.class.isAssignableFrom(mType)) {
indexes = new StorableIndex[] {
- makeIndex(type, "shipperID")
+ makeIndex(mType, "shipperID")
};
} else {
indexes = new StorableIndex[0];
@@ -272,5 +300,37 @@ public class TestIndexedQueryAnalyzer extends TestCase {
return Arrays.asList(indexes);
}
+
+ public Storage<S> storageDelegate(StorableIndex<S> index) {
+ return null;
+ }
+
+ public SortBuffer<S> createSortBuffer() {
+ throw new UnsupportedOperationException();
+ }
+
+ public Cursor<S> fetch() {
+ throw new UnsupportedOperationException();
+ }
+
+ public Cursor<S> fetch(StorableIndex<S> index) {
+ throw new UnsupportedOperationException();
+ }
+
+ public Cursor<S> fetch(StorableIndex<S> index, Object[] identityValues) {
+ throw new UnsupportedOperationException();
+ }
+
+ public Cursor<S> fetch(StorableIndex<S> index,
+ Object[] identityValues,
+ BoundaryType rangeStartBoundary,
+ Object rangeStartValue,
+ BoundaryType rangeEndBoundary,
+ Object rangeEndValue,
+ boolean reverseRange,
+ boolean reverseOrder)
+ {
+ throw new UnsupportedOperationException();
+ }
}
}
diff --git a/src/test/java/com/amazon/carbonado/qe/TestIndexedQueryExecutor.java b/src/test/java/com/amazon/carbonado/qe/TestIndexedQueryExecutor.java
index e8b3ba4..3226e13 100644
--- a/src/test/java/com/amazon/carbonado/qe/TestIndexedQueryExecutor.java
+++ b/src/test/java/com/amazon/carbonado/qe/TestIndexedQueryExecutor.java
@@ -697,26 +697,36 @@ public class TestIndexedQueryExecutor extends TestCase {
boolean mReverseRange;
boolean mReverseOrder;
- public Mock(StorableIndex<S> index, CompositeScore<S> score) {
- super(index, score);
+ Mock(StorableIndex<S> index, CompositeScore<S> score) {
+ this(index, score, new MockSupport[1]);
}
- protected Cursor<S> fetch(StorableIndex<S> index,
- Object[] identityValues,
- BoundaryType rangeStartBoundary,
- Object rangeStartValue,
- BoundaryType rangeEndBoundary,
- Object rangeEndValue,
- boolean reverseRange,
- boolean reverseOrder)
+ Mock(StorableIndex<S> index, CompositeScore<S> score, MockSupport[] ref) {
+ // Extremely bizarre hack to allow support to know about us.
+ super(ref[0] = new MockSupport(), index, score);
+ ((MockSupport<S>) ref[0]).mMock = this;
+ }
+ }
+
+ static class MockSupport<S extends Storable> implements IndexedQueryExecutor.Support<S> {
+ Mock<S> mMock;
+
+ public Cursor<S> fetch(StorableIndex<S> index,
+ Object[] identityValues,
+ BoundaryType rangeStartBoundary,
+ Object rangeStartValue,
+ BoundaryType rangeEndBoundary,
+ Object rangeEndValue,
+ boolean reverseRange,
+ boolean reverseOrder)
{
- mIdentityValues = identityValues;
- mRangeStartBoundary = rangeStartBoundary;
- mRangeStartValue = rangeStartValue;
- mRangeEndBoundary = rangeEndBoundary;
- mRangeEndValue = rangeEndValue;
- mReverseRange = reverseRange;
- mReverseOrder = reverseOrder;
+ mMock.mIdentityValues = identityValues;
+ mMock.mRangeStartBoundary = rangeStartBoundary;
+ mMock.mRangeStartValue = rangeStartValue;
+ mMock.mRangeEndBoundary = rangeEndBoundary;
+ mMock.mRangeEndValue = rangeEndValue;
+ mMock.mReverseRange = reverseRange;
+ mMock.mReverseOrder = reverseOrder;
Collection<S> empty = Collections.emptyList();
return new IteratorCursor<S>(empty);
diff --git a/src/test/java/com/amazon/carbonado/qe/TestJoinedQueryExecutor.java b/src/test/java/com/amazon/carbonado/qe/TestJoinedQueryExecutor.java
index 7d352ed..ac4ebea 100644
--- a/src/test/java/com/amazon/carbonado/qe/TestJoinedQueryExecutor.java
+++ b/src/test/java/com/amazon/carbonado/qe/TestJoinedQueryExecutor.java
@@ -169,29 +169,32 @@ public class TestJoinedQueryExecutor extends TestQueryExecutor {
protected QueryExecutor<UserAddress> addressExecutor() throws Exception {
Storage<UserAddress> addressStorage = mRepository.storageFor(UserAddress.class);
- QueryExecutor<UserAddress> addressExecutor =
- new ScanQueryExecutor<UserAddress>(addressStorage.query());
+ QueryExecutor<UserAddress> addressExecutor = new FullScanQueryExecutor<UserAddress>
+ (new ScanQuerySupport<UserAddress>(addressStorage.query()));
addressExecutor = new FilteredQueryExecutor<UserAddress>
(addressExecutor, Filter.filterFor(UserAddress.class, "state = ?"));
OrderingList<UserAddress> ordering = OrderingList.get(UserAddress.class, "+country");
- addressExecutor =
- new ArraySortedQueryExecutor<UserAddress>(addressExecutor, null, ordering);
+ addressExecutor = new SortedQueryExecutor<UserAddress>
+ (null, addressExecutor, null, ordering);
return addressExecutor;
}
- static class ScanQueryExecutor<S extends Storable> extends FullScanQueryExecutor<S> {
+ static class ScanQuerySupport<S extends Storable> implements FullScanQueryExecutor.Support<S> {
private final Query<S> mQuery;
- ScanQueryExecutor(Query<S> query) {
- super(query.getStorableType());
+ ScanQuerySupport(Query<S> query) {
mQuery = query;
}
- protected Cursor<S> fetch() throws FetchException {
+ public Class<S> getStorableType() {
+ return mQuery.getStorableType();
+ }
+
+ public Cursor<S> fetch() throws FetchException {
return mQuery.fetch();
}
}
diff --git a/src/test/java/com/amazon/carbonado/qe/TestSortedQueryExecutor.java b/src/test/java/com/amazon/carbonado/qe/TestSortedQueryExecutor.java
index 8e1eda2..53c47d6 100644
--- a/src/test/java/com/amazon/carbonado/qe/TestSortedQueryExecutor.java
+++ b/src/test/java/com/amazon/carbonado/qe/TestSortedQueryExecutor.java
@@ -50,7 +50,7 @@ public class TestSortedQueryExecutor extends TestQueryExecutor {
OrderingList<Address> ordered = createOrdering("addressCountry");
QueryExecutor<Address> executor =
- new ArraySortedQueryExecutor<Address>(unsorted, null, ordered);
+ new SortedQueryExecutor<Address>(null, unsorted, null, ordered);
assertEquals(filter, executor.getFilter());
@@ -69,7 +69,7 @@ public class TestSortedQueryExecutor extends TestQueryExecutor {
OrderingList<Address> finisher = createOrdering("addressState");
QueryExecutor<Address> executor =
- new ArraySortedQueryExecutor<Address>(unsorted, handled, finisher);
+ new SortedQueryExecutor<Address>(null, unsorted, handled, finisher);
assertEquals(filter, executor.getFilter());
diff --git a/src/test/java/com/amazon/carbonado/qe/TestUnionQueryAnalyzer.java b/src/test/java/com/amazon/carbonado/qe/TestUnionQueryAnalyzer.java
index df2696d..7488c5b 100644
--- a/src/test/java/com/amazon/carbonado/qe/TestUnionQueryAnalyzer.java
+++ b/src/test/java/com/amazon/carbonado/qe/TestUnionQueryAnalyzer.java
@@ -70,7 +70,7 @@ public class TestUnionQueryAnalyzer extends TestCase {
public void testSingleSubResult() throws Exception {
UnionQueryAnalyzer uqa =
- new UnionQueryAnalyzer(Shipment.class, TestIndexedQueryAnalyzer.IxProvider.INSTANCE);
+ new UnionQueryAnalyzer(Shipment.class, TestIndexedQueryAnalyzer.RepoAccess.INSTANCE);
Filter<Shipment> filter = Filter.filterFor(Shipment.class, "shipmentID = ?");
filter = filter.bind();
UnionQueryAnalyzer.Result result = uqa.analyze(filter, null);
@@ -81,7 +81,7 @@ public class TestUnionQueryAnalyzer extends TestCase {
public void testSingleSubResultUnspecifiedDirection() throws Exception {
UnionQueryAnalyzer uqa =
- new UnionQueryAnalyzer(Shipment.class, TestIndexedQueryAnalyzer.IxProvider.INSTANCE);
+ new UnionQueryAnalyzer(Shipment.class, TestIndexedQueryAnalyzer.RepoAccess.INSTANCE);
Filter<Shipment> filter = Filter.filterFor(Shipment.class, "shipmentID > ?");
filter = filter.bind();
OrderingList<Shipment> orderings =
@@ -98,7 +98,7 @@ public class TestUnionQueryAnalyzer extends TestCase {
public void testSimpleUnion() throws Exception {
UnionQueryAnalyzer uqa =
- new UnionQueryAnalyzer(Shipment.class, TestIndexedQueryAnalyzer.IxProvider.INSTANCE);
+ new UnionQueryAnalyzer(Shipment.class, TestIndexedQueryAnalyzer.RepoAccess.INSTANCE);
Filter<Shipment> filter = Filter.filterFor(Shipment.class,
"shipmentID = ? | orderID = ?");
filter = filter.bind();
@@ -129,7 +129,7 @@ public class TestUnionQueryAnalyzer extends TestCase {
public void testSimpleUnion2() throws Exception {
UnionQueryAnalyzer uqa =
- new UnionQueryAnalyzer(Shipment.class, TestIndexedQueryAnalyzer.IxProvider.INSTANCE);
+ new UnionQueryAnalyzer(Shipment.class, TestIndexedQueryAnalyzer.RepoAccess.INSTANCE);
Filter<Shipment> filter = Filter.filterFor(Shipment.class,
"shipmentID = ? | orderID > ?");
filter = filter.bind();
@@ -165,7 +165,7 @@ public class TestUnionQueryAnalyzer extends TestCase {
public void testSimpleUnion3() throws Exception {
UnionQueryAnalyzer uqa =
- new UnionQueryAnalyzer(Shipment.class, TestIndexedQueryAnalyzer.IxProvider.INSTANCE);
+ new UnionQueryAnalyzer(Shipment.class, TestIndexedQueryAnalyzer.RepoAccess.INSTANCE);
Filter<Shipment> filter = Filter.filterFor(Shipment.class,
"shipmentID = ? | orderID > ? & orderID <= ?");
filter = filter.bind();
@@ -206,7 +206,7 @@ public class TestUnionQueryAnalyzer extends TestCase {
public void testSimpleUnionUnspecifiedDirection() throws Exception {
UnionQueryAnalyzer uqa =
- new UnionQueryAnalyzer(Shipment.class, TestIndexedQueryAnalyzer.IxProvider.INSTANCE);
+ new UnionQueryAnalyzer(Shipment.class, TestIndexedQueryAnalyzer.RepoAccess.INSTANCE);
Filter<Shipment> filter = Filter.filterFor(Shipment.class,
"shipmentID > ? | orderID = ?");
filter = filter.bind();
@@ -251,7 +251,7 @@ public class TestUnionQueryAnalyzer extends TestCase {
// split this into a union. After futher analysis, it should decide
// that this offers no benefit and will merge them back.
UnionQueryAnalyzer uqa =
- new UnionQueryAnalyzer(Shipment.class, TestIndexedQueryAnalyzer.IxProvider.INSTANCE);
+ new UnionQueryAnalyzer(Shipment.class, TestIndexedQueryAnalyzer.RepoAccess.INSTANCE);
Filter<Shipment> filter = Filter.filterFor
(Shipment.class,
"shipmentID = ? & (shipmentID = ? | orderID = ?)");
@@ -276,7 +276,7 @@ public class TestUnionQueryAnalyzer extends TestCase {
public void testFullScan() throws Exception {
// Because no indexes were selected, there's no union to perform.
UnionQueryAnalyzer uqa =
- new UnionQueryAnalyzer(Shipment.class, TestIndexedQueryAnalyzer.IxProvider.INSTANCE);
+ new UnionQueryAnalyzer(Shipment.class, TestIndexedQueryAnalyzer.RepoAccess.INSTANCE);
Filter<Shipment> filter = Filter.filterFor
(Shipment.class, "shipmentNotes = ? | shipperID = ?");
filter = filter.bind();
@@ -298,7 +298,7 @@ public class TestUnionQueryAnalyzer extends TestCase {
// Because not all sub-results of union use an index, just fallback to
// doing a full scan.
UnionQueryAnalyzer uqa =
- new UnionQueryAnalyzer(Shipment.class, TestIndexedQueryAnalyzer.IxProvider.INSTANCE);
+ new UnionQueryAnalyzer(Shipment.class, TestIndexedQueryAnalyzer.RepoAccess.INSTANCE);
Filter<Shipment> filter = Filter.filterFor
(Shipment.class, "shipmentNotes = ? | orderID = ?");
filter = filter.bind();
@@ -320,7 +320,7 @@ public class TestUnionQueryAnalyzer extends TestCase {
// Although not all sub-results use an index, one that does has a join
// so it is exempt from folding into the full scan.
UnionQueryAnalyzer uqa =
- new UnionQueryAnalyzer(Shipment.class, TestIndexedQueryAnalyzer.IxProvider.INSTANCE);
+ new UnionQueryAnalyzer(Shipment.class, TestIndexedQueryAnalyzer.RepoAccess.INSTANCE);
Filter<Shipment> filter = Filter.filterFor
(Shipment.class, "shipmentNotes = ? | orderID = ? & order.orderTotal > ?");
filter = filter.bind();
diff --git a/src/test/java/com/amazon/carbonado/qe/TestUnionQueryExecutor.java b/src/test/java/com/amazon/carbonado/qe/TestUnionQueryExecutor.java
index 07716bb..9fccbc7 100644
--- a/src/test/java/com/amazon/carbonado/qe/TestUnionQueryExecutor.java
+++ b/src/test/java/com/amazon/carbonado/qe/TestUnionQueryExecutor.java
@@ -44,8 +44,8 @@ public class TestUnionQueryExecutor extends TestQueryExecutor {
}
public void testBasicUnion() throws Exception {
- QueryExecutor<Address> primary = new ArraySortedQueryExecutor<Address>
- (createExecutor(1, 2, 3, 4, 5, 6, 7, 8), null, createOrdering("addressID"));
+ QueryExecutor<Address> primary = new SortedQueryExecutor<Address>
+ (null, createExecutor(1, 2, 3, 4, 5, 6, 7, 8), null, createOrdering("addressID"));
Filter<Address> filter_1 = Filter.filterFor(Address.class, "addressCountry > ?");
FilterValues<Address> values_1 = filter_1.initialFilterValues();
diff --git a/src/test/java/com/amazon/carbonado/repo/toy/ToyStorage.java b/src/test/java/com/amazon/carbonado/repo/toy/ToyStorage.java
index 84792ad..bafa244 100644
--- a/src/test/java/com/amazon/carbonado/repo/toy/ToyStorage.java
+++ b/src/test/java/com/amazon/carbonado/repo/toy/ToyStorage.java
@@ -48,7 +48,9 @@ import com.amazon.carbonado.filter.FilterValues;
import com.amazon.carbonado.info.OrderedProperty;
import com.amazon.carbonado.info.StorableIntrospector;
-import com.amazon.carbonado.qe.ArraySortedQueryExecutor;
+import com.amazon.carbonado.qe.QueryExecutorFactory;
+import com.amazon.carbonado.qe.QueryFactory;
+import com.amazon.carbonado.qe.SortedQueryExecutor;
import com.amazon.carbonado.qe.FilteredQueryExecutor;
import com.amazon.carbonado.qe.IterableQueryExecutor;
import com.amazon.carbonado.qe.OrderingList;
@@ -59,7 +61,9 @@ import com.amazon.carbonado.qe.StandardQuery;
*
* @author Brian S O'Neill
*/
-public class ToyStorage<S extends Storable> implements Storage<S>, MasterSupport<S> {
+public class ToyStorage<S extends Storable>
+ implements Storage<S>, MasterSupport<S>, QueryFactory<S>, QueryExecutorFactory<S>
+{
final ToyRepository mRepo;
final Class<S> mType;
@@ -89,18 +93,36 @@ public class ToyStorage<S extends Storable> implements Storage<S>, MasterSupport
return (S) mInstanceFactory.instantiate(this);
}
- public Query<S> query() throws FetchException {
+ public Query<S> query() {
return new ToyQuery(null, null);
}
- public Query<S> query(String filter) throws FetchException {
+ public Query<S> query(String filter) {
return query(Filter.filterFor(mType, filter));
}
- public Query<S> query(Filter<S> filter) throws FetchException {
+ public Query<S> query(Filter<S> filter) {
return new ToyQuery(filter.initialFilterValues(), null);
}
+ public Query<S> query(FilterValues<S> values, OrderingList<S> ordering) {
+ return new ToyQuery(values, ordering);
+ }
+
+ public QueryExecutor<S> executor(Filter<S> filter, OrderingList<S> ordering) {
+ QueryExecutor<S> executor = new IterableQueryExecutor<S>(mType, mData, mDataLock);
+
+ if (filter != null) {
+ executor = new FilteredQueryExecutor<S>(executor, filter);
+ }
+
+ if (ordering.size() > 0) {
+ executor = new SortedQueryExecutor<S>(null, executor, null, ordering);
+ }
+
+ return executor;
+ }
+
public boolean addTrigger(Trigger<? super S> trigger) {
return false;
}
@@ -217,34 +239,24 @@ public class ToyStorage<S extends Storable> implements Storage<S>, MasterSupport
}
private class ToyQuery extends StandardQuery<S> {
- ToyQuery(FilterValues<S> values, OrderingList<S> orderings) {
- super(values, orderings);
+ ToyQuery(FilterValues<S> values, OrderingList<S> ordering) {
+ super(values, ordering);
}
- protected Storage<S> getStorage() {
- return ToyStorage.this;
- }
-
- protected Transaction enterTransactionForDelete(IsolationLevel level) {
+ protected Transaction enterTransaction(IsolationLevel level) {
return mRepo.enterTransaction(level);
}
- protected QueryExecutor<S> getExecutor(FilterValues<S> values, OrderingList<S> orderings) {
- QueryExecutor<S> executor = new IterableQueryExecutor<S>(mType, mData, mDataLock);
-
- if (values != null) {
- executor = new FilteredQueryExecutor<S>(executor, values.getFilter());
- }
-
- if (orderings.size() > 0) {
- executor = new ArraySortedQueryExecutor<S>(executor, null, orderings);
- }
+ protected QueryFactory<S> queryFactory() {
+ return ToyStorage.this;
+ }
- return executor;
+ protected QueryExecutorFactory<S> executorFactory() {
+ return ToyStorage.this;
}
- protected StandardQuery<S> newInstance(FilterValues<S> values, OrderingList<S> orderings) {
- return new ToyQuery(values, orderings);
+ protected StandardQuery<S> newInstance(FilterValues<S> values, OrderingList<S> ordering) {
+ return new ToyQuery(values, ordering);
}
}
}