/* * Copyright 2006 Amazon Technologies, Inc. or its affiliates. * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks * of Amazon Technologies, Inc. or its affiliates. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.amazon.carbonado.qe; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import com.amazon.carbonado.Storable; import com.amazon.carbonado.filter.Filter; import com.amazon.carbonado.filter.PropertyFilter; import com.amazon.carbonado.filter.RelOp; import com.amazon.carbonado.info.ChainedProperty; import com.amazon.carbonado.info.OrderedProperty; import com.amazon.carbonado.info.StorableIndex; import com.amazon.carbonado.info.StorableProperty; /** * Analyzes a simple query specification and determines which index is best * suited for its execution. Query filters passed to this analyzer cannot * contain any 'or' operations. * *
IndexedQueryAnalyzer is sharable and thread-safe. An instance for a
* particular Storable type can be cached, avoiding repeated construction
* cost. In addition, the analyzer caches learned foreign indexes.
*
* @author Brian S O'Neill
* @see UnionQueryAnalyzer
*/
public class IndexedQueryAnalyzer {
private final Class mType;
private final IndexProvider mIndexProvider;
// Growable cache which maps join properties to lists of usable foreign indexes.
private Map> mForeignIndexCache;
/**
* @param type type of storable being queried
* @param indexProvider
* @throws IllegalArgumentException if type or indexProvider is null
*/
public IndexedQueryAnalyzer(Class type, IndexProvider indexProvider) {
if (type == null || indexProvider == null) {
throw new IllegalArgumentException();
}
mType = type;
mIndexProvider = indexProvider;
}
public Class getStorableType() {
return mType;
}
/**
* @param filter optional filter which which must be {@link Filter#isBound
* bound} and cannot contain any logical 'or' operations.
* @param orderings optional properties which define desired ordering
* @throws IllegalArgumentException if filter is not supported
*/
public Result analyze(Filter filter, List bestScore = null;
StorableIndex bestLocalIndex = null;
Collection index : localIndexes) {
CompositeScore candidateScore =
CompositeScore.evaluate(index, filter, orderings);
if (bestScore == null || comparator.compare(candidateScore, bestScore) < 0) {
bestScore = candidateScore;
bestLocalIndex = index;
}
}
}
// Now try to find better foreign index.
StorableIndex> bestForeignIndex = null;
ChainedProperty bestForeignProperty = null;
for (PropertyFilter propFilter : PropertyFilterList.get(filter)) {
ChainedProperty chainedProp = propFilter.getChainedProperty();
if (chainedProp.getChainCount() == 0) {
// Cannot possibly be a join, so move on.
continue;
}
ForeignIndexes foreignIndexes = getForeignIndexes(chainedProp);
if (foreignIndexes == null) {
continue;
}
for (StorableIndex> index : foreignIndexes.mIndexes) {
CompositeScore candidateScore = CompositeScore.evaluate
(foreignIndexes.getVirtualIndex(index),
index.isUnique(),
index.isClustered(),
filter,
orderings);
if (bestScore == null || comparator.compare(candidateScore, bestScore) < 0) {
bestScore = candidateScore;
bestLocalIndex = null;
bestForeignIndex = index;
bestForeignProperty = foreignIndexes.mProperty;
}
}
}
return new Result(filter,
bestScore,
bestLocalIndex,
bestForeignIndex,
bestForeignProperty);
}
/**
* @return null if no foreign indexes for property
*/
private synchronized ForeignIndexes getForeignIndexes(ChainedProperty chainedProp) {
// Remove the last property as it is expected to be a simple storable
// property instead of a joined Storable.
chainedProp = chainedProp.trim();
ForeignIndexes foreignIndexes = null;
if (mForeignIndexCache != null) {
foreignIndexes = mForeignIndexCache.get(chainedProp);
if (foreignIndexes != null || mForeignIndexCache.containsKey(chainedProp)) {
return foreignIndexes;
}
}
// Check if property chain is properly joined and indexed along the way.
evaluate: {
if (!isProperJoin(chainedProp.getPrimeProperty())) {
break evaluate;
}
int count = chainedProp.getChainCount();
for (int i=0; i(chainedProp, indexes);
}
if (mForeignIndexCache == null) {
mForeignIndexCache = Collections.synchronizedMap
(new HashMap>());
}
mForeignIndexCache.put(chainedProp, foreignIndexes);
return foreignIndexes;
}
/**
* Checks if the property is a join and its internal properties are fully
* indexed.
*/
private boolean isProperJoin(StorableProperty> property) {
if (!property.isJoin() || property.isQuery()) {
return false;
}
// Make up a filter over the join's internal properties and then search
// for an index that filters with no remainder.
Filter> filter = Filter.getOpenFilter((Class extends Storable>) property.getType());
int count = property.getJoinElementCount();
for (int i=0; i mFilter;
private final CompositeScore mScore;
private final StorableIndex mLocalIndex;
private final StorableIndex> mForeignIndex;
private final ChainedProperty mForeignProperty;
private final Filter mRemainderFilter;
private final List filter,
CompositeScore score,
StorableIndex localIndex,
StorableIndex> foreignIndex,
ChainedProperty foreignProperty)
{
mFilter = filter;
mScore = score;
mLocalIndex = localIndex;
mForeignIndex = foreignIndex;
mForeignProperty = foreignProperty;
mRemainderFilter = score.getFilteringScore().getRemainderFilter();
mRemainderOrderings = score.getOrderingScore().getRemainderOrderings();
}
// Called by mergeRemainder.
private Result(Result result,
Filter remainderFilter,
List getFilter() {
return mFilter;
}
/**
* Returns combined handled and remainder orderings for this result.
*/
public List getCompositeScore() {
return mScore;
}
/**
* Remainder filter which overrides that in composite score.
*/
public Filter getRemainderFilter() {
return mRemainderFilter;
}
/**
* Remainder orderings which override that in composite score.
*/
public List getLocalIndex() {
return mLocalIndex;
}
/**
* Returns the foreign index that was selected, or null if a local
* index was selected. If a foreign index has been selected, then a
* {@link JoinedQueryExecutor} is needed.
*/
public StorableIndex> getForeignIndex() {
return mForeignIndex;
}
/**
* Returns the simple or chained property that maps to the selected
* foreign index. Returns null if foreign index was not selected. This
* property corresponds to the "bToAProperty" of {@link
* JoinedQueryExecutor}.
*/
public ChainedProperty getForeignProperty() {
return mForeignProperty;
}
/**
* Returns true if the given result uses the same index as this, and in
* the same way. The only allowed differences are in the remainder
* filter and orderings.
*/
public boolean canMergeRemainder(Result other) {
if (this == other || (!handlesAnything() && !other.handlesAnything())) {
return true;
}
if (equals(getLocalIndex(), other.getLocalIndex())
&& equals(getForeignIndex(), other.getForeignIndex())
&& equals(getForeignProperty(), other.getForeignProperty()))
{
return getCompositeScore().canMergeRemainder(other.getCompositeScore());
}
return false;
}
/**
* Merges the remainder filter and orderings of this result with the
* one given, returning a new result. Call canMergeRemainder first to
* verify if the merge makes any sense.
*/
public Result mergeRemainder(Result other) {
if (this == other) {
return this;
}
return new Result
(this,
getCompositeScore().mergeRemainderFilter(other.getCompositeScore()),
getCompositeScore().mergeRemainderOrderings(other.getCompositeScore()));
}
/**
* Merges the remainder filter of this result with the given filter,
* returning a new result. If handlesAnything return true, then it
* doesn't usually make sense to call this method.
*/
public Result mergeRemainderFilter(Filter filter) {
Filter remainderFilter = getRemainderFilter();
if (remainderFilter == null) {
remainderFilter = filter;
} else if (filter != null) {
remainderFilter = remainderFilter.or(filter);
}
return setRemainderFilter(remainderFilter);
}
/**
* Returns a new result with the remainder filter replaced.
*/
public Result setRemainderFilter(Filter filter) {
return new Result(this, filter, getRemainderOrderings());
}
public String toString() {
return "IndexedQueryAnalyzer.Result {score="
+ getCompositeScore() + ", localIndex="
+ getLocalIndex() + ", foreignIndex="
+ getForeignIndex() + ", foreignProperty="
+ getForeignProperty() + ", remainderFilter="
+ getRemainderFilter() + ", remainderOrderings="
+ getRemainderOrderings() + '}';
}
private boolean equals(Object a, Object b) {
return a == null ? (b == null) : (a.equals(b));
}
}
private static class ForeignIndexes {
final ChainedProperty mProperty;
final List[]> mVirtualIndexMap;
/**
* @param property type of property must be a joined Storable
*/
ForeignIndexes(ChainedProperty property, Collection[]>();
}
/**
* Prepends local chained property with names of index elements,
* producing a virtual index on local storable. This allows
* CompositeScore to evaluate it.
*/
synchronized OrderedProperty[] getVirtualIndex(StorableIndex> index) {
OrderedProperty[] virtualProps = mVirtualIndexMap.get(index);
if (virtualProps != null) {
return virtualProps;
}
OrderedProperty>[] realProps = index.getOrderedProperties();
virtualProps = new OrderedProperty[realProps.length];
for (int i=realProps.length; --i>=0; ) {
OrderedProperty> realProp = realProps[i];
ChainedProperty virtualChained =
mProperty.append(realProp.getChainedProperty());
virtualProps[i] = OrderedProperty.get(virtualChained, realProp.getDirection());
}
mVirtualIndexMap.put(index, virtualProps);
return virtualProps;
}
}
}