/* * Copyright 2006-2012 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.Comparator; import com.amazon.carbonado.Storable; import com.amazon.carbonado.filter.Filter; import com.amazon.carbonado.info.OrderedProperty; import com.amazon.carbonado.info.StorableIndex; /** * Evaluates an index for how well it matches a query's desired filtering and * ordering. A composite score is not a single absolute value \u2013 instead it * has a relative weight when compared to other scores. * * @author Brian S O'Neill * @see FilteringScore * @see OrderingScore */ public class CompositeScore<S extends Storable> { /** * Evaluates the given index for its filtering and ordering capabilities * against the given filter and order-by properties. * * @param index index to evaluate * @param filter optional filter which cannot contain any logical 'or' operations. * @param ordering optional properties which define desired ordering * @throws IllegalArgumentException if index is null or filter is not supported */ public static <S extends Storable> CompositeScore<S> evaluate (StorableIndex<S> index, Filter<S> filter, OrderingList<S> ordering) { if (index == null) { throw new IllegalArgumentException("Index required"); } return evaluate(index.getOrderedProperties(), index.isUnique(), index.isClustered(), filter, ordering); } /** * Evaluates the given index properties for its filtering and ordering * capabilities against the given filter and order-by properties. * * @param indexProperties index properties to evaluate * @param unique true if index is unique * @param clustered true if index is clustered * @param filter optional filter which cannot contain any logical 'or' operations. * @param ordering optional properties which define desired ordering * @throws IllegalArgumentException if index is null or filter is not supported */ public static <S extends Storable> CompositeScore<S> evaluate (OrderedProperty<S>[] indexProperties, boolean unique, boolean clustered, Filter<S> filter, OrderingList<S> ordering) { FilteringScore<S> filteringScore = FilteringScore .evaluate(indexProperties, unique, clustered, filter); OrderingScore<S> orderingScore = OrderingScore .evaluate(indexProperties, unique, clustered, filter, ordering); return new CompositeScore<S>(filteringScore, orderingScore); } /** * 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. */ 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; } /** * 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. */ 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; } private final FilteringScore<S> mFilteringScore; private final OrderingScore<S> mOrderingScore; private CompositeScore(FilteringScore<S> filteringScore, OrderingScore<S> orderingScore) { mFilteringScore = filteringScore; mOrderingScore = orderingScore; } /** * Returns the score on how well the evaluated index performs the desired * filtering. */ public FilteringScore<S> getFilteringScore() { return mFilteringScore; } /** * Returns the score on how well the evaluated index performs the desired * ordering. */ public OrderingScore<S> getOrderingScore() { return mOrderingScore; } /** * Returns true if the filtering score can merge its remainder filter and * the ordering score can merge its remainder orderings. */ public boolean canMergeRemainder(CompositeScore<S> other) { return getFilteringScore().canMergeRemainderFilter(other.getFilteringScore()) && getOrderingScore().canMergeRemainderOrdering(other.getOrderingScore()); } /** * Merges the remainder filter of this score with the one given using an * 'or' operation. Call canMergeRemainder first to verify if the merge * makes any sense. */ public Filter<S> mergeRemainderFilter(CompositeScore<S> other) { return getFilteringScore().mergeRemainderFilter(other.getFilteringScore()); } /** * Merges the remainder orderings of this score with the one given. Call * canMergeRemainder first to verify if the merge makes any sense. */ public OrderingList<S> mergeRemainderOrdering(CompositeScore<S> other) { return getOrderingScore().mergeRemainderOrdering(other.getOrderingScore()); } /** * Returns a new CompositeScore with the filtering remainder replaced and * covering matches recalculated. Other matches are not recalculated. * * @since 1.2 */ public CompositeScore<S> withRemainderFilter(Filter<S> filter) { return new CompositeScore<S>(mFilteringScore.withRemainderFilter(filter), mOrderingScore); } /** * Returns a new CompositeScore with the ordering remainder * replaced. Handled count is not recalculated. * * @since 1.2 */ public CompositeScore<S> withRemainderOrdering(OrderingList<S> ordering) { return new CompositeScore<S>(mFilteringScore, mOrderingScore.withRemainderOrdering(ordering)); } @Override public String toString() { return "CompositeScore {" + getFilteringScore() + ", " + getOrderingScore() + '}'; } private static class Comp implements Comparator<CompositeScore<?>> { 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, boolean slice) { mFull = full; mSlice = slice; } public int compare(CompositeScore<?> first, CompositeScore<?> second) { int result = FilteringScore.nullCompare(first, second); if (result != 0) { return result; } FilteringScore<?> firstScore = first.getFilteringScore(); FilteringScore<?> secondScore = second.getFilteringScore(); result = FilteringScore.rangeComparator().compare(firstScore, secondScore); OrderingScore<?> firstOrderingScore = first.getOrderingScore(); OrderingScore<?> secondOrderingScore = second.getOrderingScore(); if (result != 0) { if (!firstScore.hasAnyMatches() || !secondScore.hasAnyMatches()) { // Return result if either index filters nothing. return result; } // negative: first is better, zero: same, positive: second is better int handledScore = secondOrderingScore.getHandledCount() - firstOrderingScore.getHandledCount(); if (handledScore == 0) { // Neither index handles ordering any better, so don't // bother examining that. return result; } if (Integer.signum(result) == Integer.signum(handledScore)) { // Index is better at both filtering and ordering. A double win. return result; } // This is a tough call. Both indexes perform some filtering, // but one index is clearly better at it. The other index is // clearly better for ordering, however. Without knowing how // many results can be filtered out, it isn't possible to // decide which index is better. Let the user decide in this // case, by examing the preference order of filter properties. int preferenceResult = secondScore.getPreferenceScore().compareTo(firstScore.getPreferenceScore()); if (preferenceResult != 0) { return preferenceResult; } // Okay, preference is not helping. If handled filter count is // the same, choose the better ordering. Why? Most likely a nearly // identical index was created specifically for ordering. One index // might look better for filtering just because it is clustered. if (firstScore.getHandledCount() == secondScore.getHandledCount()) { if (handledScore != 0) { return handledScore; } } // Just return the result for the better filtering index. return result; } // If this point is reached, the filtering score has not been // terribly helpful in deciding an index. Check the ordering score. boolean comparedOrdering = false; if (considerOrdering(firstScore) && considerOrdering(secondScore)) { // 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()); comparedOrdering = true; if (result != 0) { return result; } } if (!mFull) { // Favor index that has any matches. if (firstScore.hasAnyMatches()) { if (!secondScore.hasAnyMatches()) { return -1; } } else if (secondScore.hasAnyMatches()) { return 1; } return 0; } // Additional tests for full comparator. result = FilteringScore.fullComparator().compare(firstScore, secondScore); if (result != 0) { return result; } // Still no idea which is best. Compare ordering if not already // done so. if (!comparedOrdering) { result = OrderingScore.fullComparator() .compare(first.getOrderingScore(), second.getOrderingScore()); comparedOrdering = true; if (result != 0) { return result; } } // Finally, just favor index with fewer properties, under the // assumption that fewer properties means smaller sized records // that need to be read in. if (firstScore.getIndexPropertyCount() < secondScore.getIndexPropertyCount()) { return -1; } else if (firstScore.getIndexPropertyCount() > secondScore.getIndexPropertyCount()) { return 1; } return result; } private boolean considerOrdering(FilteringScore<?> score) { return mSlice || score.isIndexClustered() || score.getIdentityCount() > 0 || score.hasRangeMatch(); } } }