From 755d0fa1dfa42d8987dd9dc2439f8a1f9c058fe9 Mon Sep 17 00:00:00 2001 From: "Brian S. O'Neill" Date: Sun, 6 Apr 2008 20:13:22 +0000 Subject: Added support for internal query hints. --- .../com/amazon/carbonado/qe/CompositeScore.java | 56 +++++++-- .../qe/DelegatedQueryExecutorFactory.java | 2 +- .../java/com/amazon/carbonado/qe/EmptyQuery.java | 18 +-- .../amazon/carbonado/qe/IndexedQueryAnalyzer.java | 24 ++-- .../amazon/carbonado/qe/JoinedQueryExecutor.java | 17 ++- .../java/com/amazon/carbonado/qe/QueryEngine.java | 17 +-- .../amazon/carbonado/qe/QueryExecutorCache.java | 51 ++++++-- .../amazon/carbonado/qe/QueryExecutorFactory.java | 3 +- .../java/com/amazon/carbonado/qe/QueryFactory.java | 4 +- .../java/com/amazon/carbonado/qe/QueryHint.java | 40 ++++++ .../java/com/amazon/carbonado/qe/QueryHints.java | 137 +++++++++++++++++++++ .../com/amazon/carbonado/qe/StandardQuery.java | 45 ++++--- .../amazon/carbonado/qe/StandardQueryFactory.java | 35 +++++- .../amazon/carbonado/qe/UnionQueryAnalyzer.java | 31 +++-- .../amazon/carbonado/repo/jdbc/JDBCStorage.java | 29 +++-- 15 files changed, 417 insertions(+), 92 deletions(-) create mode 100644 src/main/java/com/amazon/carbonado/qe/QueryHint.java create mode 100644 src/main/java/com/amazon/carbonado/qe/QueryHints.java (limited to 'src/main/java/com/amazon') 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 { * {@code 0} if equal, or {@code >0} if second is better. */ public static Comparator> 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> 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 { * {@code 0} if equal, or {@code >0} if second is better. */ public static Comparator> 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> fullComparator(QueryHints hints) { + if (hints != null && hints.contains(QueryHint.CONSUME_SLICE)) { + return Comp.SLICE; + } return Comp.FULL; } @@ -189,13 +223,17 @@ public class CompositeScore { } private static class Comp implements Comparator> { - static final Comparator> LOCAL_FOREIGN = new Comp(false); - static final Comparator> FULL = new Comp(true); + static final Comparator> LOCAL_FOREIGN = new Comp(false, false); + static final Comparator> SLICE = new Comp(true, true); + static final Comparator> LOCAL_FOREIGN_SLICE = new Comp(false, true); + static final Comparator> 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 { 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 { } 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 implements QueryE return mStorage.getStorableType(); } - public QueryExecutor executor(Filter filter, OrderingList ordering) + public QueryExecutor executor(Filter filter, OrderingList ordering, QueryHints hints) throws FetchException { return new DelegatedQueryExecutor(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 @@ -106,13 +106,6 @@ public final class EmptyQuery extends AbstractQuery { return 0; } - /** - * Always returns false. - */ - public boolean exists() { - return false; - } - /** * Always throws an IllegalStateException. */ @@ -194,14 +187,14 @@ public final class EmptyQuery extends AbstractQuery { } public Query or(Filter 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 not() throws FetchException { - return mFactory.query(null, null, mOrdering); + return mFactory.query(null, null, mOrdering, null); } public Query orderBy(String property) throws FetchException { @@ -253,6 +246,13 @@ public final class EmptyQuery extends AbstractQuery { 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 { * @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 filter, OrderingList ordering) + public Result analyze(Filter filter, OrderingList ordering, QueryHints hints) throws SupportException, RepositoryException { if (filter != null && !filter.isBound()) { @@ -94,7 +95,7 @@ public class IndexedQueryAnalyzer { CompositeScore bestLocalScore = null; StorableIndex bestLocalIndex = null; - final Comparator> fullComparator = CompositeScore.fullComparator(); + final Comparator> fullComparator = CompositeScore.fullComparator(hints); Collection> localIndexes = indexesFor(getStorableType()); if (localIndexes != null) { @@ -115,7 +116,7 @@ public class IndexedQueryAnalyzer { 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 { } return new Result - (filter, bestScore, bestLocalIndex, bestForeignIndex, bestForeignProperty); + (filter, bestScore, bestLocalIndex, bestForeignIndex, bestForeignProperty, hints); } /** @@ -290,18 +291,21 @@ public class IndexedQueryAnalyzer { private final StorableIndex mLocalIndex; private final StorableIndex mForeignIndex; private final ChainedProperty mForeignProperty; + private final QueryHints mHints; Result(Filter filter, CompositeScore score, StorableIndex localIndex, StorableIndex foreignIndex, - ChainedProperty foreignProperty) + ChainedProperty foreignProperty, + QueryHints hints) { mFilter = filter; mScore = score; mLocalIndex = localIndex; mForeignIndex = foreignIndex; mForeignProperty = foreignProperty; + mHints = hints; } /** @@ -434,7 +438,7 @@ public class IndexedQueryAnalyzer { .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 { */ public Result withRemainderFilter(Filter remainderFilter) { CompositeScore 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 { */ public Result withRemainderOrdering(OrderingList remainderOrdering) { CompositeScore 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 { } else if (localIndex == null) { // Use foreign executor. return JoinedQueryExecutor.build - (mRepoAccess, getForeignProperty(), getFilter(), getOrdering()); + (mRepoAccess, getForeignProperty(), getFilter(), getOrdering(), mHints); } else { CompositeScore score = getCompositeScore(); FilteringScore 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 * to instances of source type * @param targetFilter optional filter for fetching target instances * @param targetOrdering optional ordering to apply to target 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 build(RepositoryAccess repoAccess, ChainedProperty targetToSourceProperty, Filter targetFilter, - OrderingList targetOrdering) + OrderingList targetOrdering, + QueryHints hints) throws RepositoryException { if (targetOrdering == null) { @@ -94,7 +96,7 @@ public class JoinedQueryExecutor } QueryExecutor executor = - buildJoin(repoAccess, targetToSourceProperty, targetFilter, targetOrdering); + buildJoin(repoAccess, targetToSourceProperty, targetFilter, targetOrdering, hints); OrderingList handledOrdering = executor.getOrdering(); @@ -118,7 +120,8 @@ public class JoinedQueryExecutor buildJoin(RepositoryAccess repoAccess, ChainedProperty targetToSourceProperty, Filter targetFilter, - OrderingList targetOrdering) + OrderingList targetOrdering, + QueryHints hints) throws RepositoryException { StorableProperty primeTarget = targetToSourceProperty.getPrimeProperty(); @@ -140,7 +143,8 @@ public class JoinedQueryExecutor 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 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 } mInnerLoopExecutor = innerLoopExecutorFactory - .executor(innerLoopExecutorFilter, targetOrdering); + .executor(innerLoopExecutorFilter, targetOrdering, null); Filter 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 extends StandardQueryFactory mExecutorFactory = new QueryExecutorCache(new UnionQueryAnalyzer(type, access)); } - public QueryExecutor executor(Filter filter, OrderingList ordering) + public QueryExecutor executor(Filter filter, OrderingList ordering, QueryHints hints) throws RepositoryException { - return mExecutorFactory.executor(filter, ordering); + return mExecutorFactory.executor(filter, ordering, hints); } protected StandardQuery createQuery(Filter filter, FilterValues values, - OrderingList ordering) + OrderingList ordering, + QueryHints hints) { - return new Query(filter, values, ordering, null); + return new Query(filter, values, ordering, hints); } private class Query extends StandardQuery { Query(Filter filter, FilterValues values, OrderingList ordering, - QueryExecutor 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 extends StandardQueryFactory protected StandardQuery newInstance(FilterValues values, OrderingList ordering, - QueryExecutor 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 implements QueryExecutorFactory { private final QueryExecutorFactory mFactory; - // Maps filters to maps which map ordering lists to executors. - private final Map, Map, QueryExecutor>> mFilterToExecutor; + // Maps filters to maps which map ordering lists (possibly with hints) to executors. + private final Map, Map>> mFilterToExecutor; public QueryExecutorCache(QueryExecutorFactory factory) { if (factory == null) { @@ -57,11 +57,12 @@ public class QueryExecutorCache implements QueryExecutorFact * * @param filter optional filter * @param ordering optional order-by properties + * @param hints optional query hints */ - public QueryExecutor executor(Filter filter, OrderingList ordering) + public QueryExecutor executor(Filter filter, OrderingList ordering, QueryHints hints) throws RepositoryException { - Map, QueryExecutor> map; + Map> map; synchronized (mFilterToExecutor) { map = mFilterToExecutor.get(filter); if (map == null) { @@ -70,15 +71,51 @@ public class QueryExecutorCache implements QueryExecutorFact } } + Object key; + if (hints == null || hints.isEmpty()) { + key = ordering; + } else { + key = new WithHintsKey(ordering, hints); + } + QueryExecutor 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 { * * @param filter optional filter * @param ordering optional order-by properties + * @param hints optional query hints */ - QueryExecutor executor(Filter filter, OrderingList ordering) + QueryExecutor executor(Filter filter, OrderingList 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 { * @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 query(Filter filter, FilterValues values, OrderingList ordering) + Query query(Filter filter, FilterValues values, OrderingList 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 mMap; + + private QueryHints(EnumMap 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 map; + if (mMap == null) { + map = new EnumMap(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 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 extends AbstractQuery private final FilterValues mValues; // Properties that this query is ordered by. private final OrderingList mOrdering; + // Optional query hints. + private final QueryHints mHints; private volatile QueryExecutor mExecutor; @@ -60,12 +62,12 @@ public abstract class StandardQuery extends AbstractQuery * @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 filter, FilterValues values, OrderingList ordering, - QueryExecutor executor) + QueryHints hints) { if (filter != null && filter.isOpen()) { filter = null; @@ -84,7 +86,8 @@ public abstract class StandardQuery extends AbstractQuery ordering = OrderingList.emptyList(); } mOrdering = ordering; - mExecutor = executor; + + mHints = hints; } public Class getStorableType() { @@ -166,7 +169,7 @@ public abstract class StandardQuery extends AbstractQuery newValues = newValues.withValues(mValues.getSuppliedValues()); } } - return createQuery(newFilter, newValues, mOrdering); + return createQuery(newFilter, newValues, mOrdering, mHints); } public Query or(Filter filter) throws FetchException { @@ -181,7 +184,7 @@ public abstract class StandardQuery extends AbstractQuery if (mValues != null) { newValues = newValues.withValues(mValues.getSuppliedValues()); } - return createQuery(newFilter, newValues, mOrdering); + return createQuery(newFilter, newValues, mOrdering, mHints); } public Query not() throws FetchException { @@ -193,15 +196,17 @@ public abstract class StandardQuery extends AbstractQuery if (mValues != null) { newValues = newValues.withValues(mValues.getSuppliedValues()); } - return createQuery(newFilter, newValues, mOrdering); + return createQuery(newFilter, newValues, mOrdering, mHints); } public Query orderBy(String property) throws FetchException { - return createQuery(mFilter, mValues, OrderingList.get(getStorableType(), property)); + return createQuery(mFilter, mValues, + OrderingList.get(getStorableType(), property), mHints); } public Query orderBy(String... properties) throws FetchException { - return createQuery(mFilter, mValues, OrderingList.get(getStorableType(), properties)); + return createQuery(mFilter, mValues, + OrderingList.get(getStorableType(), properties), mHints); } public Cursor fetch() throws FetchException { @@ -217,7 +222,10 @@ public abstract class StandardQuery extends AbstractQuery if (start == null || (orderings = mOrdering).size() == 0) { return fetch(); } + return buildAfter(start, orderings).fetch(); + } + private Query buildAfter(S start, OrderingList orderings) throws FetchException { Class storableType = getStorableType(); Filter orderFilter = Filter.getClosedFilter(storableType); Filter lastSubFilter = Filter.getOpenFilter(storableType); @@ -251,7 +259,7 @@ public abstract class StandardQuery extends AbstractQuery } } - return query.fetch(); + return query; } public boolean tryDeleteOne() throws PersistException { @@ -406,13 +414,17 @@ public abstract class StandardQuery extends AbstractQuery return values; } + protected OrderingList getOrdering() { + return mOrdering; + } + /** * Returns the executor in use by this query. */ protected QueryExecutor executor() throws RepositoryException { QueryExecutor 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 extends AbstractQuery */ 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 extends AbstractQuery */ protected abstract StandardQuery newInstance(FilterValues values, OrderingList ordering, - QueryExecutor executor); + QueryHints hints); private StandardQuery newInstance(FilterValues values) { - return newInstance(values, mOrdering, mExecutor); + StandardQuery query = newInstance(values, mOrdering, mHints); + query.mExecutor = this.mExecutor; + return query; } private Query createQuery(Filter filter, FilterValues values, - OrderingList ordering) + OrderingList 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 implements QueryF * @throws IllegalArgumentException if filter is null */ public Query query(Filter filter, OrderingList 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 query(Filter filter, OrderingList ordering, QueryHints hints) + throws FetchException + { filter = filter.bind(); Map, Query> map; @@ -130,7 +141,7 @@ public abstract class StandardQueryFactory implements QueryF if (values == null && filter.isClosed()) { query = new EmptyQuery(this, ordering); } else { - StandardQuery standardQuery = createQuery(filter, values, ordering); + StandardQuery standardQuery = createQuery(filter, values, ordering, hints); if (!mLazySetExecutor) { try { standardQuery.setExecutor(); @@ -157,7 +168,23 @@ public abstract class StandardQueryFactory implements QueryF public Query query(Filter filter, FilterValues values, OrderingList ordering) throws FetchException { - Query 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 query(Filter filter, FilterValues values, OrderingList ordering, + QueryHints hints) + throws FetchException + { + Query 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 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 createQuery(Filter filter, FilterValues values, - OrderingList ordering) + OrderingList ordering, + QueryHints hints) throws FetchException; private ArrayList> 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 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 filter, OrderingList ordering) + public Result analyze(Filter filter, OrderingList ordering, QueryHints hints) throws SupportException, RepositoryException { if (filter != null && !filter.isBound()) { @@ -92,7 +93,7 @@ public class UnionQueryAnalyzer implements QueryExecutorFact ordering = OrderingList.emptyList(); } - return buildResult(filter, ordering); + return buildResult(filter, ordering, hints); } /** @@ -100,25 +101,27 @@ public class UnionQueryAnalyzer 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 executor(Filter filter, OrderingList ordering) + public QueryExecutor executor(Filter filter, OrderingList 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 filter, OrderingList ordering) + private Result buildResult(Filter filter, OrderingList ordering, QueryHints hints) throws SupportException, RepositoryException { List.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 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 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 implements QueryExecutorFact * Splits the filter into sub-results and possibly merges them. */ private List.Result> - splitIntoSubResults(Filter filter, OrderingList ordering) + splitIntoSubResults(Filter filter, OrderingList ordering, QueryHints hints) throws SupportException, RepositoryException { // Required for split to work. Filter 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 implements QueryExecutorFact */ private class Splitter extends Visitor { private final OrderingList mOrdering; + private final QueryHints mHints; final List.Result> mSubResults; - Splitter(OrderingList ordering) { + Splitter(OrderingList ordering, QueryHints hints) { mOrdering = ordering; + mHints = hints; mSubResults = new ArrayList.Result>(); } @@ -639,7 +644,7 @@ public class UnionQueryAnalyzer implements QueryExecutorFact private void subAnalyze(Filter subFilter) throws SupportException, RepositoryException { IndexedQueryAnalyzer.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 extends StandardQueryFactory protected StandardQuery createQuery(Filter filter, FilterValues values, - OrderingList ordering) + OrderingList 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 extends StandardQueryFactory } private class ExecutorFactory implements QueryExecutorFactory { + ExecutorFactory() { + } + public Class getStorableType() { return JDBCStorage.this.getStorableType(); } - public QueryExecutor executor(Filter filter, OrderingList ordering) + public QueryExecutor executor(Filter filter, OrderingList ordering, + QueryHints hints) throws RepositoryException { TableAliasGenerator aliasGenerator = new TableAliasGenerator(); @@ -594,9 +600,11 @@ class JDBCStorage extends StandardQueryFactory // 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 extends StandardQueryFactory JDBCQuery(Filter filter, FilterValues values, OrderingList ordering, - QueryExecutor executor) + QueryHints hints) { - super(filter, values, ordering, executor); + super(filter, values, ordering, hints); } @Override @@ -830,11 +838,10 @@ class JDBCStorage extends StandardQueryFactory return JDBCStorage.this.mExecutorFactory; } - protected StandardQuery newInstance(FilterValues values, - OrderingList ordering, - QueryExecutor executor) + protected StandardQuery newInstance(FilterValues values, OrderingList ordering, + QueryHints hints) { - return new JDBCQuery(values.getFilter(), values, ordering, executor); + return new JDBCQuery(values.getFilter(), values, ordering, hints); } } } -- cgit v1.2.3