diff options
Diffstat (limited to 'src/main/java/com/amazon')
15 files changed, 417 insertions, 92 deletions
diff --git a/src/main/java/com/amazon/carbonado/qe/CompositeScore.java b/src/main/java/com/amazon/carbonado/qe/CompositeScore.java index 0006cce..51c1f73 100644 --- a/src/main/java/com/amazon/carbonado/qe/CompositeScore.java +++ b/src/main/java/com/amazon/carbonado/qe/CompositeScore.java @@ -97,6 +97,23 @@ public class CompositeScore<S extends Storable> { * {@code 0} if equal, or {@code >0} if second is better.
*/
public static Comparator<CompositeScore<?>> localForeignComparator() {
+ return localForeignComparator(null);
+ }
+
+ /**
+ * Returns a partial comparator suited for comparing local indexes to
+ * foreign indexes. It determines which CompositeScores are better by
+ * examining identity matches, range matches and ordering. It does not
+ * matter if the scores were evaluated for different indexes or storable
+ * types. The comparator returns {@code <0} if first score is better,
+ * {@code 0} if equal, or {@code >0} if second is better.
+ *
+ * @param hints optional hints
+ */
+ public static Comparator<CompositeScore<?>> localForeignComparator(QueryHints hints) {
+ if (hints != null && hints.contains(QueryHint.CONSUME_SLICE)) {
+ return Comp.LOCAL_FOREIGN_SLICE;
+ }
return Comp.LOCAL_FOREIGN;
}
@@ -109,6 +126,23 @@ public class CompositeScore<S extends Storable> { * {@code 0} if equal, or {@code >0} if second is better.
*/
public static Comparator<CompositeScore<?>> fullComparator() {
+ return fullComparator(null);
+ }
+
+ /**
+ * Returns a comparator which determines which CompositeScores are
+ * better. It compares identity matches, range matches, ordering, open
+ * range matches, property arrangement and index cost estimate. It does not
+ * matter if the scores were evaluated for different indexes or storable
+ * types. The comparator returns {@code <0} if first score is better,
+ * {@code 0} if equal, or {@code >0} if second is better.
+ *
+ * @param hints optional hints
+ */
+ public static Comparator<CompositeScore<?>> fullComparator(QueryHints hints) {
+ if (hints != null && hints.contains(QueryHint.CONSUME_SLICE)) {
+ return Comp.SLICE;
+ }
return Comp.FULL;
}
@@ -189,13 +223,17 @@ public class CompositeScore<S extends Storable> { }
private static class Comp implements Comparator<CompositeScore<?>> {
- static final Comparator<CompositeScore<?>> LOCAL_FOREIGN = new Comp(false);
- static final Comparator<CompositeScore<?>> FULL = new Comp(true);
+ static final Comparator<CompositeScore<?>> LOCAL_FOREIGN = new Comp(false, false);
+ static final Comparator<CompositeScore<?>> SLICE = new Comp(true, true);
+ static final Comparator<CompositeScore<?>> LOCAL_FOREIGN_SLICE = new Comp(false, true);
+ static final Comparator<CompositeScore<?>> FULL = new Comp(true, false);
private final boolean mFull;
+ private final boolean mSlice;
- private Comp(boolean full) {
+ private Comp(boolean full, boolean slice) {
mFull = full;
+ mSlice = slice;
}
public int compare(CompositeScore<?> first, CompositeScore<?> second) {
@@ -266,10 +304,11 @@ public class CompositeScore<S extends Storable> { boolean comparedOrdering = false;
if (considerOrdering(firstScore) && considerOrdering(secondScore)) {
- // Only consider ordering if index is fast (clustered) or if
- // index is used for any significant filtering. A full scan of
- // an index just to get desired ordering can be very expensive
- // due to random access I/O. A sort operation is often faster.
+ // Only consider ordering if slice mode, or if index is fast
+ // (clustered) or if index is used for any significant
+ // filtering. A full scan of an index just to get desired
+ // ordering can be very expensive due to random access I/O. A
+ // sort operation is often faster.
result = OrderingScore.fullComparator()
.compare(first.getOrderingScore(), second.getOrderingScore());
@@ -326,7 +365,8 @@ public class CompositeScore<S extends Storable> { }
private boolean considerOrdering(FilteringScore<?> score) {
- return score.isIndexClustered()
+ return mSlice
+ || score.isIndexClustered()
|| score.getIdentityCount() > 0
|| score.hasRangeMatch();
}
diff --git a/src/main/java/com/amazon/carbonado/qe/DelegatedQueryExecutorFactory.java b/src/main/java/com/amazon/carbonado/qe/DelegatedQueryExecutorFactory.java index fae22b4..9c1556f 100644 --- a/src/main/java/com/amazon/carbonado/qe/DelegatedQueryExecutorFactory.java +++ b/src/main/java/com/amazon/carbonado/qe/DelegatedQueryExecutorFactory.java @@ -43,7 +43,7 @@ public class DelegatedQueryExecutorFactory<S extends Storable> implements QueryE return mStorage.getStorableType();
}
- public QueryExecutor<S> executor(Filter<S> filter, OrderingList<S> ordering)
+ public QueryExecutor<S> executor(Filter<S> filter, OrderingList<S> ordering, QueryHints hints)
throws FetchException
{
return new DelegatedQueryExecutor<S>(mStorage, filter, ordering);
diff --git a/src/main/java/com/amazon/carbonado/qe/EmptyQuery.java b/src/main/java/com/amazon/carbonado/qe/EmptyQuery.java index 626b435..f3d54f5 100644 --- a/src/main/java/com/amazon/carbonado/qe/EmptyQuery.java +++ b/src/main/java/com/amazon/carbonado/qe/EmptyQuery.java @@ -107,13 +107,6 @@ public final class EmptyQuery<S extends Storable> extends AbstractQuery<S> { }
/**
- * Always returns false.
- */
- public boolean exists() {
- return false;
- }
-
- /**
* Always throws an IllegalStateException.
*/
public Query<S> with(int value) {
@@ -194,14 +187,14 @@ public final class EmptyQuery<S extends Storable> extends AbstractQuery<S> { }
public Query<S> or(Filter<S> filter) throws FetchException {
- return mFactory.query(filter, null, mOrdering);
+ return mFactory.query(filter, null, mOrdering, null);
}
/**
* Returns a query that fetches everything, possibly in a specified order.
*/
public Query<S> not() throws FetchException {
- return mFactory.query(null, null, mOrdering);
+ return mFactory.query(null, null, mOrdering, null);
}
public Query<S> orderBy(String property) throws FetchException {
@@ -253,6 +246,13 @@ public final class EmptyQuery<S extends Storable> extends AbstractQuery<S> { return 0;
}
+ /**
+ * Always returns false.
+ */
+ public boolean exists() {
+ return false;
+ }
+
public void appendTo(Appendable app) throws IOException {
app.append("Query {type=");
app.append(getStorableType().getName());
diff --git a/src/main/java/com/amazon/carbonado/qe/IndexedQueryAnalyzer.java b/src/main/java/com/amazon/carbonado/qe/IndexedQueryAnalyzer.java index 93d4666..3ddd828 100644 --- a/src/main/java/com/amazon/carbonado/qe/IndexedQueryAnalyzer.java +++ b/src/main/java/com/amazon/carbonado/qe/IndexedQueryAnalyzer.java @@ -81,9 +81,10 @@ public class IndexedQueryAnalyzer<S extends Storable> { * @param filter optional filter which which must be {@link Filter#isBound
* bound} and cannot contain any logical 'or' operations.
* @param ordering optional properties which define desired ordering
+ * @param hints optional query hints
* @throws IllegalArgumentException if filter is not supported
*/
- public Result analyze(Filter<S> filter, OrderingList<S> ordering)
+ public Result analyze(Filter<S> filter, OrderingList<S> ordering, QueryHints hints)
throws SupportException, RepositoryException
{
if (filter != null && !filter.isBound()) {
@@ -94,7 +95,7 @@ public class IndexedQueryAnalyzer<S extends Storable> { CompositeScore<S> bestLocalScore = null;
StorableIndex<S> bestLocalIndex = null;
- final Comparator<CompositeScore<?>> fullComparator = CompositeScore.fullComparator();
+ final Comparator<CompositeScore<?>> fullComparator = CompositeScore.fullComparator(hints);
Collection<StorableIndex<S>> localIndexes = indexesFor(getStorableType());
if (localIndexes != null) {
@@ -115,7 +116,7 @@ public class IndexedQueryAnalyzer<S extends Storable> { if (bestLocalScore != null && bestLocalScore.getFilteringScore().isKeyMatch()) {
// Don't bother checking foreign indexes. The local one is perfect.
- return new Result(filter, bestLocalScore, bestLocalIndex, null, null);
+ return new Result(filter, bestLocalScore, bestLocalIndex, null, null, hints);
}
CompositeScore<?> bestForeignScore = null;
@@ -183,7 +184,7 @@ public class IndexedQueryAnalyzer<S extends Storable> { }
return new Result
- (filter, bestScore, bestLocalIndex, bestForeignIndex, bestForeignProperty);
+ (filter, bestScore, bestLocalIndex, bestForeignIndex, bestForeignProperty, hints);
}
/**
@@ -290,18 +291,21 @@ public class IndexedQueryAnalyzer<S extends Storable> { private final StorableIndex<S> mLocalIndex;
private final StorableIndex<?> mForeignIndex;
private final ChainedProperty<S> mForeignProperty;
+ private final QueryHints mHints;
Result(Filter<S> filter,
CompositeScore<S> score,
StorableIndex<S> localIndex,
StorableIndex<?> foreignIndex,
- ChainedProperty<S> foreignProperty)
+ ChainedProperty<S> foreignProperty,
+ QueryHints hints)
{
mFilter = filter;
mScore = score;
mLocalIndex = localIndex;
mForeignIndex = foreignIndex;
mForeignProperty = foreignProperty;
+ mHints = hints;
}
/**
@@ -434,7 +438,7 @@ public class IndexedQueryAnalyzer<S extends Storable> { .withRemainderFilter(remainderFilter)
.withRemainderOrdering(remainderOrdering);
- return new Result(filter, score, mLocalIndex, mForeignIndex, mForeignProperty);
+ return new Result(filter, score, mLocalIndex, mForeignIndex, mForeignProperty, mHints);
}
/**
@@ -471,7 +475,8 @@ public class IndexedQueryAnalyzer<S extends Storable> { */
public Result withRemainderFilter(Filter<S> remainderFilter) {
CompositeScore<S> score = mScore.withRemainderFilter(remainderFilter);
- return new Result(mFilter, score, mLocalIndex, mForeignIndex, mForeignProperty);
+ return new Result(mFilter, score, mLocalIndex,
+ mForeignIndex, mForeignProperty, mHints);
}
/**
@@ -479,7 +484,8 @@ public class IndexedQueryAnalyzer<S extends Storable> { */
public Result withRemainderOrdering(OrderingList<S> remainderOrdering) {
CompositeScore<S> score = mScore.withRemainderOrdering(remainderOrdering);
- return new Result(mFilter, score, mLocalIndex, mForeignIndex, mForeignProperty);
+ return new Result(mFilter, score, mLocalIndex,
+ mForeignIndex, mForeignProperty, mHints);
}
/**
@@ -506,7 +512,7 @@ public class IndexedQueryAnalyzer<S extends Storable> { } else if (localIndex == null) {
// Use foreign executor.
return JoinedQueryExecutor.build
- (mRepoAccess, getForeignProperty(), getFilter(), getOrdering());
+ (mRepoAccess, getForeignProperty(), getFilter(), getOrdering(), mHints);
} else {
CompositeScore<S> score = getCompositeScore();
FilteringScore<S> fScore = score.getFilteringScore();
diff --git a/src/main/java/com/amazon/carbonado/qe/JoinedQueryExecutor.java b/src/main/java/com/amazon/carbonado/qe/JoinedQueryExecutor.java index 12238a6..5c11157 100644 --- a/src/main/java/com/amazon/carbonado/qe/JoinedQueryExecutor.java +++ b/src/main/java/com/amazon/carbonado/qe/JoinedQueryExecutor.java @@ -78,6 +78,7 @@ public class JoinedQueryExecutor<S extends Storable, T extends Storable> * to instances of <i>source</i> type
* @param targetFilter optional filter for fetching <i>target</i> instances
* @param targetOrdering optional ordering to apply to <i>target</i> executor
+ & @param hints optional hints
* @throws IllegalArgumentException if any parameter is null or if join
* property is not a Storable type
* @throws RepositoryException from RepositoryAccess
@@ -86,7 +87,8 @@ public class JoinedQueryExecutor<S extends Storable, T extends Storable> build(RepositoryAccess repoAccess,
ChainedProperty<T> targetToSourceProperty,
Filter<T> targetFilter,
- OrderingList<T> targetOrdering)
+ OrderingList<T> targetOrdering,
+ QueryHints hints)
throws RepositoryException
{
if (targetOrdering == null) {
@@ -94,7 +96,7 @@ public class JoinedQueryExecutor<S extends Storable, T extends Storable> }
QueryExecutor<T> executor =
- buildJoin(repoAccess, targetToSourceProperty, targetFilter, targetOrdering);
+ buildJoin(repoAccess, targetToSourceProperty, targetFilter, targetOrdering, hints);
OrderingList<T> handledOrdering = executor.getOrdering();
@@ -118,7 +120,8 @@ public class JoinedQueryExecutor<S extends Storable, T extends Storable> buildJoin(RepositoryAccess repoAccess,
ChainedProperty<T> targetToSourceProperty,
Filter<T> targetFilter,
- OrderingList<T> targetOrdering)
+ OrderingList<T> targetOrdering,
+ QueryHints hints)
throws RepositoryException
{
StorableProperty<T> primeTarget = targetToSourceProperty.getPrimeProperty();
@@ -140,7 +143,8 @@ public class JoinedQueryExecutor<S extends Storable, T extends Storable> QueryExecutor outerLoopExecutor;
if (targetToSourceProperty.getChainCount() > 0) {
ChainedProperty tailProperty = targetToSourceProperty.tail();
- outerLoopExecutor = buildJoin(repoAccess, tailProperty, tailFilter, outerLoopOrdering);
+ outerLoopExecutor = buildJoin
+ (repoAccess, tailProperty, tailFilter, outerLoopOrdering, hints);
} else {
Class sourceType = targetToSourceProperty.getType();
@@ -156,7 +160,8 @@ public class JoinedQueryExecutor<S extends Storable, T extends Storable> QueryExecutorFactory outerLoopExecutorFactory = sourceAccess.getQueryExecutorFactory();
- outerLoopExecutor = outerLoopExecutorFactory.executor(tailFilter, expectedOrdering);
+ outerLoopExecutor = outerLoopExecutorFactory
+ .executor(tailFilter, expectedOrdering, hints);
}
if (targetOrdering.size() > 0) {
@@ -532,7 +537,7 @@ public class JoinedQueryExecutor<S extends Storable, T extends Storable> }
mInnerLoopExecutor = innerLoopExecutorFactory
- .executor(innerLoopExecutorFilter, targetOrdering);
+ .executor(innerLoopExecutorFilter, targetOrdering, null);
Filter<T> filter = outerLoopExecutor.getFilter()
.asJoinedFrom(ChainedProperty.get(targetToSourceProperty));
diff --git a/src/main/java/com/amazon/carbonado/qe/QueryEngine.java b/src/main/java/com/amazon/carbonado/qe/QueryEngine.java index 8218b66..8e9d642 100644 --- a/src/main/java/com/amazon/carbonado/qe/QueryEngine.java +++ b/src/main/java/com/amazon/carbonado/qe/QueryEngine.java @@ -43,26 +43,27 @@ public class QueryEngine<S extends Storable> extends StandardQueryFactory<S> mExecutorFactory = new QueryExecutorCache<S>(new UnionQueryAnalyzer<S>(type, access));
}
- public QueryExecutor<S> executor(Filter<S> filter, OrderingList<S> ordering)
+ public QueryExecutor<S> executor(Filter<S> filter, OrderingList<S> ordering, QueryHints hints)
throws RepositoryException
{
- return mExecutorFactory.executor(filter, ordering);
+ return mExecutorFactory.executor(filter, ordering, hints);
}
protected StandardQuery<S> createQuery(Filter<S> filter,
FilterValues<S> values,
- OrderingList<S> ordering)
+ OrderingList<S> ordering,
+ QueryHints hints)
{
- return new Query(filter, values, ordering, null);
+ return new Query(filter, values, ordering, hints);
}
private class Query extends StandardQuery<S> {
Query(Filter<S> filter,
FilterValues<S> values,
OrderingList<S> ordering,
- QueryExecutor<S> executor)
+ QueryHints hints)
{
- super(filter, values, ordering, executor);
+ super(filter, values, ordering, hints);
}
protected Transaction enterTransaction(IsolationLevel level) {
@@ -79,9 +80,9 @@ public class QueryEngine<S extends Storable> extends StandardQueryFactory<S> protected StandardQuery<S> newInstance(FilterValues<S> values,
OrderingList<S> ordering,
- QueryExecutor<S> executor)
+ QueryHints hints)
{
- return new Query(values.getFilter(), values, ordering, executor);
+ return new Query(values.getFilter(), values, ordering, hints);
}
}
}
diff --git a/src/main/java/com/amazon/carbonado/qe/QueryExecutorCache.java b/src/main/java/com/amazon/carbonado/qe/QueryExecutorCache.java index ab333a7..286d3e4 100644 --- a/src/main/java/com/amazon/carbonado/qe/QueryExecutorCache.java +++ b/src/main/java/com/amazon/carbonado/qe/QueryExecutorCache.java @@ -37,8 +37,8 @@ import com.amazon.carbonado.filter.Filter; 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;
+ // Maps filters to maps which map ordering lists (possibly with hints) to executors.
+ private final Map<Filter<S>, Map<Object, QueryExecutor<S>>> mFilterToExecutor;
public QueryExecutorCache(QueryExecutorFactory<S> factory) {
if (factory == null) {
@@ -57,11 +57,12 @@ public class QueryExecutorCache<S extends Storable> implements QueryExecutorFact *
* @param filter optional filter
* @param ordering optional order-by properties
+ * @param hints optional query hints
*/
- public QueryExecutor<S> executor(Filter<S> filter, OrderingList<S> ordering)
+ public QueryExecutor<S> executor(Filter<S> filter, OrderingList<S> ordering, QueryHints hints)
throws RepositoryException
{
- Map<OrderingList<S>, QueryExecutor<S>> map;
+ Map<Object, QueryExecutor<S>> map;
synchronized (mFilterToExecutor) {
map = mFilterToExecutor.get(filter);
if (map == null) {
@@ -70,15 +71,51 @@ public class QueryExecutorCache<S extends Storable> implements QueryExecutorFact }
}
+ Object key;
+ if (hints == null || hints.isEmpty()) {
+ key = ordering;
+ } else {
+ key = new WithHintsKey(ordering, hints);
+ }
+
QueryExecutor<S> executor;
synchronized (map) {
- executor = map.get(ordering);
+ executor = map.get(key);
if (executor == null) {
- executor = mFactory.executor(filter, ordering);
- map.put(ordering, executor);
+ executor = mFactory.executor(filter, ordering, hints);
+ map.put(key, executor);
}
}
return executor;
}
+
+ private static class WithHintsKey {
+ private final OrderingList mOrdering;
+ private final QueryHints mHints;
+
+ WithHintsKey(OrderingList ordering, QueryHints hints) {
+ mOrdering = ordering;
+ mHints = hints;
+ }
+
+ @Override
+ public int hashCode() {
+ return mOrdering == null ? 0 : (mOrdering.hashCode() * 31) + mHints.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof WithHintsKey) {
+ WithHintsKey other = (WithHintsKey) obj;
+ return (mOrdering == null ? other.mOrdering == null :
+ mOrdering.equals(other.mOrdering)) &&
+ mHints.equals(other.mHints);
+ }
+ return false;
+ }
+ }
}
diff --git a/src/main/java/com/amazon/carbonado/qe/QueryExecutorFactory.java b/src/main/java/com/amazon/carbonado/qe/QueryExecutorFactory.java index 5568f01..4d8e45a 100644 --- a/src/main/java/com/amazon/carbonado/qe/QueryExecutorFactory.java +++ b/src/main/java/com/amazon/carbonado/qe/QueryExecutorFactory.java @@ -36,7 +36,8 @@ public interface QueryExecutorFactory<S extends Storable> { *
* @param filter optional filter
* @param ordering optional order-by properties
+ * @param hints optional query hints
*/
- QueryExecutor<S> executor(Filter<S> filter, OrderingList<S> ordering)
+ QueryExecutor<S> executor(Filter<S> filter, OrderingList<S> ordering, QueryHints hints)
throws RepositoryException;
}
diff --git a/src/main/java/com/amazon/carbonado/qe/QueryFactory.java b/src/main/java/com/amazon/carbonado/qe/QueryFactory.java index ed1bfb4..cb51a41 100644 --- a/src/main/java/com/amazon/carbonado/qe/QueryFactory.java +++ b/src/main/java/com/amazon/carbonado/qe/QueryFactory.java @@ -39,7 +39,9 @@ public interface QueryFactory<S extends Storable> { * @param filter optional filter object, defaults to open filter if null
* @param values optional values object, defaults to filter initial values
* @param ordering optional order-by properties
+ * @param hints optional hints
*/
- Query<S> query(Filter<S> filter, FilterValues<S> values, OrderingList<S> ordering)
+ Query<S> query(Filter<S> filter, FilterValues<S> values, OrderingList<S> ordering,
+ QueryHints hints)
throws FetchException;
}
diff --git a/src/main/java/com/amazon/carbonado/qe/QueryHint.java b/src/main/java/com/amazon/carbonado/qe/QueryHint.java new file mode 100644 index 0000000..870eb0b --- /dev/null +++ b/src/main/java/com/amazon/carbonado/qe/QueryHint.java @@ -0,0 +1,40 @@ +/*
+ * Copyright 2008 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;
+
+/**
+ * Defines a hint key.
+ *
+ * @author Brian S O'Neill
+ * @see QueryHints
+ * @since 1.2
+ */
+public enum QueryHint {
+ /** Intention to consume all matched records */
+ //CONSUME_ALL,
+
+ /** Intention to consume a slice of matched records */
+ CONSUME_SLICE,
+
+ /** Favor low latency for query results */
+ //FAVOR_LATENCY,
+
+ /** Favor high throughput for query results */
+ //FAVOR_THROUGHPUT,
+}
diff --git a/src/main/java/com/amazon/carbonado/qe/QueryHints.java b/src/main/java/com/amazon/carbonado/qe/QueryHints.java new file mode 100644 index 0000000..607921f --- /dev/null +++ b/src/main/java/com/amazon/carbonado/qe/QueryHints.java @@ -0,0 +1,137 @@ +/*
+ * Copyright 2008 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.EnumMap;
+
+/**
+ * An immutable map of query hints.
+ *
+ * @author Brian S O'Neill
+ * @see QueryHint
+ * @since 1.2
+ */
+public class QueryHints implements java.io.Serializable {
+ private static final long serialVersionUID = 1L;
+ private static final QueryHints EMPTY_HINTS = new QueryHints(null);
+
+ public static QueryHints emptyHints() {
+ return EMPTY_HINTS;
+ }
+
+ private final EnumMap<QueryHint, Object> mMap;
+
+ private QueryHints(EnumMap<QueryHint, Object> map) {
+ mMap = map;
+ }
+
+ /**
+ * Returns a new QueryHints object with the given hint. The associated
+ * value is the hint object itself.
+ *
+ * @throws IllegalArgumentException if hint is null
+ */
+ public QueryHints with(QueryHint hint) {
+ return with(hint, hint);
+ }
+
+ /**
+ * Returns a new QueryHints object with the given hint and value.
+ *
+ * @throws IllegalArgumentException if hint or value is null
+ */
+ public QueryHints with(QueryHint hint, Object value) {
+ if (hint == null) {
+ throw new IllegalArgumentException("Null hint");
+ }
+ if (value == null) {
+ throw new IllegalArgumentException("Null value");
+ }
+ EnumMap<QueryHint, Object> map;
+ if (mMap == null) {
+ map = new EnumMap<QueryHint, Object>(QueryHint.class);
+ } else {
+ map = mMap.clone();
+ }
+ map.put(hint, value);
+ return new QueryHints(map);
+ }
+
+ /**
+ * Returns a new QueryHints object without the given hint.
+ */
+ public QueryHints without(QueryHint hint) {
+ if (hint == null || mMap == null || !mMap.containsKey(hint)) {
+ return this;
+ }
+ EnumMap<QueryHint, Object> map = mMap.clone();
+ map.remove(hint);
+ if (map.size() == 0) {
+ map = null;
+ }
+ return new QueryHints(map);
+ }
+
+ /**
+ * Returns false if hint is not provided.
+ */
+ public boolean contains(QueryHint hint) {
+ return get(hint) != null;
+ }
+
+ /**
+ * Returns null if hint is not provided.
+ */
+ public Object get(QueryHint hint) {
+ return hint == null ? null : (mMap == null ? null : mMap.get(hint));
+ }
+
+ public boolean isEmpty() {
+ return mMap == null ? true : mMap.isEmpty();
+ }
+
+ @Override
+ public int hashCode() {
+ return mMap == null ? 0 : mMap.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof QueryHints) {
+ QueryHints other = (QueryHints) obj;
+ if (mMap == null) {
+ return other.mMap == null;
+ } else {
+ return mMap.equals(other.mMap);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ if (mMap == null) {
+ return "QueryHints: {}";
+ }
+ return "QueryHints: " + mMap;
+ }
+}
diff --git a/src/main/java/com/amazon/carbonado/qe/StandardQuery.java b/src/main/java/com/amazon/carbonado/qe/StandardQuery.java index 3b5410c..5596c32 100644 --- a/src/main/java/com/amazon/carbonado/qe/StandardQuery.java +++ b/src/main/java/com/amazon/carbonado/qe/StandardQuery.java @@ -53,6 +53,8 @@ public abstract class StandardQuery<S extends Storable> extends AbstractQuery<S> private final FilterValues<S> mValues;
// Properties that this query is ordered by.
private final OrderingList<S> mOrdering;
+ // Optional query hints.
+ private final QueryHints mHints;
private volatile QueryExecutor<S> mExecutor;
@@ -60,12 +62,12 @@ public abstract class StandardQuery<S extends Storable> extends AbstractQuery<S> * @param filter optional filter object, defaults to open filter if null
* @param values optional values object, defaults to filter initial values
* @param ordering optional order-by properties
- * @param executor optional executor to use (by default lazily obtains and caches executor)
+ * @param hints optional query hints
*/
protected StandardQuery(Filter<S> filter,
FilterValues<S> values,
OrderingList<S> ordering,
- QueryExecutor<S> executor)
+ QueryHints hints)
{
if (filter != null && filter.isOpen()) {
filter = null;
@@ -84,7 +86,8 @@ public abstract class StandardQuery<S extends Storable> extends AbstractQuery<S> ordering = OrderingList.emptyList();
}
mOrdering = ordering;
- mExecutor = executor;
+
+ mHints = hints;
}
public Class<S> getStorableType() {
@@ -166,7 +169,7 @@ public abstract class StandardQuery<S extends Storable> extends AbstractQuery<S> newValues = newValues.withValues(mValues.getSuppliedValues());
}
}
- return createQuery(newFilter, newValues, mOrdering);
+ return createQuery(newFilter, newValues, mOrdering, mHints);
}
public Query<S> or(Filter<S> filter) throws FetchException {
@@ -181,7 +184,7 @@ public abstract class StandardQuery<S extends Storable> extends AbstractQuery<S> if (mValues != null) {
newValues = newValues.withValues(mValues.getSuppliedValues());
}
- return createQuery(newFilter, newValues, mOrdering);
+ return createQuery(newFilter, newValues, mOrdering, mHints);
}
public Query<S> not() throws FetchException {
@@ -193,15 +196,17 @@ public abstract class StandardQuery<S extends Storable> extends AbstractQuery<S> if (mValues != null) {
newValues = newValues.withValues(mValues.getSuppliedValues());
}
- return createQuery(newFilter, newValues, mOrdering);
+ return createQuery(newFilter, newValues, mOrdering, mHints);
}
public Query<S> orderBy(String property) throws FetchException {
- return createQuery(mFilter, mValues, OrderingList.get(getStorableType(), property));
+ return createQuery(mFilter, mValues,
+ OrderingList.get(getStorableType(), property), mHints);
}
public Query<S> orderBy(String... properties) throws FetchException {
- return createQuery(mFilter, mValues, OrderingList.get(getStorableType(), properties));
+ return createQuery(mFilter, mValues,
+ OrderingList.get(getStorableType(), properties), mHints);
}
public Cursor<S> fetch() throws FetchException {
@@ -217,7 +222,10 @@ public abstract class StandardQuery<S extends Storable> extends AbstractQuery<S> if (start == null || (orderings = mOrdering).size() == 0) {
return fetch();
}
+ return buildAfter(start, orderings).fetch();
+ }
+ private Query<S> buildAfter(S start, OrderingList<S> orderings) throws FetchException {
Class<S> storableType = getStorableType();
Filter<S> orderFilter = Filter.getClosedFilter(storableType);
Filter<S> lastSubFilter = Filter.getOpenFilter(storableType);
@@ -251,7 +259,7 @@ public abstract class StandardQuery<S extends Storable> extends AbstractQuery<S> }
}
- return query.fetch();
+ return query;
}
public boolean tryDeleteOne() throws PersistException {
@@ -406,13 +414,17 @@ public abstract class StandardQuery<S extends Storable> extends AbstractQuery<S> return values;
}
+ protected OrderingList<S> getOrdering() {
+ return mOrdering;
+ }
+
/**
* Returns the executor in use by this query.
*/
protected QueryExecutor<S> executor() throws RepositoryException {
QueryExecutor<S> executor = mExecutor;
if (executor == null) {
- mExecutor = executor = executorFactory().executor(mFilter, mOrdering);
+ mExecutor = executor = executorFactory().executor(mFilter, mOrdering, null);
}
return executor;
}
@@ -431,7 +443,7 @@ public abstract class StandardQuery<S extends Storable> extends AbstractQuery<S> */
protected void resetExecutor() throws RepositoryException {
if (mExecutor != null) {
- mExecutor = executorFactory().executor(mFilter, mOrdering);
+ mExecutor = executorFactory().executor(mFilter, mOrdering, null);
}
}
@@ -472,17 +484,20 @@ public abstract class StandardQuery<S extends Storable> extends AbstractQuery<S> */
protected abstract StandardQuery<S> newInstance(FilterValues<S> values,
OrderingList<S> ordering,
- QueryExecutor<S> executor);
+ QueryHints hints);
private StandardQuery<S> newInstance(FilterValues<S> values) {
- return newInstance(values, mOrdering, mExecutor);
+ StandardQuery<S> query = newInstance(values, mOrdering, mHints);
+ query.mExecutor = this.mExecutor;
+ return query;
}
private Query<S> createQuery(Filter<S> filter,
FilterValues<S> values,
- OrderingList<S> ordering)
+ OrderingList<S> ordering,
+ QueryHints hints)
throws FetchException
{
- return queryFactory().query(filter, values, ordering);
+ return queryFactory().query(filter, values, ordering, hints);
}
}
diff --git a/src/main/java/com/amazon/carbonado/qe/StandardQueryFactory.java b/src/main/java/com/amazon/carbonado/qe/StandardQueryFactory.java index 1ce509b..2faace7 100644 --- a/src/main/java/com/amazon/carbonado/qe/StandardQueryFactory.java +++ b/src/main/java/com/amazon/carbonado/qe/StandardQueryFactory.java @@ -108,6 +108,17 @@ public abstract class StandardQueryFactory<S extends Storable> implements QueryF * @throws IllegalArgumentException if filter is null
*/
public Query<S> query(Filter<S> filter, OrderingList<S> ordering) throws FetchException {
+ return query(filter, ordering, 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, QueryHints hints)
+ throws FetchException
+ {
filter = filter.bind();
Map<OrderingList<S>, Query<S>> map;
@@ -130,7 +141,7 @@ public abstract class StandardQueryFactory<S extends Storable> implements QueryF if (values == null && filter.isClosed()) {
query = new EmptyQuery<S>(this, ordering);
} else {
- StandardQuery<S> standardQuery = createQuery(filter, values, ordering);
+ StandardQuery<S> standardQuery = createQuery(filter, values, ordering, hints);
if (!mLazySetExecutor) {
try {
standardQuery.setExecutor();
@@ -157,7 +168,23 @@ public abstract class StandardQueryFactory<S extends Storable> implements QueryF public Query<S> query(Filter<S> filter, FilterValues<S> values, OrderingList<S> ordering)
throws FetchException
{
- Query<S> query = query(filter != null ? filter : Filter.getOpenFilter(mType), ordering);
+ return query(filter, values, ordering, null);
+ }
+
+ /**
+ * Returns a new or cached query for the given query specification.
+ *
+ * @param filter optional filter object, defaults to open filter if null
+ * @param values optional values object, defaults to filter initial values
+ * @param ordering optional order-by properties
+ * @param hints optional hints
+ */
+ public Query<S> query(Filter<S> filter, FilterValues<S> values, OrderingList<S> ordering,
+ QueryHints hints)
+ throws FetchException
+ {
+ Query<S> query = query(filter != null ? filter : Filter.getOpenFilter(mType),
+ ordering, hints);
if (values != null) {
query = query.withValues(values.getSuppliedValues());
@@ -203,10 +230,12 @@ public abstract class StandardQueryFactory<S extends Storable> implements QueryF * @param filter optional filter object, defaults to open filter if null
* @param values optional values object, defaults to filter initial values
* @param ordering optional order-by properties
+ * @param hints optional hints
*/
protected abstract StandardQuery<S> createQuery(Filter<S> filter,
FilterValues<S> values,
- OrderingList<S> ordering)
+ OrderingList<S> ordering,
+ QueryHints hints)
throws FetchException;
private ArrayList<StandardQuery<S>> gatherQueries() {
diff --git a/src/main/java/com/amazon/carbonado/qe/UnionQueryAnalyzer.java b/src/main/java/com/amazon/carbonado/qe/UnionQueryAnalyzer.java index 1771c71..67da7ee 100644 --- a/src/main/java/com/amazon/carbonado/qe/UnionQueryAnalyzer.java +++ b/src/main/java/com/amazon/carbonado/qe/UnionQueryAnalyzer.java @@ -80,8 +80,9 @@ public class UnionQueryAnalyzer<S extends Storable> implements QueryExecutorFact /**
* @param filter optional filter which must be {@link Filter#isBound bound}
* @param ordering optional properties which define desired ordering
+ * @param hints optional query hints
*/
- public Result analyze(Filter<S> filter, OrderingList<S> ordering)
+ public Result analyze(Filter<S> filter, OrderingList<S> ordering, QueryHints hints)
throws SupportException, RepositoryException
{
if (filter != null && !filter.isBound()) {
@@ -92,7 +93,7 @@ public class UnionQueryAnalyzer<S extends Storable> implements QueryExecutorFact ordering = OrderingList.emptyList();
}
- return buildResult(filter, ordering);
+ return buildResult(filter, ordering, hints);
}
/**
@@ -100,25 +101,27 @@ public class UnionQueryAnalyzer<S extends Storable> implements QueryExecutorFact *
* @param filter optional filter which must be {@link Filter#isBound bound}
* @param ordering optional properties which define desired ordering
+ * @param hints optional query hints
*/
- public QueryExecutor<S> executor(Filter<S> filter, OrderingList<S> ordering)
+ public QueryExecutor<S> executor(Filter<S> filter, OrderingList<S> ordering, QueryHints hints)
throws RepositoryException
{
- return analyze(filter, ordering).createExecutor();
+ return analyze(filter, ordering, hints).createExecutor();
}
/**
* Splits the filter into sub-results, merges sub-results, and possibly
* imposes a total ordering.
*/
- private Result buildResult(Filter<S> filter, OrderingList<S> ordering)
+ private Result buildResult(Filter<S> filter, OrderingList<S> ordering, QueryHints hints)
throws SupportException, RepositoryException
{
List<IndexedQueryAnalyzer<S>.Result> subResults;
if (filter == null) {
- subResults = Collections.singletonList(mIndexAnalyzer.analyze(filter, ordering));
+ subResults = Collections
+ .singletonList(mIndexAnalyzer.analyze(filter, ordering, hints));
} else {
- subResults = splitIntoSubResults(filter, ordering);
+ subResults = splitIntoSubResults(filter, ordering, hints);
}
if (subResults.size() <= 1) {
@@ -145,7 +148,7 @@ public class UnionQueryAnalyzer<S extends Storable> implements QueryExecutorFact // Re-calc with specified direction. Only do one property at a time
// since one simple change might alter the query plan.
- subResults = splitIntoSubResults(filter, ordering);
+ subResults = splitIntoSubResults(filter, ordering, hints);
if (subResults.size() <= 1) {
// Total ordering no longer required.
@@ -225,7 +228,7 @@ public class UnionQueryAnalyzer<S extends Storable> implements QueryExecutorFact // Now augment the orderings and create new sub-results.
ordering = ordering.concat(OrderedProperty.get(bestProperty, best.getBestDirection()));
- subResults = splitIntoSubResults(filter, ordering);
+ subResults = splitIntoSubResults(filter, ordering, hints);
if (subResults.size() <= 1) {
// Total ordering no longer required.
@@ -342,13 +345,13 @@ public class UnionQueryAnalyzer<S extends Storable> implements QueryExecutorFact * Splits the filter into sub-results and possibly merges them.
*/
private List<IndexedQueryAnalyzer<S>.Result>
- splitIntoSubResults(Filter<S> filter, OrderingList<S> ordering)
+ splitIntoSubResults(Filter<S> filter, OrderingList<S> ordering, QueryHints hints)
throws SupportException, RepositoryException
{
// Required for split to work.
Filter<S> dnfFilter = filter.disjunctiveNormalForm();
- Splitter splitter = new Splitter(ordering);
+ Splitter splitter = new Splitter(ordering, hints);
RepositoryException e = dnfFilter.accept(splitter, null);
if (e != null) {
throw e;
@@ -569,11 +572,13 @@ public class UnionQueryAnalyzer<S extends Storable> implements QueryExecutorFact */
private class Splitter extends Visitor<S, RepositoryException, Object> {
private final OrderingList<S> mOrdering;
+ private final QueryHints mHints;
final List<IndexedQueryAnalyzer<S>.Result> mSubResults;
- Splitter(OrderingList<S> ordering) {
+ Splitter(OrderingList<S> ordering, QueryHints hints) {
mOrdering = ordering;
+ mHints = hints;
mSubResults = new ArrayList<IndexedQueryAnalyzer<S>.Result>();
}
@@ -639,7 +644,7 @@ public class UnionQueryAnalyzer<S extends Storable> implements QueryExecutorFact private void subAnalyze(Filter<S> subFilter) throws SupportException, RepositoryException {
IndexedQueryAnalyzer<S>.Result subResult =
- mIndexAnalyzer.analyze(subFilter, mOrdering);
+ mIndexAnalyzer.analyze(subFilter, mOrdering, mHints);
// Rather than blindly add to mSubResults, try to merge with
// another result. This in turn reduces the number of cursors
diff --git a/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCStorage.java b/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCStorage.java index 942ebee..598f9f6 100644 --- a/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCStorage.java +++ b/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCStorage.java @@ -62,6 +62,7 @@ import com.amazon.carbonado.qe.QueryExecutor; import com.amazon.carbonado.qe.QueryExecutorCache;
import com.amazon.carbonado.qe.QueryExecutorFactory;
import com.amazon.carbonado.qe.QueryFactory;
+import com.amazon.carbonado.qe.QueryHints;
import com.amazon.carbonado.qe.SortedQueryExecutor;
import com.amazon.carbonado.qe.StandardQuery;
import com.amazon.carbonado.qe.StandardQueryFactory;
@@ -291,9 +292,10 @@ class JDBCStorage<S extends Storable> extends StandardQueryFactory<S> protected StandardQuery<S> createQuery(Filter<S> filter,
FilterValues<S> values,
- OrderingList<S> ordering)
+ OrderingList<S> ordering,
+ QueryHints hints)
{
- return new JDBCQuery(filter, values, ordering, null);
+ return new JDBCQuery(filter, values, ordering, hints);
}
public S instantiate(ResultSet rs) throws SQLException {
@@ -307,11 +309,15 @@ class JDBCStorage<S extends Storable> extends StandardQueryFactory<S> }
private class ExecutorFactory implements QueryExecutorFactory<S> {
+ ExecutorFactory() {
+ }
+
public Class<S> getStorableType() {
return JDBCStorage.this.getStorableType();
}
- public QueryExecutor<S> executor(Filter<S> filter, OrderingList<S> ordering)
+ public QueryExecutor<S> executor(Filter<S> filter, OrderingList<S> ordering,
+ QueryHints hints)
throws RepositoryException
{
TableAliasGenerator aliasGenerator = new TableAliasGenerator();
@@ -594,9 +600,11 @@ class JDBCStorage<S extends Storable> extends StandardQueryFactory<S> // Special case for converting character to String.
if (mAdapterMethods[i] == null) {
if (toType == String.class) {
- mAdapterMethods[i] = adapter.findAdaptMethod(jProperty.getType(), Character.class);
+ mAdapterMethods[i] = adapter
+ .findAdaptMethod(jProperty.getType(), Character.class);
if (mAdapterMethods[i] == null) {
- mAdapterMethods[i] = adapter.findAdaptMethod(jProperty.getType(), char.class);
+ mAdapterMethods[i] = adapter
+ .findAdaptMethod(jProperty.getType(), char.class);
}
}
}
@@ -798,9 +806,9 @@ class JDBCStorage<S extends Storable> extends StandardQueryFactory<S> JDBCQuery(Filter<S> filter,
FilterValues<S> values,
OrderingList<S> ordering,
- QueryExecutor<S> executor)
+ QueryHints hints)
{
- super(filter, values, ordering, executor);
+ super(filter, values, ordering, hints);
}
@Override
@@ -830,11 +838,10 @@ class JDBCStorage<S extends Storable> extends StandardQueryFactory<S> return JDBCStorage.this.mExecutorFactory;
}
- protected StandardQuery<S> newInstance(FilterValues<S> values,
- OrderingList<S> ordering,
- QueryExecutor<S> executor)
+ protected StandardQuery<S> newInstance(FilterValues<S> values, OrderingList<S> ordering,
+ QueryHints hints)
{
- return new JDBCQuery(values.getFilter(), values, ordering, executor);
+ return new JDBCQuery(values.getFilter(), values, ordering, hints);
}
}
}
|