/* * 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.List; import com.amazon.carbonado.Storable; import com.amazon.carbonado.filter.AndFilter; import com.amazon.carbonado.filter.Filter; import com.amazon.carbonado.filter.OrFilter; import com.amazon.carbonado.filter.PropertyFilter; import com.amazon.carbonado.filter.Visitor; import com.amazon.carbonado.info.OrderedProperty; import com.amazon.carbonado.info.StorableIndex; /** * Analyzes a query specification and determines how it can be executed as a * union of smaller queries. If necessary, the UnionQueryAnalyzer will alter * the query slightly, imposing a total ordering. Internally, an {@link * IndexedQueryAnalyzer} is used for selecting the best indexes. * *
UnionQueryAnalyzer 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
*/
public class UnionQueryAnalyzer {
final IndexedQueryAnalyzer mIndexAnalyzer;
/**
* @param type type of storable being queried
* @param indexProvider
* @throws IllegalArgumentException if type or indexProvider is null
*/
public UnionQueryAnalyzer(Class type, IndexProvider indexProvider) {
mIndexAnalyzer = new IndexedQueryAnalyzer(type, indexProvider);
}
/**
* @param filter optional filter which must be {@link Filter#isBound bound}
* @param orderings properties which define desired ordering
*/
public Result analyze(Filter filter, OrderedProperty... orderings) {
if (!filter.isBound()) {
// Strictly speaking, this is not required, but it detects the
// mistake of not properly calling initialFilterValues.
throw new IllegalArgumentException("Filter must be bound");
}
// Required for split to work.
filter = filter.disjunctiveNormalForm();
List filter, OrderedProperty... orderings)
{
Splitter splitter = new Splitter(orderings);
filter.accept(splitter, null);
List.Result full = null;
for (IndexedQueryAnalyzer.Result result : subResults) {
if (!result.handlesAnything()) {
full = result;
break;
}
}
if (full == null) {
// Okay, no full scan needed.
return subResults;
}
List.Result result : subResults) {
if (result == full) {
// Add after everything has been merged into it.
continue;
}
boolean exempt = result.getCompositeScore().getFilteringScore().hasAnyMatches();
// FIXME: check for joins
if (exempt) {
subResults.add(result);
} else {
full = full.mergeRemainder(result.getFilter());
}
}
mergedResults.add(full);
return mergedResults;
}
public class Result {
// FIXME: User of QueryAnalyzer results needs to identify what actual
// storage is used by an index. It is also responsible for grouping
// unions together if storage differs. If foreign index is selected,
// then join is needed.
private final List {
private final OrderedProperty[] mOrderings;
final List... orderings) {
mOrderings = orderings;
mSubResults = new ArrayList filter, Object param) {
Filter left = filter.getLeftFilter();
if (!(left instanceof OrFilter)) {
subAnalyze(left);
} else {
left.accept(this, param);
}
Filter right = filter.getRightFilter();
if (!(right instanceof OrFilter)) {
subAnalyze(right);
} else {
right.accept(this, param);
}
return null;
}
// This method should only be called if root filter has no 'or' operators.
@Override
public Object visit(AndFilter filter, Object param) {
subAnalyze(filter);
return null;
}
// This method should only be called if root filter has no logical operators.
@Override
public Object visit(PropertyFilter filter, Object param) {
subAnalyze(filter);
return null;
}
private void subAnalyze(Filter subFilter) {
IndexedQueryAnalyzer.Result subResult =
mIndexAnalyzer.analyze(subFilter, mOrderings);
// Rather than blindly add to mSubResults, try to merge with
// another result. This in turn reduces the number of cursors
// needed by the union.
int size = mSubResults.size();
for (int i=0; i