summaryrefslogtreecommitdiff
path: root/src/main/java/com/amazon/carbonado/qe
diff options
context:
space:
mode:
authorBrian S. O'Neill <bronee@gmail.com>2006-09-30 23:07:46 +0000
committerBrian S. O'Neill <bronee@gmail.com>2006-09-30 23:07:46 +0000
commitf5a69fbf5e7e343d094687263604ce18b7b6dae5 (patch)
tree45e302cbdf1be177b1080b4227910f0052b7031c /src/main/java/com/amazon/carbonado/qe
parentbddf5aea31f3ed34b43d26133de1671b2acbcc46 (diff)
Finished join optimization.
Diffstat (limited to 'src/main/java/com/amazon/carbonado/qe')
-rw-r--r--src/main/java/com/amazon/carbonado/qe/CompositeScore.java38
-rw-r--r--src/main/java/com/amazon/carbonado/qe/DelegatedQueryExecutorFactory.java51
-rw-r--r--src/main/java/com/amazon/carbonado/qe/FullScanQueryExecutor.java6
-rw-r--r--src/main/java/com/amazon/carbonado/qe/IndexedQueryAnalyzer.java130
-rw-r--r--src/main/java/com/amazon/carbonado/qe/IndexedQueryExecutor.java32
-rw-r--r--src/main/java/com/amazon/carbonado/qe/IterableQueryExecutor.java2
-rw-r--r--src/main/java/com/amazon/carbonado/qe/JoinedQueryExecutor.java643
-rw-r--r--src/main/java/com/amazon/carbonado/qe/KeyQueryExecutor.java6
-rw-r--r--src/main/java/com/amazon/carbonado/qe/OrderingList.java17
-rw-r--r--src/main/java/com/amazon/carbonado/qe/QueryEngine.java12
-rw-r--r--src/main/java/com/amazon/carbonado/qe/QueryExecutorFactory.java2
-rw-r--r--src/main/java/com/amazon/carbonado/qe/SortedQueryExecutor.java7
-rw-r--r--src/main/java/com/amazon/carbonado/qe/StorageAccess.java5
13 files changed, 743 insertions, 208 deletions
diff --git a/src/main/java/com/amazon/carbonado/qe/CompositeScore.java b/src/main/java/com/amazon/carbonado/qe/CompositeScore.java
index 6d0fad5..eba6106 100644
--- a/src/main/java/com/amazon/carbonado/qe/CompositeScore.java
+++ b/src/main/java/com/amazon/carbonado/qe/CompositeScore.java
@@ -92,6 +92,18 @@ public class CompositeScore<S extends Storable> {
}
/**
+ * 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 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
@@ -100,7 +112,7 @@ public class CompositeScore<S extends Storable> {
* {@code 0} if equal, or {@code >0} if second is better.
*/
public static Comparator<CompositeScore<?>> fullComparator() {
- return Full.INSTANCE;
+ return Comp.FULL;
}
private final FilteringScore<S> mFilteringScore;
@@ -157,8 +169,15 @@ public class CompositeScore<S extends Storable> {
return "CompositeScore {" + getFilteringScore() + ", " + getOrderingScore() + '}';
}
- private static class Full implements Comparator<CompositeScore<?>> {
- static final Comparator<CompositeScore<?>> INSTANCE = new Full();
+ private static class Comp implements Comparator<CompositeScore<?>> {
+ static final Comparator<CompositeScore<?>> LOCAL_FOREIGN = new Comp(false);
+ static final Comparator<CompositeScore<?>> FULL = new Comp(true);
+
+ private final boolean mFull;
+
+ private Comp(boolean full) {
+ mFull = full;
+ }
public int compare(CompositeScore<?> first, CompositeScore<?> second) {
int result = FilteringScore.nullCompare(first, second);
@@ -189,7 +208,18 @@ public class CompositeScore<S extends Storable> {
}
}
- result = FilteringScore.fullComparator().compare(firstScore, secondScore);
+ if (mFull) {
+ result = FilteringScore.fullComparator().compare(firstScore, secondScore);
+ } else {
+ // Favor index that has any matches.
+ if (firstScore.hasAnyMatches()) {
+ if (!secondScore.hasAnyMatches()) {
+ return -1;
+ }
+ } else if (secondScore.hasAnyMatches()) {
+ return 1;
+ }
+ }
return result;
}
diff --git a/src/main/java/com/amazon/carbonado/qe/DelegatedQueryExecutorFactory.java b/src/main/java/com/amazon/carbonado/qe/DelegatedQueryExecutorFactory.java
new file mode 100644
index 0000000..fae22b4
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/qe/DelegatedQueryExecutorFactory.java
@@ -0,0 +1,51 @@
+/*
+ * 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.Storable;
+import com.amazon.carbonado.Storage;
+
+import com.amazon.carbonado.filter.Filter;
+
+/**
+ * QueryExecutorFactory which produces executors which delegate via {@link DelegatedQueryExecutor}.
+ *
+ * @author Brian S O'Neill
+ */
+public class DelegatedQueryExecutorFactory<S extends Storable> implements QueryExecutorFactory<S> {
+ private final Storage<S> mStorage;
+
+ public DelegatedQueryExecutorFactory(Storage<S> rootStorage) {
+ if (rootStorage == null) {
+ throw new IllegalArgumentException();
+ }
+ mStorage = rootStorage;
+ }
+
+ public Class<S> getStorableType() {
+ return mStorage.getStorableType();
+ }
+
+ public QueryExecutor<S> executor(Filter<S> filter, OrderingList<S> ordering)
+ throws FetchException
+ {
+ return new DelegatedQueryExecutor<S>(mStorage, filter, ordering);
+ }
+}
diff --git a/src/main/java/com/amazon/carbonado/qe/FullScanQueryExecutor.java b/src/main/java/com/amazon/carbonado/qe/FullScanQueryExecutor.java
index b8035f0..f0e4517 100644
--- a/src/main/java/com/amazon/carbonado/qe/FullScanQueryExecutor.java
+++ b/src/main/java/com/amazon/carbonado/qe/FullScanQueryExecutor.java
@@ -45,6 +45,9 @@ public class FullScanQueryExecutor<S extends Storable> extends AbstractQueryExec
* @throws IllegalArgumentException if support is null
*/
public FullScanQueryExecutor(Support<S> support) {
+ if (support == null && this instanceof Support) {
+ support = (Support<S>) this;
+ }
if (support == null) {
throw new IllegalArgumentException();
}
@@ -88,6 +91,9 @@ public class FullScanQueryExecutor<S extends Storable> extends AbstractQueryExec
return true;
}
+ /**
+ * Provides support for {@link FullScanQueryExecutor}.
+ */
public static interface Support<S extends Storable> {
Class<S> getStorableType();
diff --git a/src/main/java/com/amazon/carbonado/qe/IndexedQueryAnalyzer.java b/src/main/java/com/amazon/carbonado/qe/IndexedQueryAnalyzer.java
index dfa76ac..219400c 100644
--- a/src/main/java/com/amazon/carbonado/qe/IndexedQueryAnalyzer.java
+++ b/src/main/java/com/amazon/carbonado/qe/IndexedQueryAnalyzer.java
@@ -90,26 +90,35 @@ public class IndexedQueryAnalyzer<S extends Storable> {
throw new IllegalArgumentException("Filter must be bound");
}
- final Comparator<CompositeScore<?>> comparator = CompositeScore.fullComparator();
-
// First find best local index.
- CompositeScore<S> bestScore = null;
+ CompositeScore<S> bestLocalScore = null;
StorableIndex<S> bestLocalIndex = null;
+ final Comparator<CompositeScore<?>> fullComparator = CompositeScore.fullComparator();
+
Collection<StorableIndex<S>> localIndexes = indexesFor(getStorableType());
if (localIndexes != null) {
for (StorableIndex<S> index : localIndexes) {
CompositeScore<S> candidateScore =
CompositeScore.evaluate(index, filter, ordering);
- if (bestScore == null || comparator.compare(candidateScore, bestScore) < 0) {
- bestScore = candidateScore;
+ if (bestLocalScore == null
+ || fullComparator.compare(candidateScore, bestLocalScore) < 0)
+ {
+ bestLocalScore = candidateScore;
bestLocalIndex = index;
}
}
}
- // Now try to find better foreign index.
+ // Now try to find best foreign index.
+
+ if (bestLocalScore.getFilteringScore().isKeyMatch()) {
+ // Don't bother checking foreign indexes. The local one is perfect.
+ return new Result(filter, bestLocalScore, bestLocalIndex, null, null);
+ }
+
+ CompositeScore<?> bestForeignScore = null;
StorableIndex<?> bestForeignIndex = null;
ChainedProperty<S> bestForeignProperty = null;
@@ -134,20 +143,47 @@ public class IndexedQueryAnalyzer<S extends Storable> {
filter,
ordering);
- if (bestScore == null || comparator.compare(candidateScore, bestScore) < 0) {
- bestScore = candidateScore;
- bestLocalIndex = null;
+ if (bestForeignScore == null
+ || fullComparator.compare(candidateScore, bestForeignScore) < 0)
+ {
+ bestForeignScore = candidateScore;
bestForeignIndex = index;
bestForeignProperty = foreignIndexes.mProperty;
}
}
}
- return new Result(filter,
- bestScore,
- bestLocalIndex,
- bestForeignIndex,
- bestForeignProperty);
+ // Check if foreign index is better than local index.
+
+ if (bestLocalScore != null && bestForeignScore != null) {
+ // When comparing local index to foreign index, use a slightly less
+ // discriminating comparator, to prevent foreign indexes from
+ // looking too good.
+
+ Comparator<CompositeScore<?>> comp = CompositeScore.localForeignComparator();
+
+ if (comp.compare(bestForeignScore, bestLocalScore) < 0) {
+ // Foreign is better.
+ bestLocalScore = null;
+ } else {
+ // Local is better.
+ bestForeignScore = null;
+ }
+ }
+
+ CompositeScore bestScore;
+
+ if (bestLocalScore != null) {
+ bestScore = bestLocalScore;
+ bestForeignIndex = null;
+ bestForeignProperty = null;
+ } else {
+ bestScore = bestForeignScore;
+ bestLocalIndex = null;
+ }
+
+ return new Result
+ (filter, bestScore, bestLocalIndex, bestForeignIndex, bestForeignProperty);
}
/**
@@ -233,23 +269,6 @@ public class IndexedQueryAnalyzer<S extends Storable> {
return false;
}
- private <F extends Storable> boolean simpleAnalyze(Filter<F> filter)
- throws SupportException, RepositoryException
- {
- Collection<StorableIndex<F>> indexes = indexesFor(filter.getStorableType());
-
- if (indexes != null) {
- for (StorableIndex<F> index : indexes) {
- FilteringScore<F> score = FilteringScore.evaluate(index, filter);
- if (score.getRemainderCount() == 0) {
- return true;
- }
- }
- }
-
- return false;
- }
-
private <T extends Storable> Collection<StorableIndex<T>> indexesFor(Class<T> type)
throws SupportException, RepositoryException
{
@@ -363,7 +382,7 @@ public class IndexedQueryAnalyzer<S extends Storable> {
/**
* 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
+ * property corresponds to the "targetToSourceProperty" of {@link
* JoinedQueryExecutor}.
*/
public ChainedProperty<S> getForeignProperty() {
@@ -475,7 +494,12 @@ public class IndexedQueryAnalyzer<S extends Storable> {
}
}
- QueryExecutor<S> executor = baseExecutor(localAccess);
+ QueryExecutor<S> executor = baseLocalExecutor(localAccess);
+
+ if (executor == null) {
+ return JoinedQueryExecutor.build
+ (mRepoAccess, getForeignProperty(), getFilter(), getOrdering());
+ }
Filter<S> remainderFilter = getRemainderFilter();
if (remainderFilter != null) {
@@ -494,9 +518,10 @@ public class IndexedQueryAnalyzer<S extends Storable> {
return executor;
}
- private QueryExecutor<S> baseExecutor(StorageAccess<S> localAccess)
- throws SupportException, FetchException, RepositoryException
- {
+ /**
+ * Returns local executor or null if foreign executor should be used.
+ */
+ private QueryExecutor<S> baseLocalExecutor(StorageAccess<S> localAccess) {
if (!handlesAnything()) {
return new FullScanQueryExecutor<S>(localAccess);
}
@@ -504,34 +529,15 @@ public class IndexedQueryAnalyzer<S extends Storable> {
StorableIndex<S> 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);
+ CompositeScore<S> score = getCompositeScore();
+ FilteringScore<S> fScore = score.getFilteringScore();
+ if (fScore.isKeyMatch()) {
+ return new KeyQueryExecutor<S>(localAccess, localIndex, fScore);
+ }
+ return new IndexedQueryExecutor<S>(localAccess, localIndex, score);
}
- return new JoinedQueryExecutor
- (mRepoAccess.getRootRepository(), getForeignProperty(), foreignExecutor);
- }
-
- private <T extends Storable> QueryExecutor<T> indexExecutor(StorageAccess<T> access,
- StorableIndex<T> index)
- {
- CompositeScore score = getCompositeScore();
- FilteringScore fScore = score.getFilteringScore();
- if (fScore.isKeyMatch()) {
- return new KeyQueryExecutor<T>(access, index, fScore);
- }
- return new IndexedQueryExecutor<T>(access, index, score);
+ return null;
}
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 fd3ab4f..46527bf 100644
--- a/src/main/java/com/amazon/carbonado/qe/IndexedQueryExecutor.java
+++ b/src/main/java/com/amazon/carbonado/qe/IndexedQueryExecutor.java
@@ -52,6 +52,7 @@ public class IndexedQueryExecutor<S extends Storable> extends AbstractQueryExecu
private final Support<S> mSupport;
private final StorableIndex<S> mIndex;
+ private final int mHandledCount;
private final int mIdentityCount;
private final Filter<S> mIdentityFilter;
private final List<PropertyFilter<S>> mExclusiveRangeStartFilters;
@@ -70,6 +71,9 @@ public class IndexedQueryExecutor<S extends Storable> extends AbstractQueryExecu
StorableIndex<S> index,
CompositeScore<S> score)
{
+ if (support == null && this instanceof Support) {
+ support = (Support<S>) this;
+ }
if (support == null || index == null || score == null) {
throw new IllegalArgumentException();
}
@@ -78,6 +82,9 @@ public class IndexedQueryExecutor<S extends Storable> extends AbstractQueryExecu
mIndex = index;
FilteringScore<S> fScore = score.getFilteringScore();
+ OrderingScore<S> oScore = score.getOrderingScore();
+
+ mHandledCount = oScore.getHandledCount();
mIdentityCount = fScore.getIdentityCount();
mIdentityFilter = fScore.getIdentityFilter();
@@ -86,7 +93,7 @@ public class IndexedQueryExecutor<S extends Storable> extends AbstractQueryExecu
mExclusiveRangeEndFilters = fScore.getExclusiveRangeEndFilters();
mInclusiveRangeEndFilters = fScore.getInclusiveRangeEndFilters();
- mReverseOrder = score.getOrderingScore().shouldReverseOrder();
+ mReverseOrder = oScore.shouldReverseOrder();
mReverseRange = fScore.shouldReverseRange();
}
@@ -185,12 +192,20 @@ public class IndexedQueryExecutor<S extends Storable> extends AbstractQueryExecu
}
public OrderingList<S> getOrdering() {
- OrderingList<S> list = OrderingList.get(mIndex.getOrderedProperties());
- if (mIdentityCount > 0) {
- list = OrderingList.get(list.subList(mIdentityCount, list.size()));
- }
- if (mReverseOrder) {
- list = list.reverseDirections();
+ OrderingList<S> list;
+ if (mHandledCount == 0) {
+ list = OrderingList.emptyList();
+ } else {
+ list = OrderingList.get(mIndex.getOrderedProperties());
+ if (mIdentityCount > 0) {
+ list = list.subList(mIdentityCount, list.size());
+ }
+ if (mHandledCount < list.size()) {
+ list = list.subList(0, mHandledCount);
+ }
+ if (mReverseOrder) {
+ list = list.reverseDirections();
+ }
}
return list;
}
@@ -253,6 +268,9 @@ public class IndexedQueryExecutor<S extends Storable> extends AbstractQueryExecu
return true;
}
+ /**
+ * Provides support for {@link IndexedQueryExecutor}.
+ */
public static interface Support<S extends Storable> {
/**
* Perform an index scan of a subset of Storables referenced by an
diff --git a/src/main/java/com/amazon/carbonado/qe/IterableQueryExecutor.java b/src/main/java/com/amazon/carbonado/qe/IterableQueryExecutor.java
index de0864f..a1b636d 100644
--- a/src/main/java/com/amazon/carbonado/qe/IterableQueryExecutor.java
+++ b/src/main/java/com/amazon/carbonado/qe/IterableQueryExecutor.java
@@ -87,7 +87,7 @@ public class IterableQueryExecutor<S extends Storable> extends AbstractQueryExec
throws IOException
{
indent(app, indentLevel);
- app.append("iterable: ");
+ app.append("collection iterator: ");
app.append(mType.getName());
newline(app);
return true;
diff --git a/src/main/java/com/amazon/carbonado/qe/JoinedQueryExecutor.java b/src/main/java/com/amazon/carbonado/qe/JoinedQueryExecutor.java
index 11df215..62aab14 100644
--- a/src/main/java/com/amazon/carbonado/qe/JoinedQueryExecutor.java
+++ b/src/main/java/com/amazon/carbonado/qe/JoinedQueryExecutor.java
@@ -20,14 +20,29 @@ package com.amazon.carbonado.qe;
import java.io.IOException;
+import java.util.Comparator;
+import java.util.Map;
+
+import org.cojen.classfile.ClassFile;
+import org.cojen.classfile.CodeBuilder;
+import org.cojen.classfile.FieldInfo;
+import org.cojen.classfile.Label;
+import org.cojen.classfile.LocalVariable;
+import org.cojen.classfile.MethodInfo;
+import org.cojen.classfile.Modifiers;
+import org.cojen.classfile.TypeDesc;
+
+import org.cojen.util.ClassInjector;
+import org.cojen.util.SoftValuedHashMap;
+
import com.amazon.carbonado.Cursor;
import com.amazon.carbonado.FetchException;
import com.amazon.carbonado.Repository;
import com.amazon.carbonado.RepositoryException;
import com.amazon.carbonado.Storable;
-import com.amazon.carbonado.SupportException;
+import com.amazon.carbonado.Storage;
-import com.amazon.carbonado.cursor.JoinedCursorFactory;
+import com.amazon.carbonado.cursor.MultiTransformedCursor;
import com.amazon.carbonado.filter.AndFilter;
import com.amazon.carbonado.filter.ClosedFilter;
@@ -36,205 +51,571 @@ import com.amazon.carbonado.filter.FilterValues;
import com.amazon.carbonado.filter.OpenFilter;
import com.amazon.carbonado.filter.OrFilter;
import com.amazon.carbonado.filter.PropertyFilter;
-import com.amazon.carbonado.filter.Visitor;
+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.StorableInfo;
import com.amazon.carbonado.info.StorableIntrospector;
import com.amazon.carbonado.info.StorableProperty;
+import com.amazon.carbonado.util.QuickConstructorGenerator;
+
+import com.amazon.carbonado.spi.CodeBuilderUtil;
+
/**
- * QueryExecutor which wraps an executor for type <i>source</i>, follows a
- * join, and produces type <i>target</i>.
+ * QueryExecutor which joins a <i>source</i> and <i>target</i> executor,
+ * producing results of target type. The source executor is called once per
+ * fetch (outer loop), but the target executor is called once per source result
+ * (inner loop).
*
* @author Brian S O'Neill
- * @see JoinedCursorFactory
* @param <S> source type
* @param <T> target type
*/
public class JoinedQueryExecutor<S extends Storable, T extends Storable>
extends AbstractQueryExecutor<T>
{
+ /**
+ * Builds and returns a complex joined excutor against a chained property,
+ * supporting multi-way joins. Filtering and ordering may also be supplied,
+ * in order to better distribute work throughout the join.
+ *
+ * @param repoAccess used to create query executors for outer and inner loops
+ * @param targetToSourceProperty join property of <i>target</i> type which maps
+ * to instances of <i>source</i> type
+ * @param targetFilter optional filter for fetching <i>target</i> instances
+ * @param targetOrdering optional ordering to apply to <i>target</i> executor
+ * @throws IllegalArgumentException if any parameter is null or if join
+ * property is not a Storable type
+ * @throws RepositoryException from RepositoryAccess
+ */
+ public static <T extends Storable> QueryExecutor<T>
+ build(RepositoryAccess repoAccess,
+ ChainedProperty<T> targetToSourceProperty,
+ Filter<T> targetFilter,
+ OrderingList<T> targetOrdering)
+ throws RepositoryException
+ {
+ if (targetOrdering == null) {
+ targetOrdering = OrderingList.emptyList();
+ }
+
+ QueryExecutor<T> executor =
+ buildJoin(repoAccess, targetToSourceProperty, targetFilter, targetOrdering);
+
+ OrderingList<T> handledOrdering = executor.getOrdering();
+
+ // Apply sort if any remaining ordering properties.
+ int handledCount = commonOrderingCount(handledOrdering, targetOrdering);
+
+ OrderingList<T> remainderOrdering =
+ targetOrdering.subList(handledCount, targetOrdering.size());
+
+ if (remainderOrdering.size() > 0) {
+ SortedQueryExecutor.Support<T> support = repoAccess
+ .storageAccessFor(targetToSourceProperty.getPrimeProperty().getEnclosingType());
+ executor = new SortedQueryExecutor<T>
+ (support, executor, handledOrdering, remainderOrdering);
+ }
+
+ return executor;
+ }
+
+ private static <T extends Storable> JoinedQueryExecutor<?, T>
+ buildJoin(RepositoryAccess repoAccess,
+ ChainedProperty<T> targetToSourceProperty,
+ Filter<T> targetFilter,
+ OrderingList<T> targetOrdering)
+ throws RepositoryException
+ {
+ StorableProperty<T> primeTarget = targetToSourceProperty.getPrimeProperty();
+
+ Filter tailFilter;
+ if (targetFilter == null) {
+ tailFilter = null;
+ } else {
+ Filter<T>.NotJoined nj = targetFilter.notJoinedFrom(ChainedProperty.get(primeTarget));
+ tailFilter = nj.getNotJoinedFilter();
+ targetFilter = nj.getRemainderFilter();
+ }
+
+ // Determine the most ordering properties the source (outer loop
+ // executor) can provide. It may use less if its selected index does
+ // not provide the ordering for free.
+ final OrderingList outerLoopOrdering = mostOrdering(primeTarget, targetOrdering);
+
+ QueryExecutor outerLoopExecutor;
+ if (targetToSourceProperty.getChainCount() > 0) {
+ ChainedProperty tailProperty = targetToSourceProperty.tail();
+ outerLoopExecutor = buildJoin(repoAccess, tailProperty, tailFilter, outerLoopOrdering);
+ } else {
+ Class sourceType = targetToSourceProperty.getType();
+
+ if (!Storable.class.isAssignableFrom(sourceType)) {
+ throw new IllegalArgumentException
+ ("Property type is not a Storable: " + targetToSourceProperty);
+ }
+
+ StorageAccess sourceAccess = repoAccess.storageAccessFor(sourceType);
+
+ OrderingList expectedOrdering =
+ expectedOrdering(sourceAccess, tailFilter, outerLoopOrdering);
+
+ QueryExecutorFactory outerLoopExecutorFactory = sourceAccess.getQueryExecutorFactory();
+
+ outerLoopExecutor = outerLoopExecutorFactory.executor(tailFilter, expectedOrdering);
+ }
+
+ if (targetOrdering.size() > 0) {
+ // If outer loop handles some of the ordering, then it can be
+ // removed from the target ordering. This simplifies or eliminates
+ // a final sort operation.
+
+ int handledCount =
+ commonOrderingCount(outerLoopExecutor.getOrdering(), outerLoopOrdering);
+
+ targetOrdering = targetOrdering.subList(handledCount, targetOrdering.size());
+ }
+
+ Class<T> targetType = primeTarget.getEnclosingType();
+ StorageAccess<T> targetAccess = repoAccess.storageAccessFor(targetType);
+
+ QueryExecutorFactory<T> innerLoopExecutorFactory = targetAccess.getQueryExecutorFactory();
+
+ return new JoinedQueryExecutor<Storable, T>(outerLoopExecutor,
+ innerLoopExecutorFactory,
+ primeTarget,
+ targetFilter,
+ targetOrdering,
+ targetAccess);
+ }
+
+ private static final String INNER_LOOP_EX_FIELD_NAME = "innerLoopExecutor";
+ private static final String INNER_LOOP_FV_FIELD_NAME = "innerLoopFilterValues";
+ private static final String ACTIVE_SOURCE_FIELD_NAME = "active";
+
+ private static final Map<StorableProperty, Class> cJoinerCursorClassCache;
+
+ static {
+ cJoinerCursorClassCache = new SoftValuedHashMap();
+ }
+
+ private static synchronized <S, T extends Storable> Joiner.Factory<S, T>
+ getJoinerFactory(StorableProperty<T> targetToSourceProperty)
+ {
+ Class clazz = cJoinerCursorClassCache.get(targetToSourceProperty);
+
+ if (clazz == null) {
+ clazz = generateJoinerCursor(targetToSourceProperty);
+ cJoinerCursorClassCache.put(targetToSourceProperty, clazz);
+ }
+
+ return (Joiner.Factory<S, T>) QuickConstructorGenerator
+ .getInstance(clazz, Joiner.Factory.class);
+ }
+
+ private static <T extends Storable> Class<Cursor<T>>
+ generateJoinerCursor(StorableProperty<T> targetToSourceProperty)
+ {
+ final Class<?> sourceType = targetToSourceProperty.getType();
+ final Class<T> targetType = targetToSourceProperty.getEnclosingType();
+
+ String packageName;
+ {
+ String name = targetType.getName();
+ int index = name.lastIndexOf('.');
+ if (index >= 0) {
+ packageName = name.substring(0, index);
+ } else {
+ packageName = "";
+ }
+ }
+
+ ClassLoader loader = targetType.getClassLoader();
+
+ ClassInjector ci = ClassInjector.create(packageName + ".JoinedCursor", loader);
+ ClassFile cf = new ClassFile(ci.getClassName(), MultiTransformedCursor.class);
+ cf.markSynthetic();
+ cf.setSourceFile(JoinedQueryExecutor.class.getName());
+ cf.setTarget("1.5");
+
+ final TypeDesc cursorType = TypeDesc.forClass(Cursor.class);
+ final TypeDesc queryExecutorType = TypeDesc.forClass(QueryExecutor.class);
+ final TypeDesc filterValuesType = TypeDesc.forClass(FilterValues.class);
+
+ // Define fields for inner loop executor and filter values, which are
+ // passed into the constructor.
+ cf.addField(Modifiers.PRIVATE.toFinal(true), INNER_LOOP_EX_FIELD_NAME, queryExecutorType);
+ cf.addField(Modifiers.PRIVATE.toFinal(true), INNER_LOOP_FV_FIELD_NAME, filterValuesType);
+
+ // If target storable can set a reference to the joined source
+ // storable, then stash a copy of it as we go. This way, when user of
+ // target storable accesses the joined property, it costs nothing.
+ boolean canSetSourceReference = targetToSourceProperty.getWriteMethod() != null;
+
+ if (canSetSourceReference) {
+ // Field to hold active source storable.
+ cf.addField(Modifiers.PRIVATE, ACTIVE_SOURCE_FIELD_NAME,
+ TypeDesc.forClass(sourceType));
+ }
+
+ // Define constructor.
+ {
+ TypeDesc[] params = {cursorType, queryExecutorType, filterValuesType};
+
+ MethodInfo mi = cf.addConstructor(Modifiers.PUBLIC, params);
+ CodeBuilder b = new CodeBuilder(mi);
+
+ b.loadThis();
+ b.loadLocal(b.getParameter(0)); // pass source cursor to superclass
+ b.invokeSuperConstructor(new TypeDesc[] {cursorType});
+
+ b.loadThis();
+ b.loadLocal(b.getParameter(1));
+ b.storeField(INNER_LOOP_EX_FIELD_NAME, queryExecutorType);
+
+ b.loadThis();
+ b.loadLocal(b.getParameter(2));
+ b.storeField(INNER_LOOP_FV_FIELD_NAME, filterValuesType);
+
+ b.returnVoid();
+ }
+
+ // Implement the transform method.
+ {
+ MethodInfo mi = cf.addMethod(Modifiers.PROTECTED, "transform", cursorType,
+ new TypeDesc[] {TypeDesc.OBJECT});
+ mi.addException(TypeDesc.forClass(FetchException.class));
+ CodeBuilder b = new CodeBuilder(mi);
+
+ LocalVariable sourceVar = b.createLocalVariable(null, TypeDesc.forClass(sourceType));
+ b.loadLocal(b.getParameter(0));
+ b.checkCast(TypeDesc.forClass(sourceType));
+ b.storeLocal(sourceVar);
+
+ if (canSetSourceReference) {
+ b.loadThis();
+ b.loadLocal(sourceVar);
+ b.storeField(ACTIVE_SOURCE_FIELD_NAME, TypeDesc.forClass(sourceType));
+ }
+
+ // Prepare to call fetch on innerLoopExecutor.
+ b.loadThis();
+ b.loadField(INNER_LOOP_EX_FIELD_NAME, queryExecutorType);
+
+ // Fill in values for innerLoopFilterValues.
+ b.loadThis();
+ b.loadField(INNER_LOOP_FV_FIELD_NAME, filterValuesType);
+
+ int propCount = targetToSourceProperty.getJoinElementCount();
+ for (int i=0; i<propCount; i++) {
+ StorableProperty<?> external = targetToSourceProperty.getExternalJoinElement(i);
+ b.loadLocal(sourceVar);
+ b.invoke(external.getReadMethod());
+
+ TypeDesc bindType = CodeBuilderUtil.bindQueryParam(external.getType());
+ CodeBuilderUtil.convertValue(b, external.getType(), bindType.toClass());
+ b.invokeVirtual(filterValuesType, "with", filterValuesType,
+ new TypeDesc[] {bindType});
+ }
+
+ // Now fetch and return.
+ b.invokeInterface(queryExecutorType, "fetch", cursorType,
+ new TypeDesc[] {filterValuesType});
+ b.returnValue(cursorType);
+ }
+
+ if (canSetSourceReference) {
+ // Override the "next" method to set source object on target.
+ MethodInfo mi = cf.addMethod(Modifiers.PUBLIC, "next", TypeDesc.OBJECT, null);
+ mi.addException(TypeDesc.forClass(FetchException.class));
+ CodeBuilder b = new CodeBuilder(mi);
+
+ b.loadThis();
+ b.invokeSuper(TypeDesc.forClass(MultiTransformedCursor.class),
+ "next", TypeDesc.OBJECT, null);
+ b.checkCast(TypeDesc.forClass(targetType));
+ b.dup();
+
+ b.loadThis();
+ b.loadField(ACTIVE_SOURCE_FIELD_NAME, TypeDesc.forClass(sourceType));
+ b.invoke(targetToSourceProperty.getWriteMethod());
+
+ b.returnValue(TypeDesc.OBJECT);
+ }
+
+ return (Class<Cursor<T>>) ci.defineClass(cf);
+ }
+
private static <S extends Storable, T extends Storable> OrderingList<T>
transformOrdering(Class<T> targetType,
String targetToSourceProperty,
QueryExecutor<S> sourceExecutor)
{
+ OrderingList<T> targetOrdering = OrderingList.emptyList();
StorableInfo<T> targetInfo = StorableIntrospector.examine(targetType);
- OrderingList<S> sourceOrdering = sourceExecutor.getOrdering();
- int size = sourceOrdering.size();
- OrderedProperty<T>[] targetOrdering = new OrderedProperty[size];
-
- for (int i=0; i<size; i++) {
- OrderedProperty<S> sourceProp = sourceOrdering.get(i);
+ for (OrderedProperty<S> sourceProp : sourceExecutor.getOrdering()) {
String targetName = targetToSourceProperty + '.' + sourceProp.getChainedProperty();
OrderedProperty<T> targetProp = OrderedProperty
.get(ChainedProperty.parse(targetInfo, targetName), sourceProp.getDirection());
- targetOrdering[i] = targetProp;
+ targetOrdering = targetOrdering.concat(targetProp);
}
- return OrderingList.get(targetOrdering);
+ return targetOrdering;
}
- private final ChainedProperty<T> mTargetToSourceProperty;
- private final JoinedCursorFactory<S, T> mFactory;
- private final QueryExecutor<S> mSourceExecutor;
+ /**
+ * Given a list of chained ordering properties, returns the properties
+ * stripped of the matching chain prefix for the targetToSourceProperty. As
+ * the target ordering is scanned left-to-right, if any property is found
+ * which doesn't match the targetToSourceProperty, the building of the new
+ * list stops. In other words, it returns a consecutive run of matching
+ * properties.
+ */
+ private static <T extends Storable> OrderingList
+ mostOrdering(StorableProperty<T> primeTarget, OrderingList<T> targetOrdering)
+ {
+ OrderingList handledOrdering = OrderingList.emptyList();
+ for (OrderedProperty<T> targetProp : targetOrdering) {
+ ChainedProperty<T> chainedProp = targetProp.getChainedProperty();
+ if (chainedProp.getPrimeProperty().equals(primeTarget)) {
+ handledOrdering = handledOrdering
+ // I hate Java generics. Note the stupid cast. I have finally
+ // realized the core problem: the wildcard model is broken.
+ .concat(OrderedProperty
+ .get((ChainedProperty) chainedProp.tail(),
+ targetProp.getDirection()));
+ } else {
+ break;
+ }
+ }
- private final FilterValues<S> mSourceFilterValues;
- private final Filter<T> mTargetFilter;
- private final OrderingList<T> mTargetOrdering;
+ return handledOrdering;
+ }
/**
- * @param repo access to storage instances for properties
- * @param targetType type of <i>target</i> instances
- * @param targetToSourceProperty property of <i>target</i> type which maps
- * to instances of <i>source</i> type.
- * @param sourceExecutor executor for <i>source</i> instances
- * @throws IllegalArgumentException if property type is not <i>source</i>
+ * Examines the given ordering against available indexes, returning the
+ * ordering that the best index can provide for free.
*/
- public JoinedQueryExecutor(Repository repo,
- Class<T> targetType,
- String targetToSourceProperty,
- QueryExecutor<S> sourceExecutor)
- throws SupportException, FetchException, RepositoryException
+ private static <T extends Storable> OrderingList<T>
+ expectedOrdering(StorageAccess<T> access, Filter<T> filter, OrderingList<T> ordering)
{
- this(repo,
- ChainedProperty.parse(StorableIntrospector.examine(targetType),
- targetToSourceProperty),
- sourceExecutor);
+ Comparator comparator = CompositeScore.fullComparator();
+
+ CompositeScore bestScore = null;
+ for (StorableIndex<T> index : access.getAllIndexes()) {
+ CompositeScore candidateScore = CompositeScore.evaluate(index, filter, ordering);
+ if (bestScore == null || comparator.compare(candidateScore, bestScore) < 0) {
+ bestScore = candidateScore;
+ }
+ }
+
+ // Reduce source ordering to that which can be handled for
+ // free. Otherwise, a sort would be performed which is a waste of time
+ // if some source results will later be filtered out.
+ int handledCount = bestScore == null ? 0 : bestScore.getOrderingScore().getHandledCount();
+ return ordering.subList(0, handledCount);
}
/**
- * @param repo access to storage instances for properties
- * @param targetToSourceProperty property of <i>target</i> type which maps
- * to instances of <i>source</i> type.
- * @param aExecutor executor for <i>A</i> instances
- * @throws IllegalArgumentException if property type is not <i>A</i>
+ * Returns the count of exactly matching properties from the two
+ * orderings. The match must be consecutive and start at the first
+ * property.
*/
- public JoinedQueryExecutor(Repository repo,
- ChainedProperty<T> targetToSourceProperty,
- QueryExecutor<S> sourceExecutor)
- throws SupportException, FetchException, RepositoryException
+ private static <T extends Storable> int
+ commonOrderingCount(OrderingList<T> orderingA, OrderingList<T> orderingB)
{
- mTargetToSourceProperty = targetToSourceProperty;
- mFactory = new JoinedCursorFactory<S, T>
- (repo, targetToSourceProperty, sourceExecutor.getStorableType());
- mSourceExecutor = sourceExecutor;
+ int commonCount = Math.min(orderingA.size(), orderingB.size());
- Filter<S> sourceFilter = sourceExecutor.getFilter();
-
- mSourceFilterValues = sourceFilter.initialFilterValues();
- mTargetFilter = sourceFilter.accept(new FilterTransformer(), null);
+ for (int i=0; i<commonCount; i++) {
+ if (!orderingA.get(i).equals(orderingB.get(i))) {
+ return i;
+ }
+ }
- mTargetOrdering = transformOrdering
- (targetToSourceProperty.getPrimeProperty().getEnclosingType(),
- targetToSourceProperty.toString(), sourceExecutor);
+ return commonCount;
}
- public Filter<T> getFilter() {
- return mTargetFilter;
- }
+ private final Filter<T> mTargetFilter;
+ private final StorableProperty<T> mTargetToSourceProperty;
- public Cursor<T> fetch(FilterValues<T> values) throws FetchException {
- return mFactory.join(mSourceExecutor.fetch(transferValues(values)));
- }
+ private final QueryExecutor<S> mOuterLoopExecutor;
+ private final FilterValues<S> mOuterLoopFilterValues;
- public OrderingList<T> getOrdering() {
- return mTargetOrdering;
- }
+ private final QueryExecutor<T> mInnerLoopExecutor;
+ private final FilterValues<T> mInnerLoopFilterValues;
- public boolean printPlan(Appendable app, int indentLevel, FilterValues<T> values)
- throws IOException
+ private final Filter<T> mSourceFilterAsFromTarget;
+ private final Filter<T> mCombinedFilter;
+ private final OrderingList<T> mCombinedOrdering;
+
+ private final Joiner.Factory<S, T> mJoinerFactory;
+
+ /**
+ * @param outerLoopExecutor executor for <i>source</i> instances
+ * @param innerLoopExecutorFactory used to construct inner loop executor
+ * @param targetToSourceProperty join property of <i>target</i> type which maps
+ * to instances of <i>source</i> type
+ * @param targetFilter optional initial filter for fetching <i>target</i> instances
+ * @param targetOrdering optional desired ordering to apply to
+ * <i>target</i> executor
+ * @param targetAccess used with target ordering to determine actual
+ * ordering which an index provides for free
+ * @throws IllegalArgumentException if any parameter is null or if join
+ * property is not of <i>source</i> type
+ * @throws RepositoryException from innerLoopExecutorFactory
+ */
+ private JoinedQueryExecutor(QueryExecutor<S> outerLoopExecutor,
+ QueryExecutorFactory<T> innerLoopExecutorFactory,
+ StorableProperty<T> targetToSourceProperty,
+ Filter<T> targetFilter,
+ OrderingList<T> targetOrdering,
+ StorageAccess<T> targetAccess)
+ throws RepositoryException
{
- int chainCount = mTargetToSourceProperty.getChainCount();
+ if (targetToSourceProperty == null || outerLoopExecutor == null) {
+ throw new IllegalArgumentException("Null parameter");
+ }
- for (int i = -1; i < chainCount; i++) {
- indent(app, indentLevel);
- app.append("join: ");
+ Class<S> sourceType = outerLoopExecutor.getStorableType();
+ if (targetToSourceProperty.getType() != sourceType) {
+ throw new IllegalArgumentException
+ ("Property is not of type \"" + sourceType.getName() + "\": " +
+ targetToSourceProperty);
+ }
- StorableProperty<?> prop;
- if (i == -1) {
- prop = mTargetToSourceProperty.getPrimeProperty();
- } else {
- prop = mTargetToSourceProperty.getChainedProperty(i);
- }
+ if (!targetToSourceProperty.isJoin()) {
+ throw new IllegalArgumentException
+ ("Property is not a join: " + targetToSourceProperty);
+ }
- app.append(prop.getEnclosingType().getName());
- newline(app);
- indent(app, indentLevel);
- app.append("...via property: ");
- app.append(prop.getName());
- newline(app);
- indentLevel = increaseIndent(indentLevel);
+ if (targetFilter != null && !targetFilter.isBound()) {
+ throw new IllegalArgumentException("Target filter must be bound");
}
- mSourceExecutor.printPlan(app, indentLevel, transferValues(values));
- return true;
- }
+ if (!outerLoopExecutor.getFilter().isBound()) {
+ throw new IllegalArgumentException("Outer loop executor filter must be bound");
+ }
- private FilterValues<S> transferValues(FilterValues<T> values) {
- if (values == null || mSourceFilterValues == null) {
- return null;
+ if (targetFilter instanceof OpenFilter) {
+ targetFilter = null;
}
- return mSourceFilterValues.withValues(values.getSuppliedValues());
- }
- private class FilterTransformer extends Visitor<S, Filter<T>, Object> {
- private final Class<T> mTargetType;
+ mTargetFilter = targetFilter;
+ mTargetToSourceProperty = targetToSourceProperty;
+ mOuterLoopExecutor = outerLoopExecutor;
+ mOuterLoopFilterValues = outerLoopExecutor.getFilter().initialFilterValues();
+
+ Class<T> targetType = targetToSourceProperty.getEnclosingType();
- FilterTransformer() {
- mTargetType = mTargetToSourceProperty.getPrimeProperty().getEnclosingType();
+ // Prepare inner loop filter which is and'd by the join property elements.
+ Filter<T> innerLoopExecutorFilter = Filter.getOpenFilter(targetType);
+ if (targetFilter != null) {
+ innerLoopExecutorFilter = innerLoopExecutorFilter.and(targetFilter);
}
+ int count = targetToSourceProperty.getJoinElementCount();
+ for (int i=0; i<count; i++) {
+ innerLoopExecutorFilter = innerLoopExecutorFilter
+ .and(targetToSourceProperty.getInternalJoinElement(i).getName(), RelOp.EQ);
+ }
+ innerLoopExecutorFilter = innerLoopExecutorFilter.bind();
+
+ mInnerLoopFilterValues = innerLoopExecutorFilter.initialFilterValues();
- public Filter<T> visit(OrFilter<S> sourceFilter, Object param) {
- return sourceFilter.getLeftFilter().accept(this, param)
- .and(sourceFilter.getRightFilter().accept(this, param));
+ // Only perform requested ordering if it index provides it for free.
+ if (targetOrdering != null) {
+ targetOrdering =
+ expectedOrdering(targetAccess, innerLoopExecutorFilter, targetOrdering);
}
- public Filter<T> visit(AndFilter<S> sourceFilter, Object param) {
- return sourceFilter.getLeftFilter().accept(this, param)
- .or(sourceFilter.getRightFilter().accept(this, param));
+ mInnerLoopExecutor = innerLoopExecutorFactory
+ .executor(innerLoopExecutorFilter, targetOrdering);
+
+ Filter<T> filter = outerLoopExecutor.getFilter()
+ .asJoinedFrom(ChainedProperty.get(targetToSourceProperty));
+
+ mSourceFilterAsFromTarget = filter;
+
+ if (targetFilter != null) {
+ filter = filter.and(targetFilter);
}
- public Filter<T> visit(PropertyFilter<S> sourceFilter, Object param) {
- String name;
+ mCombinedFilter = filter;
- ChainedProperty<S> sourceChainedProp = sourceFilter.getChainedProperty();
- if (mTargetType == sourceChainedProp.getPrimeProperty().getEnclosingType()) {
- // If type of S is already T, (which violates generic type
- // signature) then it came from join index analysis.
- name = sourceChainedProp.toString();
- } else {
- StringBuilder nameBuilder = new StringBuilder();
- try {
- mTargetToSourceProperty.appendTo(nameBuilder);
- nameBuilder.append('.');
- sourceChainedProp.appendTo(nameBuilder);
- } catch (IOException e) {
- // Not gonna happen
- }
- name = nameBuilder.toString();
- }
+ // Prepare combined ordering.
+ OrderingList<T> ordering = transformOrdering
+ (targetType, targetToSourceProperty.getName(), outerLoopExecutor);
- Filter<T> targetFilter = Filter.getOpenFilter(mTargetType);
- if (sourceFilter.isConstant()) {
- targetFilter = targetFilter
- .and(name, sourceFilter.getOperator(), sourceFilter.constant());
- } else {
- targetFilter = targetFilter.and(name, sourceFilter.getOperator());
- }
+ if (targetOrdering != null) {
+ ordering = ordering.concat(targetOrdering);
+ }
+
+ mCombinedOrdering = ordering;
+
+ mJoinerFactory = getJoinerFactory(targetToSourceProperty);
+ }
+
+ public Cursor<T> fetch(FilterValues<T> values) throws FetchException {
+ FilterValues<T> innerLoopFilterValues = mInnerLoopFilterValues;
- return targetFilter;
+ if (mTargetFilter != null) {
+ // Prepare this before opening source cursor, in case an exception is thrown.
+ innerLoopFilterValues = innerLoopFilterValues
+ .withValues(values.getValuesFor(mTargetFilter));
}
- public Filter<T> visit(OpenFilter<S> sourceFilter, Object param) {
- return Filter.getOpenFilter(mTargetType);
+ Cursor<S> outerLoopCursor = mOuterLoopExecutor.fetch(transferValues(values));
+
+ return mJoinerFactory.newJoinedCursor
+ (outerLoopCursor, mInnerLoopExecutor, innerLoopFilterValues);
+ }
+
+ public Filter<T> getFilter() {
+ return mCombinedFilter;
+ }
+
+ public OrderingList<T> getOrdering() {
+ return mCombinedOrdering;
+ }
+
+ public boolean printPlan(Appendable app, int indentLevel, FilterValues<T> values)
+ throws IOException
+ {
+ indent(app, indentLevel);
+ app.append("join: ");
+ app.append(mTargetToSourceProperty.getEnclosingType().getName());
+ newline(app);
+ indent(app, indentLevel);
+ app.append("...inner loop: ");
+ app.append(mTargetToSourceProperty.getName());
+ newline(app);
+ mInnerLoopExecutor.printPlan(app, increaseIndent(indentLevel), values);
+ indent(app, indentLevel);
+ app.append("...outer loop");
+ newline(app);
+ mOuterLoopExecutor.printPlan(app, increaseIndent(indentLevel), transferValues(values));
+ return true;
+ }
+
+ private FilterValues<S> transferValues(FilterValues<T> values) {
+ if (values == null) {
+ return null;
}
+ return mOuterLoopFilterValues.withValues(values.getValuesFor(mSourceFilterAsFromTarget));
+ }
- public Filter<T> visit(ClosedFilter<S> sourceFilter, Object param) {
- return Filter.getClosedFilter(mTargetType);
+ private static interface Joiner {
+ /**
+ * Needs to be public for {@link QuickConstructorGenerator}, but hide
+ * it inside a private interface.
+ */
+ public static interface Factory<S, T extends Storable> {
+ Cursor<T> newJoinedCursor(Cursor<S> outerLoopCursor,
+ QueryExecutor<T> innerLoopExecutor,
+ FilterValues<T> innerLoopFilterValues);
}
}
}
diff --git a/src/main/java/com/amazon/carbonado/qe/KeyQueryExecutor.java b/src/main/java/com/amazon/carbonado/qe/KeyQueryExecutor.java
index d8436c0..fcf728d 100644
--- a/src/main/java/com/amazon/carbonado/qe/KeyQueryExecutor.java
+++ b/src/main/java/com/amazon/carbonado/qe/KeyQueryExecutor.java
@@ -51,6 +51,9 @@ public class KeyQueryExecutor<S extends Storable> extends AbstractQueryExecutor<
* not unique or if score is not a key match
*/
public KeyQueryExecutor(Support<S> support, StorableIndex<S> index, FilteringScore<S> score) {
+ if (support == null && this instanceof Support) {
+ support = (Support<S>) this;
+ }
if (support == null || index == null || score == null) {
throw new IllegalArgumentException();
}
@@ -101,6 +104,9 @@ public class KeyQueryExecutor<S extends Storable> extends AbstractQueryExecutor<
return true;
}
+ /**
+ * Provides support for {@link KeyQueryExecutor}.
+ */
public static interface Support<S extends Storable> {
/**
* Select at most one Storable referenced by an index. The identity
diff --git a/src/main/java/com/amazon/carbonado/qe/OrderingList.java b/src/main/java/com/amazon/carbonado/qe/OrderingList.java
index f6366e5..d100cc9 100644
--- a/src/main/java/com/amazon/carbonado/qe/OrderingList.java
+++ b/src/main/java/com/amazon/carbonado/qe/OrderingList.java
@@ -246,6 +246,23 @@ public class OrderingList<S extends Storable> extends AbstractList<OrderedProper
return newList;
}
+ @Override
+ public OrderingList<S> subList(int fromIndex, int toIndex) {
+ // Check for optimization opportunity.
+ if (fromIndex == 0 && toIndex >= 0 && toIndex <= mSize) {
+ if (toIndex == 0) {
+ return emptyList();
+ }
+ OrderingList<S> list = this;
+ while (toIndex < list.mSize) {
+ list = list.mParent;
+ }
+ return list;
+ }
+
+ return get(super.subList(fromIndex, toIndex));
+ }
+
/**
* This method is not public because the array is not a clone.
*/
diff --git a/src/main/java/com/amazon/carbonado/qe/QueryEngine.java b/src/main/java/com/amazon/carbonado/qe/QueryEngine.java
index 1362045..59c9872 100644
--- a/src/main/java/com/amazon/carbonado/qe/QueryEngine.java
+++ b/src/main/java/com/amazon/carbonado/qe/QueryEngine.java
@@ -19,9 +19,11 @@
package com.amazon.carbonado.qe;
import com.amazon.carbonado.IsolationLevel;
+import com.amazon.carbonado.RepositoryException;
import com.amazon.carbonado.Storable;
import com.amazon.carbonado.Transaction;
+import com.amazon.carbonado.filter.Filter;
import com.amazon.carbonado.filter.FilterValues;
/**
@@ -29,7 +31,9 @@ import com.amazon.carbonado.filter.FilterValues;
*
* @author Brian S O'Neill
*/
-public class QueryEngine<S extends Storable> extends StandardQueryFactory<S> {
+public class QueryEngine<S extends Storable> extends StandardQueryFactory<S>
+ implements QueryExecutorFactory<S>
+{
final RepositoryAccess mRepoAccess;
final QueryExecutorFactory<S> mExecutorFactory;
@@ -39,6 +43,12 @@ public class QueryEngine<S extends Storable> extends StandardQueryFactory<S> {
mExecutorFactory = new QueryExecutorCache<S>(new UnionQueryAnalyzer<S>(type, access));
}
+ public QueryExecutor<S> executor(Filter<S> filter, OrderingList<S> ordering)
+ throws RepositoryException
+ {
+ return mExecutorFactory.executor(filter, ordering);
+ }
+
protected StandardQuery<S> createQuery(FilterValues<S> values, OrderingList<S> ordering) {
return new Query(values, ordering);
}
diff --git a/src/main/java/com/amazon/carbonado/qe/QueryExecutorFactory.java b/src/main/java/com/amazon/carbonado/qe/QueryExecutorFactory.java
index 1b1e680..5568f01 100644
--- a/src/main/java/com/amazon/carbonado/qe/QueryExecutorFactory.java
+++ b/src/main/java/com/amazon/carbonado/qe/QueryExecutorFactory.java
@@ -23,8 +23,6 @@ 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.
*
diff --git a/src/main/java/com/amazon/carbonado/qe/SortedQueryExecutor.java b/src/main/java/com/amazon/carbonado/qe/SortedQueryExecutor.java
index cae03d8..38a8e36 100644
--- a/src/main/java/com/amazon/carbonado/qe/SortedQueryExecutor.java
+++ b/src/main/java/com/amazon/carbonado/qe/SortedQueryExecutor.java
@@ -64,9 +64,13 @@ public class SortedQueryExecutor<S extends Storable> extends AbstractQueryExecut
OrderingList<S> handledOrdering,
OrderingList<S> remainderOrdering)
{
+ if (support == null && this instanceof Support) {
+ support = (Support<S>) this;
+ }
if (executor == null) {
throw new IllegalArgumentException();
}
+
mSupport = support;
mExecutor = executor;
@@ -128,6 +132,9 @@ public class SortedQueryExecutor<S extends Storable> extends AbstractQueryExecut
return true;
}
+ /**
+ * Provides support for {@link SortedQueryExecutor}.
+ */
public static interface Support<S extends Storable> {
/**
* Implementation must return an empty buffer for sorting.
diff --git a/src/main/java/com/amazon/carbonado/qe/StorageAccess.java b/src/main/java/com/amazon/carbonado/qe/StorageAccess.java
index 2ab2313..643d61c 100644
--- a/src/main/java/com/amazon/carbonado/qe/StorageAccess.java
+++ b/src/main/java/com/amazon/carbonado/qe/StorageAccess.java
@@ -47,6 +47,11 @@ public interface StorageAccess<S extends Storable>
Class<S> getStorableType();
/**
+ * Returns a QueryExecutorFactory instance for storage.
+ */
+ QueryExecutorFactory<S> getQueryExecutorFactory();
+
+ /**
* Returns all the available indexes.
*/
Collection<StorableIndex<S>> getAllIndexes();