summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--TODO.txt1
-rw-r--r--src/main/java/com/amazon/carbonado/cursor/JoinedCursorFactory.java467
-rw-r--r--src/main/java/com/amazon/carbonado/cursor/MultiTransformedCursor.java1
-rw-r--r--src/main/java/com/amazon/carbonado/cursor/TransformedCursor.java1
-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
-rw-r--r--src/main/java/com/amazon/carbonado/repo/indexed/IndexedStorage.java5
-rw-r--r--src/main/java/com/amazon/carbonado/spi/CodeBuilderUtil.java6
-rw-r--r--src/test/java/com/amazon/carbonado/qe/TestIndexedQueryAnalyzer.java179
-rw-r--r--src/test/java/com/amazon/carbonado/qe/TestIndexedQueryExecutor.java119
-rw-r--r--src/test/java/com/amazon/carbonado/qe/TestJoinedQueryExecutor.java129
-rw-r--r--src/test/java/com/amazon/carbonado/qe/TestOrderingList.java15
-rw-r--r--src/test/java/com/amazon/carbonado/qe/TestUnionQueryAnalyzer.java12
24 files changed, 1107 insertions, 779 deletions
diff --git a/TODO.txt b/TODO.txt
index 1fe3c2a..972f8e4 100644
--- a/TODO.txt
+++ b/TODO.txt
@@ -37,7 +37,6 @@ Carbonado TODO
== Performance ==
-* Join optimization
* Support partial lookup:
// For partial lookup, identify which properties you want.
// Selections are overridden when calling reject.
diff --git a/src/main/java/com/amazon/carbonado/cursor/JoinedCursorFactory.java b/src/main/java/com/amazon/carbonado/cursor/JoinedCursorFactory.java
deleted file mode 100644
index b689c7e..0000000
--- a/src/main/java/com/amazon/carbonado/cursor/JoinedCursorFactory.java
+++ /dev/null
@@ -1,467 +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.cursor;
-
-import java.lang.reflect.Field;
-
-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.KeyFactory;
-import org.cojen.util.SoftValuedHashMap;
-import org.cojen.util.WeakIdentityMap;
-
-import com.amazon.carbonado.Cursor;
-import com.amazon.carbonado.FetchException;
-import com.amazon.carbonado.Query;
-import com.amazon.carbonado.Repository;
-import com.amazon.carbonado.RepositoryException;
-import com.amazon.carbonado.Storable;
-import com.amazon.carbonado.Storage;
-import com.amazon.carbonado.SupportException;
-
-import com.amazon.carbonado.info.ChainedProperty;
-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;
-import static com.amazon.carbonado.spi.CommonMethodNames.*;
-
-/**
- * Given two joined types <i>source</i> and <i>target</i>, this factory
- * converts a cursor over type <i>source</i> into a cursor over type
- * <i>target</i>. For example, consider two storable types, Employer and
- * Person. A query filter for persons with a given employer might look like:
- * {@code "employer.name = ?" } The join can be manually implemented by
- * querying for employers (the source) and then using this factory to produce
- * persons (the target):
- *
- * <pre>
- * JoinedCursorFactory&lt;Employer, Person&gt; factory = new JoinedCursorFactory&lt;Employer, Person&gt;
- * (repo, Person.class, "employer", Employer.class);
- *
- * Cursor&lt;Employer&gt; employerCursor = repo.storageFor(Employer.class)
- * .query("name = ?").with(...).fetch();
- *
- * Cursor&lt;Person&gt; personCursor = factory.join(employerCursor);
- * </pre>
- *
- * Chained properties are supported as well. A query filter for persons with an
- * employer in a given state might look like: {@code "employer.address.state = ?" }
- * The join can be manually implemented as:
- *
- * <pre>
- * JoinedCursorFactory&lt;Address, Person&gt; factory = new JoinedCursorFactory&lt;Address, Person&gt;
- * (repo, Person.class, "employer.address", Address.class);
- *
- * Cursor&lt;Address&gt; addressCursor = repo.storageFor(Address.class)
- * .query("state = ?").with(...).fetch();
- *
- * Cursor&lt;Person&gt; personCursor = factory.join(addressCursor);
- * </pre>
- *
- * @author Brian S O'Neill
- * @see TransformedCursor
- * @see MultiTransformedCursor
- * @param <S> source type, can be anything
- * @param <T> target type, must be a Storable
- */
-public class JoinedCursorFactory<S, T extends Storable> {
- private static final String STORAGE_FIELD_NAME = "storage";
- private static final String QUERY_FIELD_NAME = "query";
- private static final String QUERY_FILTER_FIELD_NAME = "queryFilter";
- private static final String ACTIVE_SOURCE_FIELD_NAME = "active";
-
- private static final Map<Object, Class> cJoinerCursorClassCache;
-
- static {
- cJoinerCursorClassCache = new SoftValuedHashMap();
- }
-
- private static synchronized <T extends Storable> Joiner<?, T>
- newBasicJoiner(StorableProperty<T> targetToSourceProperty, Storage<T> targetStorage)
- throws FetchException
- {
- Class<?> sourceType = targetToSourceProperty.getType();
-
- final Object key = KeyFactory.createKey
- (new Object[] {sourceType,
- targetToSourceProperty.getEnclosingType(),
- targetToSourceProperty.getName()});
-
- Class clazz = cJoinerCursorClassCache.get(key);
-
- if (clazz == null) {
- clazz = generateBasicJoinerCursor(sourceType, targetToSourceProperty);
- cJoinerCursorClassCache.put(key, clazz);
- }
-
- // Transforming cursor class may need a Query to operate on.
- Query<T> targetQuery = null;
- try {
- String filter = (String) clazz.getField(QUERY_FILTER_FIELD_NAME).get(null);
- targetQuery = targetStorage.query(filter);
- } catch (NoSuchFieldException e) {
- } catch (IllegalAccessException e) {
- }
-
- BasicJoiner.Factory<?, T> factory = (BasicJoiner.Factory<?, T>) QuickConstructorGenerator
- .getInstance(clazz, BasicJoiner.Factory.class);
-
- return new BasicJoiner(factory, targetStorage, targetQuery);
- }
-
- private static <T extends Storable> Class<Cursor<T>>
- generateBasicJoinerCursor(Class<?> sourceType, StorableProperty<T> targetToSourceProperty)
- {
- final int propCount = targetToSourceProperty.getJoinElementCount();
-
- // Determine if join is one-to-one, in which case slightly more optimal
- // code can be generated.
- boolean isOneToOne = true;
- for (int i=0; i<propCount; i++) {
- if (!targetToSourceProperty.getInternalJoinElement(i).isPrimaryKeyMember()) {
- isOneToOne = false;
- break;
- }
- if (!targetToSourceProperty.getExternalJoinElement(i).isPrimaryKeyMember()) {
- isOneToOne = false;
- break;
- }
- }
-
- 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);
- Class superclass = isOneToOne ? TransformedCursor.class : MultiTransformedCursor.class;
- ClassFile cf = new ClassFile(ci.getClassName(), superclass);
- cf.markSynthetic();
- cf.setSourceFile(JoinedCursorFactory.class.getName());
- cf.setTarget("1.5");
-
- final TypeDesc queryType = TypeDesc.forClass(Query.class);
- final TypeDesc cursorType = TypeDesc.forClass(Cursor.class);
- final TypeDesc storageType = TypeDesc.forClass(Storage.class);
- final TypeDesc storableType = TypeDesc.forClass(Storable.class);
-
- if (isOneToOne) {
- cf.addField(Modifiers.PRIVATE.toFinal(true), STORAGE_FIELD_NAME, storageType);
- } else {
- // Field to hold query which fetches type T.
- cf.addField(Modifiers.PRIVATE.toFinal(true), QUERY_FIELD_NAME, queryType);
- }
-
- boolean canSetSourceReference = targetToSourceProperty.getWriteMethod() != null;
-
- if (canSetSourceReference && !isOneToOne) {
- // Field to hold active S storable.
- cf.addField(Modifiers.PRIVATE, ACTIVE_SOURCE_FIELD_NAME,
- TypeDesc.forClass(sourceType));
- }
-
- // Constructor accepts a Storage and Query, but Storage is only used
- // for one-to-one, and Query is only used for one-to-many.
- {
- MethodInfo mi = cf.addConstructor(Modifiers.PUBLIC,
- new TypeDesc[] {cursorType, storageType, queryType});
- CodeBuilder b = new CodeBuilder(mi);
-
- b.loadThis();
- b.loadLocal(b.getParameter(0)); // pass S cursor to superclass
- b.invokeSuperConstructor(new TypeDesc[] {cursorType});
-
- if (isOneToOne) {
- b.loadThis();
- b.loadLocal(b.getParameter(1)); // push T storage to stack
- b.storeField(STORAGE_FIELD_NAME, storageType);
- } else {
- b.loadThis();
- b.loadLocal(b.getParameter(2)); // push T query to stack
- b.storeField(QUERY_FIELD_NAME, queryType);
- }
-
- b.returnVoid();
- }
-
- // For one-to-many, a query is needed. Save the query filter in a
- // public static field to be grabbed later.
- if (!isOneToOne) {
- StringBuilder queryBuilder = new StringBuilder();
-
- for (int i=0; i<propCount; i++) {
- if (i > 0) {
- queryBuilder.append(" & ");
- }
- queryBuilder.append(targetToSourceProperty.getInternalJoinElement(i).getName());
- queryBuilder.append(" = ?");
- }
-
- FieldInfo fi = cf.addField(Modifiers.PUBLIC.toStatic(true).toFinal(true),
- QUERY_FILTER_FIELD_NAME, TypeDesc.STRING);
- fi.setConstantValue(queryBuilder.toString());
- }
-
- // Implement the transform method.
- if (isOneToOne) {
- MethodInfo mi = cf.addMethod(Modifiers.PROTECTED, "transform", TypeDesc.OBJECT,
- new TypeDesc[] {TypeDesc.OBJECT});
- mi.addException(TypeDesc.forClass(FetchException.class));
- CodeBuilder b = new CodeBuilder(mi);
-
- LocalVariable sourceVar = b.createLocalVariable(null, storableType);
- b.loadLocal(b.getParameter(0));
- b.checkCast(TypeDesc.forClass(sourceType));
- b.storeLocal(sourceVar);
-
- // Prepare T storable.
- b.loadThis();
- b.loadField(STORAGE_FIELD_NAME, storageType);
- b.invokeInterface(storageType, PREPARE_METHOD_NAME, storableType, null);
- LocalVariable targetVar = b.createLocalVariable(null, storableType);
- b.checkCast(TypeDesc.forClass(targetType));
- b.storeLocal(targetVar);
-
- // Copy pk property values from S to T.
- for (int i=0; i<propCount; i++) {
- StorableProperty<T> internal = targetToSourceProperty.getInternalJoinElement(i);
- StorableProperty<?> external = targetToSourceProperty.getExternalJoinElement(i);
-
- b.loadLocal(targetVar);
- b.loadLocal(sourceVar);
- b.invoke(external.getReadMethod());
- b.invoke(internal.getWriteMethod());
- }
-
- // tryLoad target.
- b.loadLocal(targetVar);
- b.invokeInterface(storableType, TRY_LOAD_METHOD_NAME, TypeDesc.BOOLEAN, null);
- Label wasLoaded = b.createLabel();
- b.ifZeroComparisonBranch(wasLoaded, "!=");
-
- b.loadNull();
- b.returnValue(storableType);
-
- wasLoaded.setLocation();
-
- if (canSetSourceReference) {
- b.loadLocal(targetVar);
- b.loadLocal(sourceVar);
- b.invoke(targetToSourceProperty.getWriteMethod());
- }
-
- b.loadLocal(targetVar);
- b.returnValue(storableType);
- } else {
- 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, storableType);
- 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));
- }
-
- // Populate query parameters.
- b.loadThis();
- b.loadField(QUERY_FIELD_NAME, queryType);
-
- 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.invokeInterface(queryType, WITH_METHOD_NAME, queryType,
- new TypeDesc[] {bindType});
- }
-
- // Now fetch and return.
- b.invokeInterface(queryType, FETCH_METHOD_NAME, cursorType, null);
- b.returnValue(cursorType);
- }
-
- if (canSetSourceReference && !isOneToOne) {
- // Override the "next" method to set S object on T.
- 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(storableType);
- }
-
- return (Class<Cursor<T>>) ci.defineClass(cf);
- }
-
- private final Joiner<S, T> mJoiner;
-
- /**
- * @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 sourceType type of <i>source</i> instances
- * @throws IllegalArgumentException if property type is not <i>source</i>
- */
- public JoinedCursorFactory(Repository repo,
- Class<T> targetType,
- String targetToSourceProperty,
- Class<S> sourceType)
- throws SupportException, FetchException, RepositoryException
- {
- this(repo,
- ChainedProperty.parse(StorableIntrospector.examine(targetType),
- targetToSourceProperty),
- sourceType);
- }
-
- /**
- * @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 sourceType type of <i>source</i> instances
- * @throws IllegalArgumentException if property type is not <i>source</i>
- */
- public JoinedCursorFactory(Repository repo,
- ChainedProperty<T> targetToSourceProperty,
- Class<S> sourceType)
- throws SupportException, FetchException, RepositoryException
- {
- if (targetToSourceProperty.getType() != sourceType) {
- throw new IllegalArgumentException
- ("Property is not of type \"" + sourceType.getName() + "\": " +
- targetToSourceProperty);
- }
-
- StorableProperty<T> primeTarget = targetToSourceProperty.getPrimeProperty();
- Storage<T> primeTargetStorage = repo.storageFor(primeTarget.getEnclosingType());
-
- Joiner joiner = newBasicJoiner(primeTarget, primeTargetStorage);
-
- int chainCount = targetToSourceProperty.getChainCount();
- for (int i=0; i<chainCount; i++) {
- StorableProperty prop = targetToSourceProperty.getChainedProperty(i);
- Storage storage = repo.storageFor(prop.getEnclosingType());
-
- joiner = new MultiJoiner(newBasicJoiner(prop, storage), joiner);
- }
-
- mJoiner = (Joiner<S, T>) joiner;
- }
-
- /**
- * Given a cursor over type <i>source</i>, returns a new cursor over joined
- * property of type <i>target</i>.
- */
- public Cursor<T> join(Cursor<S> cursor) {
- return mJoiner.join(cursor);
- }
-
- private static interface Joiner<S, T extends Storable> {
- Cursor<T> join(Cursor<S> cursor);
- }
-
- /**
- * Support for joins without an intermediate hop.
- */
- private static class BasicJoiner<S, T extends Storable> implements Joiner<S, T> {
- private final Factory<S, T> mJoinerFactory;
- private final Storage<T> mTargetStorage;
- private final Query<T> mTargetQuery;
-
- BasicJoiner(Factory<S, T> factory, Storage<T> targetStorage, Query<T> targetQuery) {
- mJoinerFactory = factory;
- mTargetStorage = targetStorage;
- mTargetQuery = targetQuery;
- }
-
- public Cursor<T> join(Cursor<S> cursor) {
- return mJoinerFactory.newJoinedCursor(cursor, mTargetStorage, mTargetQuery);
- }
-
- /**
- * Needs to be public for {@link QuickConstructorGenerator}.
- */
- public static interface Factory<S, T extends Storable> {
- Cursor<T> newJoinedCursor(Cursor<S> cursor,
- Storage<T> targetStorage, Query<T> targetQuery);
- }
- }
-
- /**
- * Support for joins with an intermediate hop -- multi-way joins.
- */
- private static class MultiJoiner<S, X extends Storable, T extends Storable>
- implements Joiner<S, T>
- {
- private final Joiner<S, X> mSourceToMid;
- private final Joiner<X, T> mMidToTarget;
-
- MultiJoiner(Joiner<S, X> sourceToMidJoiner, Joiner<X, T> midToTargetJoiner) {
- mSourceToMid = sourceToMidJoiner;
- mMidToTarget = midToTargetJoiner;
- }
-
- public Cursor<T> join(Cursor<S> cursor) {
- return mMidToTarget.join(mSourceToMid.join(cursor));
- }
- }
-}
diff --git a/src/main/java/com/amazon/carbonado/cursor/MultiTransformedCursor.java b/src/main/java/com/amazon/carbonado/cursor/MultiTransformedCursor.java
index 3f06421..7606eab 100644
--- a/src/main/java/com/amazon/carbonado/cursor/MultiTransformedCursor.java
+++ b/src/main/java/com/amazon/carbonado/cursor/MultiTransformedCursor.java
@@ -31,7 +31,6 @@ import com.amazon.carbonado.FetchInterruptedException;
* joins.
*
* @author Brian S O'Neill
- * @see JoinedCursorFactory
* @param <S> source type, can be anything
* @param <T> target type, can be anything
*/
diff --git a/src/main/java/com/amazon/carbonado/cursor/TransformedCursor.java b/src/main/java/com/amazon/carbonado/cursor/TransformedCursor.java
index 428e74e..70161cc 100644
--- a/src/main/java/com/amazon/carbonado/cursor/TransformedCursor.java
+++ b/src/main/java/com/amazon/carbonado/cursor/TransformedCursor.java
@@ -30,7 +30,6 @@ import com.amazon.carbonado.FetchInterruptedException;
* one-to-one joins. Use {@link MultiTransformedCursor} for one-to-many joins.
*
* @author Brian S O'Neill
- * @see JoinedCursorFactory
* @param <S> source type, can be anything
* @param <T> target type, can be anything
*/
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();
diff --git a/src/main/java/com/amazon/carbonado/repo/indexed/IndexedStorage.java b/src/main/java/com/amazon/carbonado/repo/indexed/IndexedStorage.java
index 2901ea0..fde1952 100644
--- a/src/main/java/com/amazon/carbonado/repo/indexed/IndexedStorage.java
+++ b/src/main/java/com/amazon/carbonado/repo/indexed/IndexedStorage.java
@@ -59,6 +59,7 @@ import com.amazon.carbonado.cursor.SortBuffer;
import com.amazon.carbonado.qe.BoundaryType;
import com.amazon.carbonado.qe.QueryEngine;
+import com.amazon.carbonado.qe.QueryExecutorFactory;
import com.amazon.carbonado.qe.StorageAccess;
import com.amazon.carbonado.spi.RepairExecutor;
@@ -266,6 +267,10 @@ class IndexedStorage<S extends Storable> implements Storage<S>, StorageAccess<S>
return accessors.toArray(new IndexEntryAccessor[accessors.size()]);
}
+ public QueryExecutorFactory<S> getQueryExecutorFactory() {
+ return mQueryEngine;
+ }
+
public Collection<StorableIndex<S>> getAllIndexes() {
return mIndexSet;
}
diff --git a/src/main/java/com/amazon/carbonado/spi/CodeBuilderUtil.java b/src/main/java/com/amazon/carbonado/spi/CodeBuilderUtil.java
index 4e2e777..e3b24bd 100644
--- a/src/main/java/com/amazon/carbonado/spi/CodeBuilderUtil.java
+++ b/src/main/java/com/amazon/carbonado/spi/CodeBuilderUtil.java
@@ -385,6 +385,8 @@ public class CodeBuilderUtil {
* Determines which overloaded "with" method on Query should be bound to.
*/
public static TypeDesc bindQueryParam(Class clazz) {
+ // This method is a bit vestigial. Once upon a time the Query class did
+ // not support all primitive types.
if (clazz.isPrimitive()) {
TypeDesc type = TypeDesc.forClass(clazz);
switch (type.getTypeCode()) {
@@ -392,6 +394,10 @@ public class CodeBuilderUtil {
case TypeDesc.LONG_CODE:
case TypeDesc.FLOAT_CODE:
case TypeDesc.DOUBLE_CODE:
+ case TypeDesc.BOOLEAN_CODE:
+ case TypeDesc.CHAR_CODE:
+ case TypeDesc.BYTE_CODE:
+ case TypeDesc.SHORT_CODE:
return type;
}
}
diff --git a/src/test/java/com/amazon/carbonado/qe/TestIndexedQueryAnalyzer.java b/src/test/java/com/amazon/carbonado/qe/TestIndexedQueryAnalyzer.java
index ef1711f..6b1919a 100644
--- a/src/test/java/com/amazon/carbonado/qe/TestIndexedQueryAnalyzer.java
+++ b/src/test/java/com/amazon/carbonado/qe/TestIndexedQueryAnalyzer.java
@@ -27,12 +27,17 @@ import junit.framework.TestCase;
import junit.framework.TestSuite;
import com.amazon.carbonado.Cursor;
+import com.amazon.carbonado.FetchException;
+import com.amazon.carbonado.Query;
import com.amazon.carbonado.Repository;
+import com.amazon.carbonado.RepositoryException;
import com.amazon.carbonado.Storable;
import com.amazon.carbonado.Storage;
+import com.amazon.carbonado.cursor.ArraySortBuffer;
import com.amazon.carbonado.cursor.SortBuffer;
+import com.amazon.carbonado.info.OrderedProperty;
import com.amazon.carbonado.info.StorableIndex;
import com.amazon.carbonado.filter.Filter;
@@ -47,8 +52,6 @@ import com.amazon.carbonado.stored.Shipment;
import com.amazon.carbonado.stored.Shipper;
import com.amazon.carbonado.stored.StorableTestBasic;
-import static com.amazon.carbonado.qe.TestIndexedQueryExecutor.Mock;
-
/**
*
*
@@ -118,7 +121,8 @@ public class TestIndexedQueryAnalyzer extends TestCase {
assertTrue(result.handlesAnything());
assertEquals(filter, result.getCompositeScore().getFilteringScore().getIdentityFilter());
- assertEquals(makeIndex(Shipment.class, "orderID"), result.getLocalIndex());
+ assertEquals(makeIndex(Shipment.class, "orderID", "shipmentNotes"),
+ result.getLocalIndex());
assertEquals(null, result.getForeignIndex());
assertEquals(null, result.getForeignProperty());
@@ -133,7 +137,8 @@ public class TestIndexedQueryAnalyzer extends TestCase {
result.getCompositeScore().getFilteringScore().getRangeStartFilters();
assertEquals(1, rangeFilters.size());
assertEquals(filter, rangeFilters.get(0));
- assertEquals(makeIndex(Shipment.class, "orderID"), result.getLocalIndex());
+ assertEquals(makeIndex(Shipment.class, "orderID", "shipmentNotes"),
+ result.getLocalIndex());
assertEquals(null, result.getForeignIndex());
assertEquals(null, result.getForeignProperty());
}
@@ -183,7 +188,8 @@ public class TestIndexedQueryAnalyzer extends TestCase {
assertTrue(result.getCompositeScore().getFilteringScore().hasRangeStart());
assertEquals(Filter.filterFor(Shipment.class, "order.orderTotal >= ?").bind(),
result.getCompositeScore().getFilteringScore().getRemainderFilter());
- assertEquals(makeIndex(Shipment.class, "orderID"), result.getLocalIndex());
+ assertEquals(makeIndex(Shipment.class, "orderID", "shipmentNotes"),
+ result.getLocalIndex());
assertEquals(null, result.getForeignIndex());
assertEquals(null, result.getForeignProperty());
}
@@ -198,20 +204,19 @@ public class TestIndexedQueryAnalyzer extends TestCase {
assertTrue(result.handlesAnything());
assertEquals(filter, result.getCompositeScore().getFilteringScore().getIdentityFilter());
assertEquals(null, result.getLocalIndex());
- assertEquals(makeIndex(Address.class, "addressState"), result.getForeignIndex());
+ assertEquals(makeIndex(Address.class, "addressState", "-addressCity"),
+ result.getForeignIndex());
assertEquals("order.address", result.getForeignProperty().toString());
}
public void testChainedJoinExecutor() throws Exception {
- Repository repo = new ToyRepository();
-
IndexedQueryAnalyzer<Shipment> iqa =
new IndexedQueryAnalyzer<Shipment>(Shipment.class, RepoAccess.INSTANCE);
Filter<Shipment> filter = Filter.filterFor
(Shipment.class, "order.address.addressState = ? & order.address.addressZip = ?");
FilterValues<Shipment> values = filter.initialFilterValues();
filter = values.getFilter();
- IndexedQueryAnalyzer.Result result = iqa.analyze(filter, null);
+ IndexedQueryAnalyzer<Shipment>.Result result = iqa.analyze(filter, null);
assertTrue(result.handlesAnything());
assertEquals(Filter.filterFor(Shipment.class, "order.address.addressState = ?").bind(),
@@ -219,30 +224,117 @@ public class TestIndexedQueryAnalyzer extends TestCase {
assertEquals(Filter.filterFor(Shipment.class, "order.address.addressZip = ?").bind(),
result.getCompositeScore().getFilteringScore().getRemainderFilter());
assertEquals(null, result.getLocalIndex());
- assertEquals(makeIndex(Address.class, "addressState"), result.getForeignIndex());
+ assertEquals(makeIndex(Address.class, "addressState", "-addressCity"),
+ result.getForeignIndex());
assertEquals("order.address", result.getForeignProperty().toString());
- Mock ixExec = new Mock(result.getForeignIndex(), result.getCompositeScore());
+ QueryExecutor<Shipment> joinExec = JoinedQueryExecutor.build
+ (RepoAccess.INSTANCE,
+ result.getForeignProperty(), result.getFilter(), result.getOrdering());
+
+ FilterValues<Shipment> fv = values.with("WA").with("12345");
- QueryExecutor joinExec = new JoinedQueryExecutor
- (repo, result.getForeignProperty(), ixExec);
+ StringBuffer buf = new StringBuffer();
+ joinExec.printPlan(buf, 0, fv);
+ String plan = buf.toString();
- QueryExecutor filteredExec = new FilteredQueryExecutor
- (joinExec, result.getCompositeScore().getFilteringScore().getRemainderFilter());
+ // This is actually a pretty terrible plan due to the iterators. This
+ // is expected however, since we lied and said we had indexes.
+ String expected =
+ "join: com.amazon.carbonado.stored.Shipment\n" +
+ "...inner loop: order\n" +
+ " filter: orderID = ?\n" +
+ " collection iterator: com.amazon.carbonado.stored.Shipment\n" +
+ "...outer loop\n" +
+ " join: com.amazon.carbonado.stored.Order\n" +
+ " ...inner loop: address\n" +
+ " filter: addressID = ?\n" +
+ " collection iterator: com.amazon.carbonado.stored.Order\n" +
+ " ...outer loop\n" +
+ " filter: addressState = WA & addressZip = 12345\n" +
+ " collection iterator: com.amazon.carbonado.stored.Address\n";
- //System.out.println();
- //filteredExec.printPlan(System.out, 0, null);
+ assertEquals(expected, plan);
- joinExec.fetch(values.with("WA"));
+ joinExec.fetch(fv);
+
+ }
+
+ public void testComplexChainedJoinExecutor() throws Exception {
+ IndexedQueryAnalyzer<Shipment> iqa =
+ new IndexedQueryAnalyzer<Shipment>(Shipment.class, RepoAccess.INSTANCE);
+ Filter<Shipment> filter = Filter.filterFor
+ (Shipment.class,
+ "order.address.addressState = ? & order.address.addressID != ? " +
+ "& order.address.addressZip = ? & order.orderTotal > ? & shipmentNotes <= ? " +
+ "& order.addressID > ?");
+ FilterValues<Shipment> values = filter.initialFilterValues();
+ filter = values.getFilter();
+ OrderingList<Shipment> ordering = OrderingList
+ .get(Shipment.class, "order.address.addressCity", "shipmentNotes", "order.orderTotal");
+ IndexedQueryAnalyzer.Result result = iqa.analyze(filter, ordering);
+
+ assertTrue(result.handlesAnything());
+ assertEquals(Filter.filterFor(Shipment.class, "order.address.addressState = ?").bind(),
+ result.getCompositeScore().getFilteringScore().getIdentityFilter());
+ assertEquals(null, result.getLocalIndex());
+ assertEquals(makeIndex(Address.class, "addressState", "-addressCity"),
+ result.getForeignIndex());
+ assertEquals("order.address", result.getForeignProperty().toString());
- assertEquals(1, ixExec.mIdentityValues.length);
- assertEquals("WA", ixExec.mIdentityValues[0]);
- assertEquals(BoundaryType.OPEN, ixExec.mRangeStartBoundary);
- assertEquals(null, ixExec.mRangeStartValue);
- assertEquals(BoundaryType.OPEN, ixExec.mRangeEndBoundary);
- assertEquals(null, ixExec.mRangeEndValue);
- assertFalse(ixExec.mReverseRange);
- assertFalse(ixExec.mReverseOrder);
+ QueryExecutor<Shipment> joinExec = JoinedQueryExecutor.build
+ (RepoAccess.INSTANCE,
+ result.getForeignProperty(), result.getFilter(), result.getOrdering());
+
+ FilterValues<Shipment> fv =
+ values.with("WA").with(45).with("12345").with(100).with("Z").with(2);
+
+ StringBuffer buf = new StringBuffer();
+ joinExec.printPlan(buf, 0, fv);
+ String plan = buf.toString();
+
+ // This is actually a pretty terrible plan due to the iterators. This
+ // is expected however, since we lied and said we had indexes.
+ String expected =
+ "sort: [+order.address.addressCity, +shipmentNotes], [+order.orderTotal]\n" +
+ " join: com.amazon.carbonado.stored.Shipment\n" +
+ " ...inner loop: order\n" +
+ " sort: [+shipmentNotes]\n" +
+ " filter: shipmentNotes <= Z & orderID = ?\n" +
+ " collection iterator: com.amazon.carbonado.stored.Shipment\n" +
+ " ...outer loop\n" +
+ " join: com.amazon.carbonado.stored.Order\n" +
+ " ...inner loop: address\n" +
+ " filter: orderTotal > 100 & addressID > 2 & addressID = ?\n" +
+ " collection iterator: com.amazon.carbonado.stored.Order\n" +
+ " ...outer loop\n" +
+ " sort: [+addressCity]\n" +
+ " filter: addressState = WA & addressID != 45 & addressZip = 12345\n" +
+ " collection iterator: com.amazon.carbonado.stored.Address\n";
+
+ //System.out.println(plan);
+ assertEquals(expected, plan);
+
+ joinExec.fetch(fv);
+
+ // Now do it the easier way and compare plans.
+ QueryExecutor<Shipment> joinExec2 = result.createExecutor();
+
+ StringBuffer buf2 = new StringBuffer();
+ joinExec2.printPlan(buf2, 0, fv);
+ String plan2 = buf2.toString();
+
+ assertEquals(expected, plan2);
+
+ Filter<Shipment> expectedFilter = Filter.filterFor
+ (Shipment.class,
+ "order.address.addressState = ? & order.address.addressID != ? " +
+ "& order.address.addressZip = ? & order.orderTotal > ? " +
+ "& order.addressID > ?" +
+ "& shipmentNotes <= ? ");
+
+ assertEquals(expectedFilter.disjunctiveNormalForm(),
+ joinExec2.getFilter().unbind().disjunctiveNormalForm());
}
static class RepoAccess implements RepositoryAccess {
@@ -261,7 +353,9 @@ public class TestIndexedQueryAnalyzer extends TestCase {
* Partially implemented StorageAccess which only supplies information
* about indexes.
*/
- static class StoreAccess<S extends Storable> implements StorageAccess<S> {
+ static class StoreAccess<S extends Storable>
+ implements StorageAccess<S>, QueryExecutorFactory<S>
+ {
private final Class<S> mType;
StoreAccess(Class<S> type) {
@@ -272,24 +366,45 @@ public class TestIndexedQueryAnalyzer extends TestCase {
return mType;
}
+ public QueryExecutorFactory<S> getQueryExecutorFactory() {
+ return this;
+ }
+
+ public QueryExecutor<S> executor(Filter<S> filter, OrderingList<S> ordering) {
+ Iterable<S> iterable = Collections.emptyList();
+
+ QueryExecutor<S> exec = new IterableQueryExecutor<S>
+ (filter.getStorableType(), iterable);
+
+ if (filter != null) {
+ exec = new FilteredQueryExecutor<S>(exec, filter);
+ }
+
+ if (ordering != null && ordering.size() > 0) {
+ exec = new SortedQueryExecutor<S>(null, exec, null, ordering);
+ }
+
+ return exec;
+ }
+
public Collection<StorableIndex<S>> getAllIndexes() {
StorableIndex<S>[] indexes;
if (Address.class.isAssignableFrom(mType)) {
indexes = new StorableIndex[] {
makeIndex(mType, "addressID"),
- makeIndex(mType, "addressState")
+ makeIndex(mType, "addressState", "-addressCity")
};
} else if (Order.class.isAssignableFrom(mType)) {
indexes = new StorableIndex[] {
makeIndex(mType, "orderID"),
makeIndex(mType, "orderTotal"),
- makeIndex(mType, "addressID")
+ makeIndex(mType, "addressID", "orderTotal")
};
} else if (Shipment.class.isAssignableFrom(mType)) {
indexes = new StorableIndex[] {
makeIndex(mType, "shipmentID"),
- makeIndex(mType, "orderID"),
+ makeIndex(mType, "orderID", "shipmentNotes"),
};
} else if (Shipper.class.isAssignableFrom(mType)) {
indexes = new StorableIndex[] {
@@ -315,6 +430,10 @@ public class TestIndexedQueryAnalyzer extends TestCase {
}
public SortBuffer<S> createSortBuffer() {
+ return new ArraySortBuffer<S>();
+ }
+
+ public long countAll() {
throw new UnsupportedOperationException();
}
diff --git a/src/test/java/com/amazon/carbonado/qe/TestIndexedQueryExecutor.java b/src/test/java/com/amazon/carbonado/qe/TestIndexedQueryExecutor.java
index 8908825..a68fbac 100644
--- a/src/test/java/com/amazon/carbonado/qe/TestIndexedQueryExecutor.java
+++ b/src/test/java/com/amazon/carbonado/qe/TestIndexedQueryExecutor.java
@@ -76,7 +76,7 @@ public class TestIndexedQueryExecutor extends TestCase {
CompositeScore<StorableTestBasic> score = CompositeScore.evaluate(index, filter, null);
- Mock<StorableTestBasic> executor = new Mock(index, score);
+ Mock<StorableTestBasic> executor = new Mock<StorableTestBasic>(index, score);
executor.fetch(values.with(100));
@@ -96,7 +96,7 @@ public class TestIndexedQueryExecutor extends TestCase {
score = CompositeScore.evaluate(index, filter, null);
- executor = new Mock(index, score);
+ executor = new Mock<StorableTestBasic>(index, score);
executor.fetch(values.with(100).with(5));
@@ -117,7 +117,7 @@ public class TestIndexedQueryExecutor extends TestCase {
score = CompositeScore.evaluate(index, filter, null);
- executor = new Mock(index, score);
+ executor = new Mock<StorableTestBasic>(index, score);
executor.fetch(values.with(200));
@@ -140,7 +140,7 @@ public class TestIndexedQueryExecutor extends TestCase {
CompositeScore<StorableTestBasic> score = CompositeScore.evaluate(index, filter, null);
- Mock<StorableTestBasic> executor = new Mock(index, score);
+ Mock<StorableTestBasic> executor = new Mock<StorableTestBasic>(index, score);
executor.fetch(values.with(100));
@@ -159,7 +159,7 @@ public class TestIndexedQueryExecutor extends TestCase {
score = CompositeScore.evaluate(index, filter, null);
- executor = new Mock(index, score);
+ executor = new Mock<StorableTestBasic>(index, score);
executor.fetch(values.with(100));
@@ -178,7 +178,7 @@ public class TestIndexedQueryExecutor extends TestCase {
score = CompositeScore.evaluate(index, filter, null);
- executor = new Mock(index, score);
+ executor = new Mock<StorableTestBasic>(index, score);
executor.fetch(values.with(10).with(30));
@@ -197,7 +197,7 @@ public class TestIndexedQueryExecutor extends TestCase {
score = CompositeScore.evaluate(index, filter, null);
- executor = new Mock(index, score);
+ executor = new Mock<StorableTestBasic>(index, score);
executor.fetch(values.with(10).with(30));
@@ -216,7 +216,7 @@ public class TestIndexedQueryExecutor extends TestCase {
score = CompositeScore.evaluate(index, filter, null);
- executor = new Mock(index, score);
+ executor = new Mock<StorableTestBasic>(index, score);
executor.fetch(values.with(10).with(10));
@@ -235,7 +235,7 @@ public class TestIndexedQueryExecutor extends TestCase {
score = CompositeScore.evaluate(index, filter, null);
- executor = new Mock(index, score);
+ executor = new Mock<StorableTestBasic>(index, score);
executor.fetch(values.with(30).with(10));
@@ -255,7 +255,7 @@ public class TestIndexedQueryExecutor extends TestCase {
score = CompositeScore.evaluate(index, filter,
makeOrdering(StorableTestBasic.class, "-intProp"));
- executor = new Mock(index, score);
+ executor = new Mock<StorableTestBasic>(index, score);
executor.fetch(values.with(100).with(30));
@@ -276,7 +276,7 @@ public class TestIndexedQueryExecutor extends TestCase {
score = CompositeScore.evaluate(index, filter, null);
- executor = new Mock(index, score);
+ executor = new Mock<StorableTestBasic>(index, score);
executor.fetch(values.with(100));
@@ -296,7 +296,7 @@ public class TestIndexedQueryExecutor extends TestCase {
score = CompositeScore.evaluate(index, filter,
makeOrdering(StorableTestBasic.class, "-intProp"));
- executor = new Mock(index, score);
+ executor = new Mock<StorableTestBasic>(index, score);
executor.fetch(values.with(100));
@@ -316,7 +316,7 @@ public class TestIndexedQueryExecutor extends TestCase {
score = CompositeScore.evaluate(index, filter,
makeOrdering(StorableTestBasic.class, "intProp"));
- executor = new Mock(index, score);
+ executor = new Mock<StorableTestBasic>(index, score);
executor.fetch(values.with(100));
@@ -339,7 +339,7 @@ public class TestIndexedQueryExecutor extends TestCase {
CompositeScore<StorableTestBasic> score = CompositeScore.evaluate(index, filter, null);
- Mock<StorableTestBasic> executor = new Mock(index, score);
+ Mock<StorableTestBasic> executor = new Mock<StorableTestBasic>(index, score);
executor.fetch(values.with(100));
@@ -358,7 +358,7 @@ public class TestIndexedQueryExecutor extends TestCase {
score = CompositeScore.evaluate(index, filter, null);
- executor = new Mock(index, score);
+ executor = new Mock<StorableTestBasic>(index, score);
executor.fetch(values.with(100));
@@ -377,7 +377,7 @@ public class TestIndexedQueryExecutor extends TestCase {
score = CompositeScore.evaluate(index, filter, null);
- executor = new Mock(index, score);
+ executor = new Mock<StorableTestBasic>(index, score);
executor.fetch(values.with(10).with(30));
@@ -396,7 +396,7 @@ public class TestIndexedQueryExecutor extends TestCase {
score = CompositeScore.evaluate(index, filter, null);
- executor = new Mock(index, score);
+ executor = new Mock<StorableTestBasic>(index, score);
executor.fetch(values.with(10).with(30));
@@ -415,7 +415,7 @@ public class TestIndexedQueryExecutor extends TestCase {
score = CompositeScore.evaluate(index, filter, null);
- executor = new Mock(index, score);
+ executor = new Mock<StorableTestBasic>(index, score);
executor.fetch(values.with(10).with(10));
@@ -434,7 +434,7 @@ public class TestIndexedQueryExecutor extends TestCase {
score = CompositeScore.evaluate(index, filter, null);
- executor = new Mock(index, score);
+ executor = new Mock<StorableTestBasic>(index, score);
executor.fetch(values.with(30).with(10));
@@ -454,7 +454,7 @@ public class TestIndexedQueryExecutor extends TestCase {
score = CompositeScore.evaluate(index, filter,
makeOrdering(StorableTestBasic.class, "-intProp"));
- executor = new Mock(index, score);
+ executor = new Mock<StorableTestBasic>(index, score);
executor.fetch(values.with(100).with(30));
@@ -475,7 +475,7 @@ public class TestIndexedQueryExecutor extends TestCase {
score = CompositeScore.evaluate(index, filter, null);
- executor = new Mock(index, score);
+ executor = new Mock<StorableTestBasic>(index, score);
executor.fetch(values.with(100));
@@ -495,7 +495,7 @@ public class TestIndexedQueryExecutor extends TestCase {
score = CompositeScore.evaluate(index, filter,
makeOrdering(StorableTestBasic.class, "-intProp"));
- executor = new Mock(index, score);
+ executor = new Mock<StorableTestBasic>(index, score);
executor.fetch(values.with(100));
@@ -515,7 +515,7 @@ public class TestIndexedQueryExecutor extends TestCase {
score = CompositeScore.evaluate(index, filter,
makeOrdering(StorableTestBasic.class, "intProp"));
- executor = new Mock(index, score);
+ executor = new Mock<StorableTestBasic>(index, score);
executor.fetch(values.with(100));
@@ -541,7 +541,7 @@ public class TestIndexedQueryExecutor extends TestCase {
CompositeScore<StorableTestBasic> score = CompositeScore.evaluate(index, filter, null);
- Mock<StorableTestBasic> executor = new Mock(index, score);
+ Mock<StorableTestBasic> executor = new Mock<StorableTestBasic>(index, score);
executor.fetch(values.with(100).with(200));
@@ -560,7 +560,7 @@ public class TestIndexedQueryExecutor extends TestCase {
score = CompositeScore.evaluate(index, filter, null);
- executor = new Mock(index, score);
+ executor = new Mock<StorableTestBasic>(index, score);
executor.fetch(values.with(100).with(10));
@@ -580,7 +580,7 @@ public class TestIndexedQueryExecutor extends TestCase {
score = CompositeScore.evaluate(index, filter, null);
- executor = new Mock(index, score);
+ executor = new Mock<StorableTestBasic>(index, score);
executor.fetch(values.with(10).with(100).with(30));
@@ -612,7 +612,7 @@ public class TestIndexedQueryExecutor extends TestCase {
score = CompositeScore.evaluate(index, filter, null);
- executor = new Mock(index, score);
+ executor = new Mock<StorableTestBasic>(index, score);
executor.fetch(values.with(3).with(56.5).with(200.2));
@@ -628,7 +628,7 @@ public class TestIndexedQueryExecutor extends TestCase {
score = CompositeScore.evaluate(index, filter,
makeOrdering(StorableTestBasic.class, "doubleProp"));
- executor = new Mock(index, score);
+ executor = new Mock<StorableTestBasic>(index, score);
executor.fetch(values.with(3).with(56.5).with(200.2));
@@ -644,7 +644,7 @@ public class TestIndexedQueryExecutor extends TestCase {
score = CompositeScore.evaluate(index, filter,
makeOrdering(StorableTestBasic.class, "stringProp"));
- executor = new Mock(index, score);
+ executor = new Mock<StorableTestBasic>(index, score);
executor.fetch(values.with(3).with(56.5).with(200.2));
@@ -665,7 +665,7 @@ public class TestIndexedQueryExecutor extends TestCase {
score = CompositeScore.evaluate(index, filter,
makeOrdering(StorableTestBasic.class, "-stringProp"));
- executor = new Mock(index, score);
+ executor = new Mock<StorableTestBasic>(index, score);
executor.fetch(values.with(3).with(56.5).with("foo"));
@@ -684,11 +684,40 @@ public class TestIndexedQueryExecutor extends TestCase {
assertEquals(expectedOrdering, executor.getOrdering());
}
+ public void testHandledOrdering() throws Exception {
+ // Tests that ordering of executor only reveals what it actually uses.
+
+ StorableIndex<StorableTestBasic> index;
+ Filter<StorableTestBasic> filter;
+ FilterValues<StorableTestBasic> values;
+ CompositeScore<StorableTestBasic> score;
+ Mock<StorableTestBasic> executor;
+
+ index = makeIndex(StorableTestBasic.class, "intProp", "-doubleProp", "stringProp");
+
+ filter = Filter.filterFor(StorableTestBasic.class, "intProp = ?");
+ values = filter.initialFilterValues();
+ filter = values.getFilter();
+
+ score = CompositeScore.evaluate
+ (index, filter,
+ makeOrdering(StorableTestBasic.class, "intProp", "doubleProp"));
+
+ executor = new Mock<StorableTestBasic>(index, score);
+
+ assertEquals(values.getFilter(), executor.getFilter());
+ List<OrderedProperty<StorableTestBasic>> expectedOrdering =
+ makeOrdering(StorableTestBasic.class, "+doubleProp");
+ assertEquals(expectedOrdering, executor.getOrdering());
+ }
+
/**
* Mock object doesn't really open a cursor -- it just captures the passed
* parameters.
*/
- static class Mock<S extends Storable> extends IndexedQueryExecutor<S> {
+ static class Mock<S extends Storable> extends IndexedQueryExecutor<S>
+ implements IndexedQueryExecutor.Support<S>
+ {
Object[] mIdentityValues;
BoundaryType mRangeStartBoundary;
Object mRangeStartValue;
@@ -697,19 +726,9 @@ public class TestIndexedQueryExecutor extends TestCase {
boolean mReverseRange;
boolean mReverseOrder;
- Mock(StorableIndex<S> index, CompositeScore<S> score) {
- this(index, score, new MockSupport[1]);
- }
-
- Mock(StorableIndex<S> index, CompositeScore<S> score, MockSupport[] ref) {
- // Extremely bizarre hack to allow support to know about us.
- super(ref[0] = new MockSupport(), index, score);
- ((MockSupport<S>) ref[0]).mMock = this;
+ public Mock(StorableIndex<S> index, CompositeScore<S> score) {
+ super(null, index, score);
}
- }
-
- static class MockSupport<S extends Storable> implements IndexedQueryExecutor.Support<S> {
- Mock<S> mMock;
public Cursor<S> fetchSubset(StorableIndex<S> index,
Object[] identityValues,
@@ -720,13 +739,13 @@ public class TestIndexedQueryExecutor extends TestCase {
boolean reverseRange,
boolean reverseOrder)
{
- mMock.mIdentityValues = identityValues;
- mMock.mRangeStartBoundary = rangeStartBoundary;
- mMock.mRangeStartValue = rangeStartValue;
- mMock.mRangeEndBoundary = rangeEndBoundary;
- mMock.mRangeEndValue = rangeEndValue;
- mMock.mReverseRange = reverseRange;
- mMock.mReverseOrder = reverseOrder;
+ mIdentityValues = identityValues;
+ mRangeStartBoundary = rangeStartBoundary;
+ mRangeStartValue = rangeStartValue;
+ mRangeEndBoundary = rangeEndBoundary;
+ mRangeEndValue = rangeEndValue;
+ mReverseRange = reverseRange;
+ mReverseOrder = reverseOrder;
Collection<S> empty = Collections.emptyList();
return new IteratorCursor<S>(empty);
diff --git a/src/test/java/com/amazon/carbonado/qe/TestJoinedQueryExecutor.java b/src/test/java/com/amazon/carbonado/qe/TestJoinedQueryExecutor.java
index 423cd74..e1abca9 100644
--- a/src/test/java/com/amazon/carbonado/qe/TestJoinedQueryExecutor.java
+++ b/src/test/java/com/amazon/carbonado/qe/TestJoinedQueryExecutor.java
@@ -18,8 +18,12 @@
package com.amazon.carbonado.qe;
+import java.util.Arrays;
import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
import java.util.List;
+import java.util.Map;
import junit.framework.TestSuite;
@@ -27,14 +31,21 @@ import com.amazon.carbonado.Cursor;
import com.amazon.carbonado.FetchException;
import com.amazon.carbonado.Query;
import com.amazon.carbonado.Repository;
+import com.amazon.carbonado.RepositoryException;
import com.amazon.carbonado.Storable;
import com.amazon.carbonado.Storage;
+import com.amazon.carbonado.cursor.ArraySortBuffer;
+import com.amazon.carbonado.cursor.SortBuffer;
+
import com.amazon.carbonado.filter.Filter;
import com.amazon.carbonado.filter.FilterValues;
+import com.amazon.carbonado.info.ChainedProperty;
import com.amazon.carbonado.info.Direction;
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;
@@ -69,10 +80,23 @@ public class TestJoinedQueryExecutor extends TestQueryExecutor {
}
public void testJoin() throws Exception {
- QueryExecutor<UserAddress> addressExecutor = addressExecutor();
+ StorableInfo<UserInfo> info = StorableIntrospector.examine(UserInfo.class);
+ Map<String, ? extends StorableProperty<UserInfo>> properties = info.getAllProperties();
+
+ RepositoryAccess repoAccess = new RepoAccess();
+
+ ChainedProperty<UserInfo> targetToSourceProperty =
+ ChainedProperty.get(properties.get("address"));
+
+ Filter<UserInfo> targetFilter = Filter.filterFor(UserInfo.class, "address.state = ?");
+ OrderingList<UserInfo> targetOrdering =
+ OrderingList.get(UserInfo.class, "+address.country");
+
+ QueryExecutor<UserInfo> userExecutor = JoinedQueryExecutor.build
+ (repoAccess, targetToSourceProperty, targetFilter, targetOrdering);
- QueryExecutor<UserInfo> userExecutor = new JoinedQueryExecutor<UserAddress, UserInfo>
- (mRepository, UserInfo.class, "address", addressExecutor);
+ //System.out.println();
+ //userExecutor.printPlan(System.out, 0, null);
assertEquals("address.state = ?", userExecutor.getFilter().toString());
assertEquals("+address.country", userExecutor.getOrdering().get(0).toString());
@@ -146,8 +170,13 @@ public class TestJoinedQueryExecutor extends TestQueryExecutor {
// Now do a multi join, finding everyone with an explicit neighbor in IL.
- userExecutor = new JoinedQueryExecutor<UserAddress, UserInfo>
- (mRepository, UserInfo.class, "address.neighbor", addressExecutor);
+ targetToSourceProperty = ChainedProperty.parse(info, "address.neighbor");
+
+ targetFilter = Filter.filterFor(UserInfo.class, "address.neighbor.state = ?");
+ targetOrdering = OrderingList.get(UserInfo.class, "+address.neighbor.country");
+
+ userExecutor = JoinedQueryExecutor.build
+ (repoAccess, targetToSourceProperty, targetFilter, targetOrdering);
assertEquals("address.neighbor.state = ?", userExecutor.getFilter().toString());
assertEquals("+address.neighbor.country", userExecutor.getOrdering().get(0).toString());
@@ -163,24 +192,88 @@ public class TestJoinedQueryExecutor extends TestQueryExecutor {
cursor.close();
assertEquals(1L, userExecutor.count(values));
-
}
- protected QueryExecutor<UserAddress> addressExecutor() throws Exception {
- Storage<UserAddress> addressStorage = mRepository.storageFor(UserAddress.class);
+ class RepoAccess implements RepositoryAccess {
+ public Repository getRootRepository() {
+ return mRepository;
+ }
- QueryExecutor<UserAddress> addressExecutor = new FullScanQueryExecutor<UserAddress>
- (new ScanQuerySupport<UserAddress>(addressStorage.query()));
+ public <S extends Storable> StorageAccess<S> storageAccessFor(Class<S> type) {
+ return new StoreAccess<S>(type);
+ }
+ }
+
+ class StoreAccess<S extends Storable> implements StorageAccess<S>, QueryExecutorFactory<S> {
+ private final Class<S> mType;
+
+ StoreAccess(Class<S> type) {
+ mType = type;
+ }
+
+ public Class<S> getStorableType() {
+ return mType;
+ }
+
+ public QueryExecutorFactory<S> getQueryExecutorFactory() {
+ return this;
+ }
- addressExecutor = new FilteredQueryExecutor<UserAddress>
- (addressExecutor, Filter.filterFor(UserAddress.class, "state = ?"));
+ public QueryExecutor<S> executor(Filter<S> filter, OrderingList<S> ordering)
+ throws RepositoryException
+ {
+ Storage<S> storage = mRepository.storageFor(mType);
- OrderingList<UserAddress> ordering = OrderingList.get(UserAddress.class, "+country");
+ QueryExecutor<S> exec = new FullScanQueryExecutor<S>
+ (new ScanQuerySupport<S>(storage.query()));
- addressExecutor = new SortedQueryExecutor<UserAddress>
- (null, addressExecutor, null, ordering);
+ if (filter != null) {
+ exec = new FilteredQueryExecutor<S>(exec, filter);
+ }
- return addressExecutor;
+ if (ordering != null && ordering.size() > 0) {
+ exec = new SortedQueryExecutor<S>(null, exec, null, ordering);
+ }
+
+ return exec;
+ }
+
+ public Collection<StorableIndex<S>> getAllIndexes() {
+ StorableIndex<S>[] indexes = new StorableIndex[0];
+ return Arrays.asList(indexes);
+ }
+
+ public Storage<S> storageDelegate(StorableIndex<S> index) {
+ return null;
+ }
+
+ public SortBuffer<S> createSortBuffer() {
+ return new ArraySortBuffer<S>();
+ }
+
+ public long countAll() {
+ throw new UnsupportedOperationException();
+ }
+
+ public Cursor<S> fetchAll() {
+ throw new UnsupportedOperationException();
+ }
+
+ public Cursor<S> fetchOne(StorableIndex<S> index, Object[] identityValues) {
+ throw new UnsupportedOperationException();
+ }
+
+ public Cursor<S> fetchSubset(StorableIndex<S> index,
+ Object[] identityValues,
+ BoundaryType rangeStartBoundary,
+ Object rangeStartValue,
+ BoundaryType rangeEndBoundary,
+ Object rangeEndValue,
+ boolean reverseRange,
+ boolean reverseOrder)
+ {
+ throw new UnsupportedOperationException();
+ }
}
static class ScanQuerySupport<S extends Storable> implements FullScanQueryExecutor.Support<S> {
@@ -194,6 +287,10 @@ public class TestJoinedQueryExecutor extends TestQueryExecutor {
return mQuery.getStorableType();
}
+ public long countAll() throws FetchException {
+ return mQuery.count();
+ }
+
public Cursor<S> fetchAll() throws FetchException {
return mQuery.fetch();
}
diff --git a/src/test/java/com/amazon/carbonado/qe/TestOrderingList.java b/src/test/java/com/amazon/carbonado/qe/TestOrderingList.java
index ea9bc10..8af7a2e 100644
--- a/src/test/java/com/amazon/carbonado/qe/TestOrderingList.java
+++ b/src/test/java/com/amazon/carbonado/qe/TestOrderingList.java
@@ -184,6 +184,21 @@ public class TestOrderingList extends TestCase {
assertTrue(list_1 == list_2);
}
+ public void testSubList() throws Exception {
+ OrderingList<StorableTestBasic> list_1 =
+ OrderingList.get(StorableTestBasic.class, "date", "-intProp", "~stringProp");
+
+ assertEquals(0, list_1.subList(0, 0).size());
+ assertEquals(list_1, list_1.subList(0, 3));
+
+ OrderingList<StorableTestBasic> sub = list_1.subList(0, 1);
+ assertEquals(1, sub.size());
+ assertEquals("+date", sub.get(0).toString());
+
+ sub = list_1.subList(1, 3);
+ assertEquals(2, sub.size());
+ }
+
public void testAsArray() throws Exception {
OrderingList<StorableTestBasic> list =
OrderingList.get(StorableTestBasic.class, "date", "intProp", "stringProp");
diff --git a/src/test/java/com/amazon/carbonado/qe/TestUnionQueryAnalyzer.java b/src/test/java/com/amazon/carbonado/qe/TestUnionQueryAnalyzer.java
index e9cc8f9..7c92772 100644
--- a/src/test/java/com/amazon/carbonado/qe/TestUnionQueryAnalyzer.java
+++ b/src/test/java/com/amazon/carbonado/qe/TestUnionQueryAnalyzer.java
@@ -131,7 +131,8 @@ public class TestUnionQueryAnalyzer extends TestCase {
assertTrue(res_1.handlesAnything());
assertEquals(Filter.filterFor(Shipment.class, "orderID = ?").bind(),
res_1.getCompositeScore().getFilteringScore().getIdentityFilter());
- assertEquals(makeIndex(Shipment.class, "orderID"), res_1.getLocalIndex());
+ assertEquals(makeIndex(Shipment.class, "orderID", "shipmentNotes"),
+ res_1.getLocalIndex());
assertEquals(null, res_1.getForeignIndex());
assertEquals(null, res_1.getForeignProperty());
assertEquals(1, res_1.getRemainderOrdering().size());
@@ -163,7 +164,8 @@ public class TestUnionQueryAnalyzer extends TestCase {
assertTrue(res_1.handlesAnything());
assertTrue(res_1.getCompositeScore().getFilteringScore().hasRangeStart());
assertFalse(res_1.getCompositeScore().getFilteringScore().hasRangeEnd());
- assertEquals(makeIndex(Shipment.class, "orderID"), res_1.getLocalIndex());
+ assertEquals(makeIndex(Shipment.class, "orderID", "shipmentNotes"),
+ res_1.getLocalIndex());
assertEquals(null, res_1.getForeignIndex());
assertEquals(null, res_1.getForeignProperty());
assertEquals(1, res_1.getRemainderOrdering().size());
@@ -205,7 +207,7 @@ public class TestUnionQueryAnalyzer extends TestCase {
rangeFilters = res_1.getCompositeScore().getFilteringScore().getRangeEndFilters();
assertEquals(1, rangeFilters.size());
assertEquals(Filter.filterFor(Shipment.class, "orderID <= ?").bind(), rangeFilters.get(0));
- assertEquals(makeIndex(Shipment.class, "orderID"), res_1.getLocalIndex());
+ assertEquals(makeIndex(Shipment.class, "orderID", "shipmentNotes"), res_1.getLocalIndex());
assertEquals(null, res_1.getForeignIndex());
assertEquals(null, res_1.getForeignProperty());
// Sort operation required because the "shipmentID" index was not chosen.
@@ -249,7 +251,7 @@ public class TestUnionQueryAnalyzer extends TestCase {
assertTrue(res_1.handlesAnything());
assertEquals(Filter.filterFor(Shipment.class, "orderID = ?").bind(),
res_1.getCompositeScore().getFilteringScore().getIdentityFilter());
- assertEquals(makeIndex(Shipment.class, "orderID"), res_1.getLocalIndex());
+ assertEquals(makeIndex(Shipment.class, "orderID", "shipmentNotes"), res_1.getLocalIndex());
assertEquals(null, res_1.getForeignIndex());
assertEquals(null, res_1.getForeignProperty());
assertEquals(1, res_1.getRemainderOrdering().size());
@@ -342,7 +344,7 @@ public class TestUnionQueryAnalyzer extends TestCase {
IndexedQueryAnalyzer<Shipment>.Result res_1 = subResults.get(1);
assertTrue(res_0.handlesAnything());
- assertEquals(makeIndex(Shipment.class, "orderID"), res_0.getLocalIndex());
+ assertEquals(makeIndex(Shipment.class, "orderID", "shipmentNotes"), res_0.getLocalIndex());
assertEquals(null, res_0.getForeignIndex());
assertEquals(null, res_0.getForeignProperty());
assertEquals(1, res_0.getRemainderOrdering().size());