From b1ba29c24f6c48c9b028bc8c4c5e9c729092f5ac Mon Sep 17 00:00:00 2001 From: "Brian S. O'Neill" Date: Mon, 11 Sep 2006 01:33:12 +0000 Subject: Completed new query engine implementation. --- .../carbonado/qe/ArraySortedQueryExecutor.java | 52 ----- .../java/com/amazon/carbonado/qe/EmptyQuery.java | 41 ++-- .../com/amazon/carbonado/qe/FilteringScore.java | 6 +- .../carbonado/qe/FullScanIndexedQueryExecutor.java | 61 +++--- .../amazon/carbonado/qe/FullScanQueryExecutor.java | 34 +-- .../com/amazon/carbonado/qe/IndexProvider.java | 37 ---- .../amazon/carbonado/qe/IndexedQueryAnalyzer.java | 182 +++++++++++++---- .../amazon/carbonado/qe/IndexedQueryExecutor.java | 84 ++++---- .../amazon/carbonado/qe/IterableQueryExecutor.java | 39 +++- .../com/amazon/carbonado/qe/KeyQueryExecutor.java | 35 ++-- .../java/com/amazon/carbonado/qe/OrderingList.java | 25 +++ .../amazon/carbonado/qe/PropertyFilterList.java | 4 +- .../java/com/amazon/carbonado/qe/QueryEngine.java | 67 ++++++ .../com/amazon/carbonado/qe/QueryExecutor.java | 1 + .../amazon/carbonado/qe/QueryExecutorCache.java | 86 ++++++++ .../amazon/carbonado/qe/QueryExecutorFactory.java | 44 ++++ .../java/com/amazon/carbonado/qe/QueryFactory.java | 44 ++++ .../com/amazon/carbonado/qe/RepositoryAccess.java | 44 ++++ .../amazon/carbonado/qe/SortedQueryExecutor.java | 28 ++- .../com/amazon/carbonado/qe/StandardQuery.java | 153 ++++++++------ .../amazon/carbonado/qe/StandardQueryFactory.java | 227 +++++++++++++++++++++ .../com/amazon/carbonado/qe/StorageAccess.java | 63 ++++++ .../amazon/carbonado/qe/UnionQueryAnalyzer.java | 110 ++++++++-- .../java/com/amazon/carbonado/spi/BaseQuery.java | 4 +- 24 files changed, 1130 insertions(+), 341 deletions(-) delete mode 100644 src/main/java/com/amazon/carbonado/qe/ArraySortedQueryExecutor.java delete mode 100644 src/main/java/com/amazon/carbonado/qe/IndexProvider.java create mode 100644 src/main/java/com/amazon/carbonado/qe/QueryEngine.java create mode 100644 src/main/java/com/amazon/carbonado/qe/QueryExecutorCache.java create mode 100644 src/main/java/com/amazon/carbonado/qe/QueryExecutorFactory.java create mode 100644 src/main/java/com/amazon/carbonado/qe/QueryFactory.java create mode 100644 src/main/java/com/amazon/carbonado/qe/RepositoryAccess.java create mode 100644 src/main/java/com/amazon/carbonado/qe/StandardQueryFactory.java create mode 100644 src/main/java/com/amazon/carbonado/qe/StorageAccess.java (limited to 'src/main/java/com') diff --git a/src/main/java/com/amazon/carbonado/qe/ArraySortedQueryExecutor.java b/src/main/java/com/amazon/carbonado/qe/ArraySortedQueryExecutor.java deleted file mode 100644 index 85b6c2e..0000000 --- a/src/main/java/com/amazon/carbonado/qe/ArraySortedQueryExecutor.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * 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.List; - -import com.amazon.carbonado.Storable; - -import com.amazon.carbonado.cursor.ArraySortBuffer; -import com.amazon.carbonado.cursor.SortBuffer; - -import com.amazon.carbonado.info.OrderedProperty; - -/** - * QueryExecutor which wraps another and sorts the results within an array. - * - * @author Brian S O'Neill - * @see ArraySortBuffer - */ -public class ArraySortedQueryExecutor extends SortedQueryExecutor { - /** - * @param executor executor to wrap - * @throws IllegalArgumentException if executor is null or if remainder - * ordering is empty - */ - public ArraySortedQueryExecutor(QueryExecutor executor, - OrderingList handledOrdering, - OrderingList remainderOrdering) - { - super(executor, handledOrdering, remainderOrdering); - } - - protected SortBuffer createSortBuffer() { - return new ArraySortBuffer(); - } -} diff --git a/src/main/java/com/amazon/carbonado/qe/EmptyQuery.java b/src/main/java/com/amazon/carbonado/qe/EmptyQuery.java index 5da585a..f07e7ff 100644 --- a/src/main/java/com/amazon/carbonado/qe/EmptyQuery.java +++ b/src/main/java/com/amazon/carbonado/qe/EmptyQuery.java @@ -24,7 +24,6 @@ import com.amazon.carbonado.Cursor; import com.amazon.carbonado.FetchException; import com.amazon.carbonado.IsolationLevel; import com.amazon.carbonado.PersistNoneException; -import com.amazon.carbonado.Storage; import com.amazon.carbonado.Storable; import com.amazon.carbonado.Query; @@ -41,20 +40,20 @@ import com.amazon.carbonado.info.OrderedProperty; * @author Brian S O'Neill */ public final class EmptyQuery extends AbstractQuery { - private final Storage mRootStorage; + private final QueryFactory mFactory; // Properties that this query is ordered by. private final OrderingList mOrdering; /** - * @param rootStorage required root storage object, used by 'or' and 'not' methods + * @param factory required query factory, used by 'or' and 'not' methods * @param ordering optional order-by properties */ - public EmptyQuery(Storage rootStorage, OrderingList ordering) { - if (rootStorage == null) { + public EmptyQuery(QueryFactory factory, OrderingList ordering) { + if (factory == null) { throw new IllegalArgumentException(); } - mRootStorage = rootStorage; + mFactory = factory; if (ordering == null) { ordering = OrderingList.emptyList(); } @@ -62,23 +61,23 @@ public final class EmptyQuery extends AbstractQuery { } /** - * @param rootStorage required root storage object, used by 'or' and 'not' methods + * @param factory required query factory, used by 'or' and 'not' methods * @param ordering optional order-by property */ - public EmptyQuery(Storage rootStorage, String ordering) { - this(rootStorage, OrderingList.get(rootStorage.getStorableType(), ordering)); + public EmptyQuery(QueryFactory factory, String ordering) { + this(factory, OrderingList.get(factory.getStorableType(), ordering)); } /** - * @param rootStorage required root storage object, used by 'or' and 'not' methods + * @param factory required query factory, used by 'or' and 'not' methods * @param orderings optional order-by properties */ - public EmptyQuery(Storage rootStorage, String... orderings) { - this(rootStorage, OrderingList.get(rootStorage.getStorableType(), orderings)); + public EmptyQuery(QueryFactory factory, String... orderings) { + this(factory, OrderingList.get(factory.getStorableType(), orderings)); } public Class getStorableType() { - return mRootStorage.getStorableType(); + return mFactory.getStorableType(); } /** @@ -182,30 +181,24 @@ public final class EmptyQuery extends AbstractQuery { throw new IllegalStateException("Query is already guaranteed to fetch nothing"); } - /** - * Returns a query that fetches only what's specified by the given filter. - */ public Query or(Filter filter) throws FetchException { - return mRootStorage.query(filter); + FilterValues values = filter == null ? null : filter.initialFilterValues(); + return mFactory.query(values, mOrdering); } /** * Returns a query that fetches everything, possibly in a specified order. */ public Query not() throws FetchException { - Query query = mRootStorage.query(); - if (mOrdering.size() > 0) { - query = query.orderBy(mOrdering.asStringArray()); - } - return query; + return mFactory.query(null, mOrdering); } public Query orderBy(String property) throws FetchException { - return new EmptyQuery(mRootStorage, property); + return new EmptyQuery(mFactory, property); } public Query orderBy(String... properties) throws FetchException { - return new EmptyQuery(mRootStorage, properties); + return new EmptyQuery(mFactory, properties); } /** diff --git a/src/main/java/com/amazon/carbonado/qe/FilteringScore.java b/src/main/java/com/amazon/carbonado/qe/FilteringScore.java index 5ca9d04..c9178a1 100644 --- a/src/main/java/com/amazon/carbonado/qe/FilteringScore.java +++ b/src/main/java/com/amazon/carbonado/qe/FilteringScore.java @@ -521,11 +521,7 @@ public class FilteringScore { Filter otherRemainderFilter = other.getRemainderFilter(); if (thisRemainderFilter == null) { - if (otherRemainderFilter == null) { - return null; - } else { - return otherRemainderFilter; - } + return otherRemainderFilter; } else if (otherRemainderFilter == null) { return thisRemainderFilter; } else if (thisRemainderFilter.equals(otherRemainderFilter)) { diff --git a/src/main/java/com/amazon/carbonado/qe/FullScanIndexedQueryExecutor.java b/src/main/java/com/amazon/carbonado/qe/FullScanIndexedQueryExecutor.java index e7c6f2b..cfd012b 100644 --- a/src/main/java/com/amazon/carbonado/qe/FullScanIndexedQueryExecutor.java +++ b/src/main/java/com/amazon/carbonado/qe/FullScanIndexedQueryExecutor.java @@ -35,53 +35,62 @@ import com.amazon.carbonado.info.OrderedProperty; import com.amazon.carbonado.info.StorableIndex; /** - * Abstract QueryExecutor which fully scans all Storables of a given type, - * referencing an index. + * QueryExecutor which fully scans all Storables of a given type, referencing + * an index. * * @author Brian S O'Neill */ -public abstract class FullScanIndexedQueryExecutor - extends FullScanQueryExecutor -{ - private static Class getType(StorableIndex index) { - if (index == null) { - throw new IllegalArgumentException(); - } - return index.getStorableType(); - } - +public class FullScanIndexedQueryExecutor extends AbstractQueryExecutor { + private final Support mSupport; private final StorableIndex mIndex; /** + * @param support support for full scan * @param index index to use, which may be a primary key index - * @throws IllegalArgumentException if index is null + * @throws IllegalArgumentException if support or index is null */ - public FullScanIndexedQueryExecutor(StorableIndex index) { - super(getType(index)); + public FullScanIndexedQueryExecutor(Support support, StorableIndex index) { + if (support == null || index == null) { + throw new IllegalArgumentException(); + } + mSupport = support; mIndex = index; } - @Override + /** + * Returns an open filter. + */ + public Filter getFilter() { + return Filter.getOpenFilter(mIndex.getStorableType()); + } + public Cursor fetch(FilterValues values) throws FetchException { - return fetch(mIndex); + return mSupport.fetch(mIndex); } /** * Returns the natural order of the index. */ - @Override public OrderingList getOrdering() { return OrderingList.get(mIndex.getOrderedProperties()); } - protected Cursor fetch() throws FetchException { - return fetch(mIndex); + public boolean printPlan(Appendable app, int indentLevel, FilterValues values) + throws IOException + { + indent(app, indentLevel); + app.append("full index scan: "); + app.append(mIndex.getStorableType().getName()); + newline(app); + return true; } - /** - * Return a new Cursor instance referenced by the given index. - * - * @param index index to open, which may be a primary key index - */ - protected abstract Cursor fetch(StorableIndex index) throws FetchException; + public static interface Support { + /** + * Perform a full scan of all Storables referenced by an index. + * + * @param index index to scan, which may be a primary key index + */ + Cursor fetch(StorableIndex index) throws FetchException; + } } diff --git a/src/main/java/com/amazon/carbonado/qe/FullScanQueryExecutor.java b/src/main/java/com/amazon/carbonado/qe/FullScanQueryExecutor.java index fc29983..2875542 100644 --- a/src/main/java/com/amazon/carbonado/qe/FullScanQueryExecutor.java +++ b/src/main/java/com/amazon/carbonado/qe/FullScanQueryExecutor.java @@ -33,33 +33,33 @@ import com.amazon.carbonado.filter.FilterValues; import com.amazon.carbonado.info.OrderedProperty; /** - * Abstract QueryExecutor which fully scans all Storables of a given type. + * QueryExecutor which fully scans all Storables of a given type. * * @author Brian S O'Neill */ -public abstract class FullScanQueryExecutor extends AbstractQueryExecutor { - private final Class mType; +public class FullScanQueryExecutor extends AbstractQueryExecutor { + private final Support mSupport; /** - * @param type type of Storable - * @throws IllegalArgumentException if type is null + * @param support support for full scan + * @throws IllegalArgumentException if support is null */ - public FullScanQueryExecutor(Class type) { - if (type == null) { + public FullScanQueryExecutor(Support support) { + if (support == null) { throw new IllegalArgumentException(); } - mType = type; + mSupport = support; } /** * Returns an open filter. */ public Filter getFilter() { - return Filter.getOpenFilter(mType); + return Filter.getOpenFilter(mSupport.getStorableType()); } public Cursor fetch(FilterValues values) throws FetchException { - return fetch(); + return mSupport.fetch(); } /** @@ -74,13 +74,17 @@ public abstract class FullScanQueryExecutor extends Abstract { indent(app, indentLevel); app.append("full scan: "); - app.append(mType.getName()); + app.append(mSupport.getStorableType().getName()); newline(app); return true; } - /** - * Return a new Cursor instance. - */ - protected abstract Cursor fetch() throws FetchException; + public static interface Support { + Class getStorableType(); + + /** + * Perform a full scan of all Storables. + */ + Cursor fetch() throws FetchException; + } } diff --git a/src/main/java/com/amazon/carbonado/qe/IndexProvider.java b/src/main/java/com/amazon/carbonado/qe/IndexProvider.java deleted file mode 100644 index 69277e4..0000000 --- a/src/main/java/com/amazon/carbonado/qe/IndexProvider.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * 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.Collection; - -import com.amazon.carbonado.Storable; - -import com.amazon.carbonado.info.StorableIndex; - -/** - * An index provider is typically a repository implementation. - * - * @author Brian S O'Neill - */ -public interface IndexProvider { - /** - * Returns all the available indexes for the given type. - */ - Collection> indexesFor(Class type); -} diff --git a/src/main/java/com/amazon/carbonado/qe/IndexedQueryAnalyzer.java b/src/main/java/com/amazon/carbonado/qe/IndexedQueryAnalyzer.java index 594fcc1..9b46227 100644 --- a/src/main/java/com/amazon/carbonado/qe/IndexedQueryAnalyzer.java +++ b/src/main/java/com/amazon/carbonado/qe/IndexedQueryAnalyzer.java @@ -26,7 +26,11 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import com.amazon.carbonado.FetchException; +import com.amazon.carbonado.RepositoryException; import com.amazon.carbonado.Storable; +import com.amazon.carbonado.Storage; +import com.amazon.carbonado.SupportException; import com.amazon.carbonado.filter.Filter; import com.amazon.carbonado.filter.PropertyFilter; @@ -50,23 +54,23 @@ import com.amazon.carbonado.info.StorableProperty; * @see UnionQueryAnalyzer */ public class IndexedQueryAnalyzer { - private final Class mType; - private final IndexProvider mIndexProvider; + final Class mType; + final RepositoryAccess mRepoAccess; // Growable cache which maps join properties to lists of usable foreign indexes. private Map, ForeignIndexes> mForeignIndexCache; /** * @param type type of storable being queried - * @param indexProvider + * @param access repository access for examing available indexes * @throws IllegalArgumentException if type or indexProvider is null */ - public IndexedQueryAnalyzer(Class type, IndexProvider indexProvider) { - if (type == null || indexProvider == null) { + public IndexedQueryAnalyzer(Class type, RepositoryAccess access) { + if (type == null || access == null) { throw new IllegalArgumentException(); } mType = type; - mIndexProvider = indexProvider; + mRepoAccess = access; } public Class getStorableType() { @@ -81,8 +85,6 @@ public class IndexedQueryAnalyzer { */ public Result analyze(Filter filter, OrderingList ordering) { 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"); } @@ -92,7 +94,7 @@ public class IndexedQueryAnalyzer { CompositeScore bestScore = null; StorableIndex bestLocalIndex = null; - Collection> localIndexes = mIndexProvider.indexesFor(mType); + Collection> localIndexes = indexesFor(getStorableType()); if (localIndexes != null) { for (StorableIndex index : localIndexes) { CompositeScore candidateScore = @@ -177,7 +179,7 @@ public class IndexedQueryAnalyzer { // All foreign indexes are available for use. Class foreignType = chainedProp.getLastProperty().getType(); - Collection> indexes = mIndexProvider.indexesFor(foreignType); + Collection> indexes = indexesFor(foreignType); foreignIndexes = new ForeignIndexes(chainedProp, indexes); } @@ -211,7 +213,7 @@ public class IndexedQueryAnalyzer { // Java generics are letting me down. I cannot use proper specification // because compiler gets confused with all the wildcards. - Collection indexes = mIndexProvider.indexesFor(filter.getStorableType()); + Collection indexes = indexesFor(filter.getStorableType()); if (indexes != null) { for (Object index : indexes) { @@ -226,7 +228,7 @@ public class IndexedQueryAnalyzer { } private boolean simpleAnalyze(Filter filter) { - Collection> indexes = mIndexProvider.indexesFor(filter.getStorableType()); + Collection> indexes = indexesFor(filter.getStorableType()); if (indexes != null) { for (StorableIndex index : indexes) { @@ -240,6 +242,10 @@ public class IndexedQueryAnalyzer { return false; } + private Collection> indexesFor(Class type) { + return mRepoAccess.storageAccessFor(type).getAllIndexes(); + } + public class Result { private final Filter mFilter; @@ -257,27 +263,24 @@ public class IndexedQueryAnalyzer { StorableIndex foreignIndex, ChainedProperty foreignProperty) { - mFilter = filter; - mScore = score; - mLocalIndex = localIndex; - mForeignIndex = foreignIndex; - mForeignProperty = foreignProperty; - mRemainderFilter = score.getFilteringScore().getRemainderFilter(); - mRemainderOrdering = score.getOrderingScore().getRemainderOrdering(); + this(filter, score, localIndex, foreignIndex, foreignProperty, + score.getFilteringScore().getRemainderFilter(), + score.getOrderingScore().getRemainderOrdering()); } - // Called by mergeRemainder. - private Result(Result result, + private Result(Filter filter, + CompositeScore score, + StorableIndex localIndex, + StorableIndex foreignIndex, + ChainedProperty foreignProperty, Filter remainderFilter, OrderingList remainderOrdering) { - mFilter = result.mFilter == null ? remainderFilter - : (remainderFilter == null ? result.mFilter : result.mFilter.or(remainderFilter)); - - mScore = result.mScore; - mLocalIndex = result.mLocalIndex; - mForeignIndex = result.mForeignIndex; - mForeignProperty = result.mForeignProperty; + mFilter = filter; + mScore = score; + mLocalIndex = localIndex; + mForeignIndex = foreignIndex; + mForeignProperty = foreignProperty; mRemainderFilter = remainderFilter; mRemainderOrdering = remainderOrdering; } @@ -387,10 +390,17 @@ public class IndexedQueryAnalyzer { return this; } + Filter remainderFilter = + mergeFilters(getRemainderFilter(), other.getRemainderFilter()); + + OrderingList remainderOrdering = + getRemainderOrdering().concat(other.getRemainderOrdering()).reduce(); + + Filter filter = mergeFilters(getFilter(), remainderFilter); + return new Result - (this, - getCompositeScore().mergeRemainderFilter(other.getCompositeScore()), - getCompositeScore().mergeRemainderOrdering(other.getCompositeScore())); + (filter, mScore, mLocalIndex, mForeignIndex, mForeignProperty, + remainderFilter, remainderOrdering); } /** @@ -399,20 +409,114 @@ public class IndexedQueryAnalyzer { * 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(mergeFilters(getRemainderFilter(), filter)); + } + + private Filter mergeFilters(Filter a, Filter b) { + if (a == null) { + return b; } - return setRemainderFilter(remainderFilter); + if (b == null) { + return a; + } + return a.or(b).reduce(); } /** * Returns a new result with the remainder filter replaced. */ - public Result setRemainderFilter(Filter filter) { - return new Result(this, filter, getRemainderOrdering()); + public Result setRemainderFilter(Filter remainderFilter) { + return new Result + (mFilter, mScore, mLocalIndex, mForeignIndex, mForeignProperty, + remainderFilter, mRemainderOrdering); + } + + /** + * Returns a new result with the remainder ordering replaced. + */ + public Result setRemainderOrdering(OrderingList remainderOrdering) { + return new Result + (mFilter, mScore, mLocalIndex, mForeignIndex, mForeignProperty, + mRemainderFilter, remainderOrdering); + } + + /** + * Creates a QueryExecutor based on this result. + */ + public QueryExecutor createExecutor() + throws SupportException, FetchException, RepositoryException + { + StorableIndex localIndex = getLocalIndex(); + StorageAccess localAccess = mRepoAccess.storageAccessFor(getStorableType()); + + if (localIndex != null) { + Storage delegate = localAccess.storageDelegate(localIndex); + if (delegate != null) { + return new DelegatedQueryExecutor(delegate, getFilter(), getOrdering()); + } + } + + QueryExecutor executor = baseExecutor(localAccess); + + Filter remainderFilter = getRemainderFilter(); + if (remainderFilter != null) { + executor = new FilteredQueryExecutor(executor, remainderFilter); + } + + OrderingList remainderOrdering = getRemainderOrdering(); + if (remainderOrdering.size() > 0) { + executor = new SortedQueryExecutor + (localAccess, + executor, + getCompositeScore().getOrderingScore().getHandledOrdering(), + remainderOrdering); + } + + return executor; + } + + private QueryExecutor baseExecutor(StorageAccess localAccess) + throws SupportException, FetchException, RepositoryException + { + if (!handlesAnything()) { + return new FullScanQueryExecutor(localAccess); + } + + StorableIndex localIndex = getLocalIndex(); + + if (localIndex != null) { + return indexExecutor(localAccess, localIndex); + } + + StorableIndex foreignIndex = getForeignIndex(); + StorageAccess foreignAccess = mRepoAccess + .storageAccessFor(foreignIndex.getStorableType()); + + QueryExecutor foreignExecutor; + Storage delegate = foreignAccess.storageDelegate(foreignIndex); + if (delegate != null) { + foreignExecutor = new DelegatedQueryExecutor(delegate, getFilter(), getOrdering()); + } else { + foreignExecutor = indexExecutor(foreignAccess, foreignIndex); + } + + return new JoinedQueryExecutor + (mRepoAccess.getRootRepository(), getForeignProperty(), foreignExecutor); + } + + private QueryExecutor indexExecutor(StorageAccess access, + StorableIndex index) + { + CompositeScore score = getCompositeScore(); + FilteringScore fScore = score.getFilteringScore(); + + if (!fScore.hasAnyMatches()) { + return new FullScanIndexedQueryExecutor(access, index); + } + if (fScore.isKeyMatch()) { + return new KeyQueryExecutor(access, index, fScore); + } + return new IndexedQueryExecutor(access, index, score); } public String toString() { diff --git a/src/main/java/com/amazon/carbonado/qe/IndexedQueryExecutor.java b/src/main/java/com/amazon/carbonado/qe/IndexedQueryExecutor.java index a306db1..f8d2b23 100644 --- a/src/main/java/com/amazon/carbonado/qe/IndexedQueryExecutor.java +++ b/src/main/java/com/amazon/carbonado/qe/IndexedQueryExecutor.java @@ -36,11 +36,11 @@ import com.amazon.carbonado.info.StorableIndex; import com.amazon.carbonado.info.OrderedProperty; /** - * Abstract QueryExecutor which utilizes an index. + * QueryExecutor which utilizes an index. * * @author Brian S O'Neill */ -public abstract class IndexedQueryExecutor extends AbstractQueryExecutor { +public class IndexedQueryExecutor extends AbstractQueryExecutor { /** * Compares two objects which are assumed to be Comparable. If one value is * null, it is treated as being higher. This consistent with all other @@ -50,6 +50,7 @@ public abstract class IndexedQueryExecutor extends AbstractQ return a == null ? (b == null ? 0 : -1) : (b == null ? 1 : ((Comparable) a).compareTo(b)); } + private final Support mSupport; private final StorableIndex mIndex; private final Filter mIdentityFilter; private final List> mExclusiveRangeStartFilters; @@ -62,13 +63,17 @@ public abstract class IndexedQueryExecutor extends AbstractQ /** * @param index index to use, which may be a primary key index * @param score score determines how best to utilize the index - * @throws IllegalArgumentException if index or score is null + * @throws IllegalArgumentException if any parameter is null */ - public IndexedQueryExecutor(StorableIndex index, CompositeScore score) { - if (index == null || score == null) { + public IndexedQueryExecutor(Support support, + StorableIndex index, + CompositeScore score) + { + if (support == null || index == null || score == null) { throw new IllegalArgumentException(); } + mSupport = support; mIndex = index; FilteringScore fScore = score.getFilteringScore(); @@ -147,11 +152,11 @@ public abstract class IndexedQueryExecutor extends AbstractQ } } - return fetch(mIndex, identityValues, - rangeStartBoundary, rangeStartValue, - rangeEndBoundary, rangeEndValue, - mReverseRange, - mReverseOrder); + return mSupport.fetch(mIndex, identityValues, + rangeStartBoundary, rangeStartValue, + rangeEndBoundary, rangeEndValue, + mReverseRange, + mReverseOrder); } public Filter getFilter() { @@ -235,32 +240,35 @@ public abstract class IndexedQueryExecutor extends AbstractQ return true; } - /** - * Return a new Cursor instance constrained by the given parameters. The - * index values are aligned with the index properties at property index - * 0. An optional start or end boundary matches up with the index property - * following the last of the index values. - * - * @param index index to open, which may be a primary key index - * @param identityValues optional list of exactly matching values to apply to index - * @param rangeStartBoundary start boundary type - * @param rangeStartValue value to start at if boundary is not open - * @param rangeEndBoundary end boundary type - * @param rangeEndValue value to end at if boundary is not open - * @param reverseRange indicates that range operates on a property whose - * natural order is descending. Only the code that opens the physical - * cursor should examine this parameter. If true, then the range start and - * end parameter pairs need to be swapped. - * @param reverseOrder when true, iteration should be reversed from its - * natural order - */ - protected abstract Cursor fetch(StorableIndex index, - Object[] identityValues, - BoundaryType rangeStartBoundary, - Object rangeStartValue, - BoundaryType rangeEndBoundary, - Object rangeEndValue, - boolean reverseRange, - boolean reverseOrder) - throws FetchException; + public static interface Support { + /** + * Perform an index scan of a subset of Storables referenced by an + * index. The identity values are aligned with the index properties at + * property 0. An optional range start or range end aligns with the index + * property following the last of the identity values. + * + * + * @param index index to open, which may be a primary key index + * @param identityValues optional list of exactly matching values to apply to index + * @param rangeStartBoundary start boundary type + * @param rangeStartValue value to start at if boundary is not open + * @param rangeEndBoundary end boundary type + * @param rangeEndValue value to end at if boundary is not open + * @param reverseRange indicates that range operates on a property whose + * natural order is descending. Only the code that opens the physical + * cursor should examine this parameter. If true, then the range start and + * end parameter pairs need to be swapped. + * @param reverseOrder when true, iteration should be reversed from its + * natural order + */ + Cursor fetch(StorableIndex index, + Object[] identityValues, + BoundaryType rangeStartBoundary, + Object rangeStartValue, + BoundaryType rangeEndBoundary, + Object rangeEndValue, + boolean reverseRange, + boolean reverseOrder) + throws FetchException; + } } diff --git a/src/main/java/com/amazon/carbonado/qe/IterableQueryExecutor.java b/src/main/java/com/amazon/carbonado/qe/IterableQueryExecutor.java index b80066e..de0864f 100644 --- a/src/main/java/com/amazon/carbonado/qe/IterableQueryExecutor.java +++ b/src/main/java/com/amazon/carbonado/qe/IterableQueryExecutor.java @@ -18,11 +18,16 @@ package com.amazon.carbonado.qe; +import java.io.IOException; + import java.util.concurrent.locks.Lock; import com.amazon.carbonado.Cursor; import com.amazon.carbonado.Storable; +import com.amazon.carbonado.filter.Filter; +import com.amazon.carbonado.filter.FilterValues; + import com.amazon.carbonado.cursor.IteratorCursor; /** @@ -31,7 +36,8 @@ import com.amazon.carbonado.cursor.IteratorCursor; * @author Brian S O'Neill * @see IteratorCursor */ -public class IterableQueryExecutor extends FullScanQueryExecutor { +public class IterableQueryExecutor extends AbstractQueryExecutor { + private final Class mType; private final Iterable mIterable; private final Lock mLock; @@ -51,12 +57,39 @@ public class IterableQueryExecutor extends FullScanQueryExec * @throws IllegalArgumentException if type is null */ public IterableQueryExecutor(Class type, Iterable iterable, Lock lock) { - super(type); + if (type == null) { + throw new IllegalArgumentException(); + } + mType = type; mIterable = iterable; mLock = lock; } - protected Cursor fetch() { + /** + * Returns an open filter. + */ + public Filter getFilter() { + return Filter.getOpenFilter(mType); + } + + public Cursor fetch(FilterValues values) { return new IteratorCursor(mIterable, mLock); } + + /** + * Returns an empty list. + */ + public OrderingList getOrdering() { + return OrderingList.emptyList(); + } + + public boolean printPlan(Appendable app, int indentLevel, FilterValues values) + throws IOException + { + indent(app, indentLevel); + app.append("iterable: "); + app.append(mType.getName()); + newline(app); + return true; + } } diff --git a/src/main/java/com/amazon/carbonado/qe/KeyQueryExecutor.java b/src/main/java/com/amazon/carbonado/qe/KeyQueryExecutor.java index 8209be7..acde1a8 100644 --- a/src/main/java/com/amazon/carbonado/qe/KeyQueryExecutor.java +++ b/src/main/java/com/amazon/carbonado/qe/KeyQueryExecutor.java @@ -34,12 +34,13 @@ import com.amazon.carbonado.info.OrderedProperty; import com.amazon.carbonado.info.StorableIndex; /** - * Abstract QueryExecutor which has a fully specified key, and so cursors - * produce at most one result. + * QueryExecutor which has a fully specified key, and so cursors produce at + * most one result. * * @author Brian S O'Neill */ -public abstract class KeyQueryExecutor extends AbstractQueryExecutor { +public class KeyQueryExecutor extends AbstractQueryExecutor { + private final Support mSupport; private final StorableIndex mIndex; private final Filter mKeyFilter; @@ -49,18 +50,18 @@ public abstract class KeyQueryExecutor extends AbstractQuery * @throws IllegalArgumentException if any parameter is null or if index is * not unique or if score is not a key match */ - public KeyQueryExecutor(StorableIndex index, FilteringScore score) { - if (index == null || score == null) { + public KeyQueryExecutor(Support support, StorableIndex index, FilteringScore score) { + if (support == null || index == null || score == null) { throw new IllegalArgumentException(); } if (!index.isUnique() || !score.isKeyMatch()) { throw new IllegalArgumentException(); } + mSupport = support; mIndex = index; mKeyFilter = score.getIdentityFilter(); } - @Override public Class getStorableType() { // Storable type of filter may differ if index is used along with a // join. The type of the index is the correct storable type. @@ -68,7 +69,7 @@ public abstract class KeyQueryExecutor extends AbstractQuery } public Cursor fetch(FilterValues values) throws FetchException { - return fetch(mIndex, values.getValuesFor(mKeyFilter)); + return mSupport.fetch(mIndex, values.getValuesFor(mKeyFilter)); } public Filter getFilter() { @@ -100,12 +101,16 @@ public abstract class KeyQueryExecutor extends AbstractQuery return true; } - /** - * Return a new Cursor instance referenced by the given index. - * - * @param index index to open, which may be a primary key index - * @param keyValues list of exactly matching values to apply to index - */ - protected abstract Cursor fetch(StorableIndex index, Object[] keyValues) - throws FetchException; + public static interface Support { + /** + * Select at most one Storable referenced by an index. The identity + * values fully specify all elements of the index, and the index is + * unique. + * + * @param index index to open, which may be a primary key index + * @param identityValues of exactly matching values to apply to index + */ + Cursor fetch(StorableIndex index, Object[] identityValues) + throws FetchException; + } } diff --git a/src/main/java/com/amazon/carbonado/qe/OrderingList.java b/src/main/java/com/amazon/carbonado/qe/OrderingList.java index b32f430..08a39f5 100644 --- a/src/main/java/com/amazon/carbonado/qe/OrderingList.java +++ b/src/main/java/com/amazon/carbonado/qe/OrderingList.java @@ -20,13 +20,16 @@ package com.amazon.carbonado.qe; import java.util.AbstractList; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import org.cojen.util.SoftValuedHashMap; import com.amazon.carbonado.Storable; +import com.amazon.carbonado.info.ChainedProperty; import com.amazon.carbonado.info.OrderedProperty; import com.amazon.carbonado.info.StorableIntrospector; @@ -179,6 +182,28 @@ public class OrderingList extends AbstractList reduce() { + if (size() == 0) { + return this; + } + + Set> seen = new HashSet>(); + OrderingList newList = emptyList(); + + for (OrderedProperty property : this) { + ChainedProperty chained = property.getChainedProperty(); + if (!seen.contains(chained)) { + newList = newList.concat(property); + seen.add(chained); + } + } + + return newList; + } + /** * Returns this list with all orderings in reverse. */ diff --git a/src/main/java/com/amazon/carbonado/qe/PropertyFilterList.java b/src/main/java/com/amazon/carbonado/qe/PropertyFilterList.java index 5842ef2..f17a2df 100644 --- a/src/main/java/com/amazon/carbonado/qe/PropertyFilterList.java +++ b/src/main/java/com/amazon/carbonado/qe/PropertyFilterList.java @@ -83,7 +83,7 @@ class PropertyFilterList { } }, null); - Collections.sort(list, new PropertyFilterComparator()); + Collections.sort(list, new PFComparator()); ((ArrayList) list).trimToSize(); list = Collections.unmodifiableList(list); @@ -96,7 +96,7 @@ class PropertyFilterList { return list; } - private static class PropertyFilterComparator + private static class PFComparator implements Comparator> { public int compare(PropertyFilter a, PropertyFilter b) { diff --git a/src/main/java/com/amazon/carbonado/qe/QueryEngine.java b/src/main/java/com/amazon/carbonado/qe/QueryEngine.java new file mode 100644 index 0000000..1362045 --- /dev/null +++ b/src/main/java/com/amazon/carbonado/qe/QueryEngine.java @@ -0,0 +1,67 @@ +/* + * 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 com.amazon.carbonado.IsolationLevel; +import com.amazon.carbonado.Storable; +import com.amazon.carbonado.Transaction; + +import com.amazon.carbonado.filter.FilterValues; + +/** + * Complete rule-based query engine implementation. + * + * @author Brian S O'Neill + */ +public class QueryEngine extends StandardQueryFactory { + final RepositoryAccess mRepoAccess; + final QueryExecutorFactory mExecutorFactory; + + public QueryEngine(Class type, RepositoryAccess access) { + super(type); + mRepoAccess = access; + mExecutorFactory = new QueryExecutorCache(new UnionQueryAnalyzer(type, access)); + } + + protected StandardQuery createQuery(FilterValues values, OrderingList ordering) { + return new Query(values, ordering); + } + + private class Query extends StandardQuery { + Query(FilterValues values, OrderingList ordering) { + super(values, ordering); + } + + protected Transaction enterTransaction(IsolationLevel level) { + return mRepoAccess.getRootRepository().enterTransaction(level); + } + + protected QueryFactory queryFactory() { + return QueryEngine.this; + } + + protected QueryExecutorFactory executorFactory() { + return mExecutorFactory; + } + + protected StandardQuery newInstance(FilterValues values, OrderingList ordering) { + return new Query(values, ordering); + } + } +} diff --git a/src/main/java/com/amazon/carbonado/qe/QueryExecutor.java b/src/main/java/com/amazon/carbonado/qe/QueryExecutor.java index c81077f..6348fb9 100644 --- a/src/main/java/com/amazon/carbonado/qe/QueryExecutor.java +++ b/src/main/java/com/amazon/carbonado/qe/QueryExecutor.java @@ -36,6 +36,7 @@ import com.amazon.carbonado.info.OrderedProperty; * together forming a query plan. * * @author Brian S O'Neill + * @see QueryExecutorFactory */ public interface QueryExecutor { /** diff --git a/src/main/java/com/amazon/carbonado/qe/QueryExecutorCache.java b/src/main/java/com/amazon/carbonado/qe/QueryExecutorCache.java new file mode 100644 index 0000000..da944fa --- /dev/null +++ b/src/main/java/com/amazon/carbonado/qe/QueryExecutorCache.java @@ -0,0 +1,86 @@ +/* + * 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.Map; + +import org.cojen.util.SoftValuedHashMap; +import org.cojen.util.WeakIdentityMap; + +import com.amazon.carbonado.RepositoryException; +import com.amazon.carbonado.Storable; + +import com.amazon.carbonado.filter.Filter; + +import com.amazon.carbonado.info.OrderedProperty; + +/** + * QueryExecutors should be cached since expensive analysis is often required + * to build them. + * + * @author Brian S O'Neill + */ +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; + + protected QueryExecutorCache(QueryExecutorFactory factory) { + if (factory == null) { + throw new IllegalArgumentException(); + } + mFactory = factory; + mFilterToExecutor = new WeakIdentityMap(7); + } + + public Class getStorableType() { + return mFactory.getStorableType(); + } + + /** + * Returns an executor from the cache. + * + * @param filter optional filter + * @param ordering optional order-by properties + */ + public QueryExecutor executor(Filter filter, OrderingList ordering) + throws RepositoryException + { + Map, QueryExecutor> map; + synchronized (mFilterToExecutor) { + map = mFilterToExecutor.get(filter); + if (map == null) { + map = new SoftValuedHashMap(7); + mFilterToExecutor.put(filter, map); + } + } + + QueryExecutor executor; + synchronized (map) { + executor = map.get(ordering); + if (executor == null) { + executor = mFactory.executor(filter, ordering); + map.put(ordering, executor); + } + } + + return executor; + } +} diff --git a/src/main/java/com/amazon/carbonado/qe/QueryExecutorFactory.java b/src/main/java/com/amazon/carbonado/qe/QueryExecutorFactory.java new file mode 100644 index 0000000..1b1e680 --- /dev/null +++ b/src/main/java/com/amazon/carbonado/qe/QueryExecutorFactory.java @@ -0,0 +1,44 @@ +/* + * 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 com.amazon.carbonado.RepositoryException; +import com.amazon.carbonado.Storable; + +import com.amazon.carbonado.filter.Filter; + +import com.amazon.carbonado.info.OrderedProperty; + +/** + * Produces {@link QueryExecutor} instances from a query specification. + * + * @author Brian S O'Neill + */ +public interface QueryExecutorFactory { + Class getStorableType(); + + /** + * Returns an executor that handles the given query specification. + * + * @param filter optional filter + * @param ordering optional order-by properties + */ + QueryExecutor executor(Filter filter, OrderingList ordering) + throws RepositoryException; +} diff --git a/src/main/java/com/amazon/carbonado/qe/QueryFactory.java b/src/main/java/com/amazon/carbonado/qe/QueryFactory.java new file mode 100644 index 0000000..738d18a --- /dev/null +++ b/src/main/java/com/amazon/carbonado/qe/QueryFactory.java @@ -0,0 +1,44 @@ +/* + * 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 com.amazon.carbonado.FetchException; +import com.amazon.carbonado.Query; +import com.amazon.carbonado.Storable; + +import com.amazon.carbonado.filter.FilterValues; + +import com.amazon.carbonado.info.OrderedProperty; + +/** + * Produces {@link Query} instances from a query specification. + * + * @author Brian S O'Neill + */ +public interface QueryFactory { + Class getStorableType(); + + /** + * Returns a query that handles the given query specification. + * + * @param values optional values object, defaults to open filter if null + * @param ordering optional order-by properties + */ + Query query(FilterValues values, OrderingList ordering) throws FetchException; +} diff --git a/src/main/java/com/amazon/carbonado/qe/RepositoryAccess.java b/src/main/java/com/amazon/carbonado/qe/RepositoryAccess.java new file mode 100644 index 0000000..6e3c3de --- /dev/null +++ b/src/main/java/com/amazon/carbonado/qe/RepositoryAccess.java @@ -0,0 +1,44 @@ +/* + * 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 com.amazon.carbonado.MalformedTypeException; +import com.amazon.carbonado.Repository; +import com.amazon.carbonado.Storable; +import com.amazon.carbonado.SupportException; + +/** + * Provides internal access to a {@link Repository}, necessary for query + * execution. + * + * @author Brian S O'Neill + */ +public interface RepositoryAccess { + Repository getRootRepository(); + + /** + * Returns a StorageAccess instance for the given user defined Storable + * class or interface. + * + * @return specific type of StorageAccess instance + * @throws IllegalArgumentException if specified type is null + * @throws MalformedTypeException if specified type is not suitable + */ + StorageAccess storageAccessFor(Class type); +} diff --git a/src/main/java/com/amazon/carbonado/qe/SortedQueryExecutor.java b/src/main/java/com/amazon/carbonado/qe/SortedQueryExecutor.java index 05474ed..7b12e43 100644 --- a/src/main/java/com/amazon/carbonado/qe/SortedQueryExecutor.java +++ b/src/main/java/com/amazon/carbonado/qe/SortedQueryExecutor.java @@ -26,6 +26,7 @@ import com.amazon.carbonado.Cursor; import com.amazon.carbonado.FetchException; import com.amazon.carbonado.Storable; +import com.amazon.carbonado.cursor.ArraySortBuffer; import com.amazon.carbonado.cursor.SortBuffer; import com.amazon.carbonado.cursor.SortedCursor; @@ -35,12 +36,13 @@ import com.amazon.carbonado.filter.FilterValues; import com.amazon.carbonado.info.OrderedProperty; /** - * Abstract QueryExecutor which wraps another and sorts the results. + * QueryExecutor which wraps another and sorts the results. * * @author Brian S O'Neill * @see SortedCursor */ -public abstract class SortedQueryExecutor extends AbstractQueryExecutor { +public class SortedQueryExecutor extends AbstractQueryExecutor { + private final Support mSupport; private final QueryExecutor mExecutor; private final Comparator mHandledComparator; @@ -50,19 +52,22 @@ public abstract class SortedQueryExecutor extends AbstractQu private final OrderingList mRemainderOrdering; /** + * @param support optional support to control sort buffer; if null, array is used * @param executor executor to wrap * @param handledOrdering optional handled ordering * @param remainderOrdering required remainder ordering - * @throws IllegalArgumentException if executor is null or if remainder - * ordering is empty + * @throws IllegalArgumentException if executor is null or if + * remainder ordering is empty */ - public SortedQueryExecutor(QueryExecutor executor, + public SortedQueryExecutor(Support support, + QueryExecutor executor, OrderingList handledOrdering, OrderingList remainderOrdering) { if (executor == null) { throw new IllegalArgumentException(); } + mSupport = support; mExecutor = executor; if (handledOrdering != null && handledOrdering.size() == 0) { @@ -90,7 +95,8 @@ public abstract class SortedQueryExecutor extends AbstractQu public Cursor fetch(FilterValues values) throws FetchException { Cursor cursor = mExecutor.fetch(values); - SortBuffer buffer = createSortBuffer(); + SortBuffer buffer = + mSupport == null ? new ArraySortBuffer() : mSupport.createSortBuffer(); return new SortedCursor(cursor, buffer, mHandledComparator, mFinisherComparator); } @@ -122,8 +128,10 @@ public abstract class SortedQueryExecutor extends AbstractQu return true; } - /** - * Implementation must return an empty buffer for sorting. - */ - protected abstract SortBuffer createSortBuffer(); + public static interface Support { + /** + * Implementation must return an empty buffer for sorting. + */ + SortBuffer createSortBuffer(); + } } diff --git a/src/main/java/com/amazon/carbonado/qe/StandardQuery.java b/src/main/java/com/amazon/carbonado/qe/StandardQuery.java index 7304828..bb1dcc3 100644 --- a/src/main/java/com/amazon/carbonado/qe/StandardQuery.java +++ b/src/main/java/com/amazon/carbonado/qe/StandardQuery.java @@ -27,6 +27,7 @@ import com.amazon.carbonado.FetchException; import com.amazon.carbonado.IsolationLevel; import com.amazon.carbonado.PersistException; import com.amazon.carbonado.PersistMultipleException; +import com.amazon.carbonado.RepositoryException; import com.amazon.carbonado.Storage; import com.amazon.carbonado.Storable; import com.amazon.carbonado.Transaction; @@ -58,13 +59,6 @@ public abstract class StandardQuery extends AbstractQuery private volatile QueryExecutor mExecutor; - /** - * @param values optional values object, defaults to open filter if null - */ - protected StandardQuery(FilterValues values) { - this(values, null); - } - /** * @param values optional values object, defaults to open filter if null * @param ordering optional order-by properties @@ -78,7 +72,7 @@ public abstract class StandardQuery extends AbstractQuery } public Class getStorableType() { - return getRootStorage().getStorableType(); + return queryFactory().getStorableType(); } public Filter getFilter() { @@ -141,47 +135,50 @@ public abstract class StandardQuery extends AbstractQuery } public Query and(Filter filter) throws FetchException { - FilterValues values = mValues; - Query newQuery; - if (values == null) { - newQuery = getRootStorage().query(filter); + FilterValues newValues; + if (mValues == null) { + newValues = filter.initialFilterValues(); } else { - newQuery = getRootStorage().query(values.getFilter().and(filter)); - newQuery = newQuery.withValues(values.getValues()); + newValues = mValues.getFilter().and(filter) + .initialFilterValues().withValues(mValues.getValues()); } - return mOrdering.size() == 0 ? newQuery : newQuery.orderBy(mOrdering.asStringArray()); + return createQuery(newValues, mOrdering); } public Query or(Filter filter) throws FetchException { - FilterValues values = mValues; - if (values == null) { + if (mValues == null) { throw new IllegalStateException("Query is already guaranteed to fetch everything"); } - Query newQuery = getRootStorage().query(values.getFilter().or(filter)); - newQuery = newQuery.withValues(values.getValues()); - return mOrdering.size() == 0 ? newQuery : newQuery.orderBy(mOrdering.asStringArray()); + FilterValues newValues = mValues.getFilter().or(filter) + .initialFilterValues().withValues(mValues.getValues()); + return createQuery(newValues, mOrdering); } public Query not() throws FetchException { - FilterValues values = mValues; - if (values == null) { - return new EmptyQuery(getRootStorage(), mOrdering); + if (mValues == null) { + return new EmptyQuery(queryFactory(), mOrdering); } - Query newQuery = getRootStorage().query(values.getFilter().not()); - newQuery = newQuery.withValues(values.getSuppliedValues()); - return mOrdering.size() == 0 ? newQuery : newQuery.orderBy(mOrdering.asStringArray()); + // FIXME: Just like with DelegatedQueryExecutor, need a better way of + // transfering values. + FilterValues newValues = mValues.getFilter().not() + .initialFilterValues().withValues(mValues.getSuppliedValues()); + return createQuery(newValues, mOrdering); } public Query orderBy(String property) throws FetchException { - return newInstance(mValues, OrderingList.get(getStorableType(), property)); + return createQuery(mValues, OrderingList.get(getStorableType(), property)); } public Query orderBy(String... properties) throws FetchException { - return newInstance(mValues, OrderingList.get(getStorableType(), properties)); + return createQuery(mValues, OrderingList.get(getStorableType(), properties)); } public Cursor fetch() throws FetchException { - return executor().fetch(mValues); + try { + return executor().fetch(mValues); + } catch (RepositoryException e) { + throw e.toFetchException(); + } } public Cursor fetchAfter(S start) throws FetchException { @@ -216,19 +213,19 @@ public abstract class StandardQuery extends AbstractQuery lastSubFilter = lastSubFilter.and(propertyName, RelOp.EQ); } - Query newQuery = this.and(orderFilter); + Query query = this.and(orderFilter); for (int i=0; i cursor = fetch(); boolean result; @@ -259,7 +256,7 @@ public abstract class StandardQuery extends AbstractQuery } public void deleteAll() throws PersistException { - Transaction txn = enterTransactionForDelete(IsolationLevel.READ_COMMITTED); + Transaction txn = enterTransaction(IsolationLevel.READ_COMMITTED); try { Cursor cursor = fetch(); try { @@ -282,23 +279,37 @@ public abstract class StandardQuery extends AbstractQuery } public long count() throws FetchException { - return executor().count(mValues); + try { + return executor().count(mValues); + } catch (RepositoryException e) { + throw e.toFetchException(); + } } public boolean printNative(Appendable app, int indentLevel) throws IOException { - return executor().printNative(app, indentLevel, mValues); + try { + return executor().printNative(app, indentLevel, mValues); + } catch (RepositoryException e) { + return false; + } } public boolean printPlan(Appendable app, int indentLevel) throws IOException { - return executor().printPlan(app, indentLevel, mValues); + try { + return executor().printPlan(app, indentLevel, mValues); + } catch (RepositoryException e) { + return false; + } } @Override public int hashCode() { - int hash = getRootStorage().hashCode() * 31; + int hash = queryFactory().hashCode(); + hash = hash * 31 + executorFactory().hashCode(); if (mValues != null) { - hash += mValues.hashCode(); + hash = hash * 31 + mValues.hashCode(); } + hash = hash * 31 + mOrdering.hashCode(); return hash; } @@ -309,8 +320,10 @@ public abstract class StandardQuery extends AbstractQuery } if (obj instanceof StandardQuery) { StandardQuery other = (StandardQuery) obj; - return getRootStorage().equals(other.getRootStorage()) && - (mValues == null ? (other.mValues == null) : (mValues.equals(other.mValues))); + return queryFactory().equals(other.queryFactory()) + && executorFactory().equals(other.executorFactory()) + && (mValues == null ? (other.mValues == null) : (mValues.equals(other.mValues))) + && mOrdering.equals(other.mOrdering); } return false; } @@ -351,18 +364,32 @@ public abstract class StandardQuery extends AbstractQuery } /** - * Clears any cached reference to a query executor. The next time this - * Query is used, it will get an executor from getExecutor and cache a - * reference to it. + * Ensures that a cached query executor reference is available. If not, the + * query executor factory is called and the executor is cached. */ - protected void clearExecutorReference() { - mExecutor = null; + protected void setExecutorReference() throws RepositoryException { + executor(); } /** - * Return the root Storage object that the query is operating on. + * Resets any cached reference to a query executor. If a reference is + * available, it is replaced, but a clear reference is not set. */ - protected abstract Storage getRootStorage(); + protected void resetExecutorReference() throws RepositoryException { + if (mExecutor != null) { + Filter filter = mValues == null ? null : mValues.getFilter(); + mExecutor = executorFactory().executor(filter, mOrdering); + } + } + + /** + * Clears any cached reference to a query executor. The next time this + * Query is used, it will get an executor from the query executor factory + * and cache a reference to it. + */ + protected void clearExecutorReference() { + mExecutor = null; + } /** * Enter a transaction as needed by the standard delete operation, or null @@ -370,16 +397,17 @@ public abstract class StandardQuery extends AbstractQuery * * @param level minimum desired isolation level */ - protected abstract Transaction enterTransactionForDelete(IsolationLevel level); + protected abstract Transaction enterTransaction(IsolationLevel level); /** - * Return a new or cached query executor. - * - * @param values optional values object, defaults to open filter if null - * @param orderings order-by properties, never null + * Return a QueryFactory which is used to form new queries from this one. */ - protected abstract QueryExecutor getExecutor(FilterValues values, - OrderingList orderings); + protected abstract QueryFactory queryFactory(); + + /** + * Return a QueryExecutorFactory which is used to get an executor. + */ + protected abstract QueryExecutorFactory executorFactory(); /** * Return a new or cached instance of StandardQuery implementation, using @@ -396,10 +424,21 @@ public abstract class StandardQuery extends AbstractQuery return newInstance(values, mOrdering); } - private QueryExecutor executor() { + private Query createQuery(FilterValues values) throws FetchException { + return queryFactory().query(values, mOrdering); + } + + private Query createQuery(FilterValues values, OrderingList ordering) + throws FetchException + { + return queryFactory().query(values, ordering); + } + + private QueryExecutor executor() throws RepositoryException { QueryExecutor executor = mExecutor; if (executor == null) { - mExecutor = executor = getExecutor(mValues, mOrdering); + Filter filter = mValues == null ? null : mValues.getFilter(); + mExecutor = executor = executorFactory().executor(filter, mOrdering); } return executor; } diff --git a/src/main/java/com/amazon/carbonado/qe/StandardQueryFactory.java b/src/main/java/com/amazon/carbonado/qe/StandardQueryFactory.java new file mode 100644 index 0000000..8b9a3a9 --- /dev/null +++ b/src/main/java/com/amazon/carbonado/qe/StandardQueryFactory.java @@ -0,0 +1,227 @@ +/* + * 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.Map; + +import org.cojen.util.SoftValuedHashMap; +import org.cojen.util.WeakIdentityMap; + +import com.amazon.carbonado.FetchException; +import com.amazon.carbonado.IsolationLevel; +import com.amazon.carbonado.Query; +import com.amazon.carbonado.RepositoryException; +import com.amazon.carbonado.Storable; +import com.amazon.carbonado.Transaction; + +import com.amazon.carbonado.filter.ClosedFilter; +import com.amazon.carbonado.filter.Filter; +import com.amazon.carbonado.filter.FilterValues; + +/** + * Builds and caches StandardQuery instances. + * + * @author Brian S O'Neill + */ +public abstract class StandardQueryFactory implements QueryFactory { + private final Class mType; + private final boolean mLazySetExecutor; + + private final Map> mStringToQuery; + + // Maps filters to maps which map ordering lists to queries. + private final Map, Map, Query>> mFilterToQuery; + + protected StandardQueryFactory(Class type) { + this(type, false); + } + + /** + * @param lazySetExecutor by default, query executors are built and set + * eagerly. Pass true to build and set executor on first query use. + */ + protected StandardQueryFactory(Class type, boolean lazySetExecutor) { + if (type == null) { + throw new IllegalArgumentException(); + } + mType = type; + mLazySetExecutor = lazySetExecutor; + mStringToQuery = new SoftValuedHashMap(7); + mFilterToQuery = new WeakIdentityMap(7); + } + + public Class getStorableType() { + return mType; + } + + /** + * Returns a new or cached query that fetches everything. + */ + public Query query() throws FetchException { + return query(Filter.getOpenFilter(mType), null); + } + + /** + * Returns a new or cached query for the given filter. + * + * @throws IllegalArgumentException if filter is null + */ + public Query query(String filter) throws FetchException { + synchronized (mStringToQuery) { + Query query = mStringToQuery.get(filter); + if (query == null) { + if (filter == null) { + throw new IllegalArgumentException("Query filter must not be null"); + } + query = query(Filter.filterFor(mType, filter), null); + mStringToQuery.put(filter, query); + } + return query; + } + } + + /** + * Returns a new or cached query for the given filter. + * + * @throws IllegalArgumentException if filter is null + */ + public Query query(Filter filter) throws FetchException { + return query(filter, 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) throws FetchException { + Map, Query> map; + synchronized (mFilterToQuery) { + map = mFilterToQuery.get(filter); + if (map == null) { + if (filter == null) { + throw new IllegalArgumentException("Query filter must not be null"); + } + map = new SoftValuedHashMap(7); + mFilterToQuery.put(filter, map); + } + } + + Query query; + synchronized (map) { + query = map.get(ordering); + if (query == null) { + FilterValues values = filter.initialFilterValues(); + if (values == null && filter instanceof ClosedFilter) { + query = new EmptyQuery(this, ordering); + } else { + StandardQuery standardQuery = createQuery(values, ordering); + if (!mLazySetExecutor) { + try { + standardQuery.setExecutorReference(); + } catch (RepositoryException e) { + throw e.toFetchException(); + } + } + query = standardQuery; + } + map.put(ordering, query); + } + } + + return query; + } + + /** + * Returns a new or cached query for the given query specification. + * + * @param values optional values object, defaults to open filter if null + * @param ordering optional order-by properties + */ + public Query query(FilterValues values, OrderingList ordering) throws FetchException { + Query query; + if (values == null) { + query = query(Filter.getOpenFilter(mType), ordering); + } else { + // FIXME: Just like with DelegatedQueryExecutor, need a better way + // of transfering values. + query = query(values.getFilter(), ordering).withValues(values.getSuppliedValues()); + } + return query; + } + + /** + * For each cached query, calls {@link StandardQuery#setExecutorReference}. + */ + public void setExecutorReferences() throws RepositoryException { + for (StandardQuery query : gatherQueries()) { + query.setExecutorReference(); + } + } + + /** + * For each cached query, calls {@link StandardQuery#resetExecutorReference}. + * This call can be used to rebuild all cached query plans after the set of + * available indexes has changed. + */ + public void resetExecutorReferences() throws RepositoryException { + for (StandardQuery query : gatherQueries()) { + query.resetExecutorReference(); + } + } + + /** + * For each cached query, calls {@link StandardQuery#clearExecutorReference}. + * This call can be used to clear all cached query plans after the set of + * available indexes has changed. + */ + public void clearExecutorReferences() { + for (StandardQuery query : gatherQueries()) { + query.clearExecutorReference(); + } + } + + /** + * Implement this method to return query implementations. + * + * @param values optional values object, defaults to open filter if null + * @param ordering optional order-by properties + */ + protected abstract StandardQuery createQuery(FilterValues values, + OrderingList ordering); + + private ArrayList> gatherQueries() { + // Copy all queries and operate on the copy instead of holding lock for + // potentially a long time. + ArrayList> queries = new ArrayList>(); + + synchronized (mFilterToQuery) { + for (Map, Query> map : mFilterToQuery.values()) { + for (Query query : map.values()) { + if (query instanceof StandardQuery) { + queries.add((StandardQuery) query); + } + } + } + } + + return queries; + } +} diff --git a/src/main/java/com/amazon/carbonado/qe/StorageAccess.java b/src/main/java/com/amazon/carbonado/qe/StorageAccess.java new file mode 100644 index 0000000..5f93952 --- /dev/null +++ b/src/main/java/com/amazon/carbonado/qe/StorageAccess.java @@ -0,0 +1,63 @@ +/* + * 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.Collection; + +import com.amazon.carbonado.Cursor; +import com.amazon.carbonado.FetchException; +import com.amazon.carbonado.Storable; +import com.amazon.carbonado.Storage; + +import com.amazon.carbonado.cursor.SortBuffer; + +import com.amazon.carbonado.info.StorableIndex; + +/** + * Provides internal access to a {@link Storage}, necessary for query + * execution. + * + * @author Brian S O'Neill + */ +public interface StorageAccess + extends FullScanQueryExecutor.Support, + FullScanIndexedQueryExecutor.Support, + KeyQueryExecutor.Support, + IndexedQueryExecutor.Support, + SortedQueryExecutor.Support +{ + /** + * Returns the specific type of Storable managed by this object. + */ + Class getStorableType(); + + /** + * Returns all the available indexes. + */ + Collection> getAllIndexes(); + + /** + * If the given index is not directly supported by storage, queries should + * be delegated. Return the storage to delegate to or null if index should + * not be delegated. + * + * @throws IllegalArgumentException if index is unknown + */ + Storage storageDelegate(StorableIndex index); +} diff --git a/src/main/java/com/amazon/carbonado/qe/UnionQueryAnalyzer.java b/src/main/java/com/amazon/carbonado/qe/UnionQueryAnalyzer.java index 039aea4..e0d49c2 100644 --- a/src/main/java/com/amazon/carbonado/qe/UnionQueryAnalyzer.java +++ b/src/main/java/com/amazon/carbonado/qe/UnionQueryAnalyzer.java @@ -26,7 +26,11 @@ import java.util.List; import java.util.Map; import java.util.Set; +import com.amazon.carbonado.FetchException; +import com.amazon.carbonado.RepositoryException; import com.amazon.carbonado.Storable; +import com.amazon.carbonado.Storage; +import com.amazon.carbonado.SupportException; import com.amazon.carbonado.filter.AndFilter; import com.amazon.carbonado.filter.Filter; @@ -54,16 +58,22 @@ import com.amazon.carbonado.info.StorableKey; * * @author Brian S O'Neill */ -public class UnionQueryAnalyzer { +public class UnionQueryAnalyzer implements QueryExecutorFactory { final IndexedQueryAnalyzer mIndexAnalyzer; + final RepositoryAccess mRepoAccess; /** * @param type type of storable being queried - * @param indexProvider + * @param access repository access for examing available indexes * @throws IllegalArgumentException if type or indexProvider is null */ - public UnionQueryAnalyzer(Class type, IndexProvider indexProvider) { - mIndexAnalyzer = new IndexedQueryAnalyzer(type, indexProvider); + public UnionQueryAnalyzer(Class type, RepositoryAccess access) { + mIndexAnalyzer = new IndexedQueryAnalyzer(type, access); + mRepoAccess = access; + } + + public Class getStorableType() { + return mIndexAnalyzer.getStorableType(); } /** @@ -72,8 +82,6 @@ public class UnionQueryAnalyzer { */ public Result analyze(Filter filter, OrderingList ordering) { 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"); } @@ -81,11 +89,33 @@ public class UnionQueryAnalyzer { ordering = OrderingList.emptyList(); } + return new Result(buildSubResults(filter, ordering)); + } + + /** + * Returns an executor that handles the given query specification. + * + * @param filter optional filter which must be {@link Filter#isBound bound} + * @param ordering optional properties which define desired ordering + */ + public QueryExecutor executor(Filter filter, OrderingList ordering) + throws RepositoryException + { + return analyze(filter, ordering).createExecutor(); + } + + /** + * Splits the filter into sub-results, merges sub-results, and possibly + * imposes a total ordering. + */ + private List.Result> + buildSubResults(Filter filter, OrderingList ordering) + { List.Result> subResults = splitIntoSubResults(filter, ordering); if (subResults.size() <= 1) { // Total ordering not required. - return new Result(subResults); + return subResults; } // If any orderings have an unspecified direction, switch to ASCENDING @@ -111,7 +141,7 @@ public class UnionQueryAnalyzer { if (subResults.size() <= 1) { // Total ordering no longer required. - return new Result(subResults); + return subResults; } } @@ -125,7 +155,7 @@ public class UnionQueryAnalyzer { ChainedProperty property = op.getChainedProperty(); if (pruneKeys(keys, property)) { // Found a key which is fully covered, indicating total ordering. - return new Result(subResults); + return subResults; } } @@ -194,7 +224,7 @@ public class UnionQueryAnalyzer { } } - return new Result(subResults); + return subResults; } /** @@ -263,6 +293,9 @@ public class UnionQueryAnalyzer { return Direction.UNSPECIFIED; } + /** + * Splits the filter into sub-results and possibly merges them. + */ private List.Result> splitIntoSubResults(Filter filter, OrderingList ordering) { @@ -340,16 +373,28 @@ public class UnionQueryAnalyzer { 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. + Storage storageDelegate(IndexedQueryAnalyzer.Result result) { + StorableIndex localIndex = result.getLocalIndex(); + StorageAccess localAccess = mRepoAccess.storageAccessFor(getStorableType()); + if (localIndex != null) { + return localAccess.storageDelegate(localIndex); + } + + StorableIndex foreignIndex = result.getForeignIndex(); + StorageAccess foreignAccess = mRepoAccess.storageAccessFor(foreignIndex.getStorableType()); + + return foreignAccess.storageDelegate(foreignIndex); + } + + public class Result { private final List.Result> mSubResults; Result(List.Result> subResults) { - mSubResults = subResults; + if (subResults.size() < 1) { + throw new IllegalArgumentException(); + } + mSubResults = Collections.unmodifiableList(subResults); } /** @@ -359,6 +404,27 @@ public class UnionQueryAnalyzer { public List.Result> getSubResults() { return mSubResults; } + + /** + * Creates a QueryExecutor based on this result. + */ + public QueryExecutor createExecutor() + throws SupportException, FetchException, RepositoryException + { + List.Result> subResults = getSubResults(); + int size = subResults.size(); + + if (size == 1) { + return subResults.get(0).createExecutor(); + } + + List> executors = new ArrayList>(size); + for (int i=0; i(executors); + } } /** @@ -482,6 +548,8 @@ public class UnionQueryAnalyzer { IndexedQueryAnalyzer.Result subResult = mIndexAnalyzer.analyze(subFilter, mOrdering); + Storage subResultStorage = storageDelegate(subResult); + // Rather than blindly add to mSubResults, try to merge with // another result. This in turn reduces the number of cursors // needed by the union. @@ -489,7 +557,15 @@ public class UnionQueryAnalyzer { int size = mSubResults.size(); for (int i=0; i.Result existing = mSubResults.get(i); - if (existing.canMergeRemainder(subResult)) { + boolean canMerge = existing.canMergeRemainder(subResult); + if (!canMerge) { + Storage existingStorage = storageDelegate(existing); + if (existingStorage != null && existingStorage == subResultStorage) { + // Merge common delegates together. + canMerge = true; + } + } + if (canMerge) { mSubResults.set(i, existing.mergeRemainder(subResult)); return; } diff --git a/src/main/java/com/amazon/carbonado/spi/BaseQuery.java b/src/main/java/com/amazon/carbonado/spi/BaseQuery.java index b1a1d45..e5fc3b0 100644 --- a/src/main/java/com/amazon/carbonado/spi/BaseQuery.java +++ b/src/main/java/com/amazon/carbonado/spi/BaseQuery.java @@ -211,7 +211,9 @@ public abstract class BaseQuery extends AbstractQuery imp public Query not() throws FetchException { FilterValues values = getFilterValues(); if (values == null) { - return new EmptyQuery(mStorage, mOrderings); + // FIXME: fix this or remove BaseQuery class. + throw new UnsupportedOperationException(); + //return new EmptyQuery(mStorage, mOrderings); } Query newQuery = mStorage.query(values.getFilter().not()); newQuery = newQuery.withValues(values.getSuppliedValues()); -- cgit v1.2.3