diff options
Diffstat (limited to 'src')
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);
          }
      }
  }
  | 
