diff options
Diffstat (limited to 'src/main/java/com')
18 files changed, 754 insertions, 677 deletions
| 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<Employer, Person> factory = new JoinedCursorFactory<Employer, Person>
 - *     (repo, Person.class, "employer", Employer.class);
 - *
 - * Cursor<Employer> employerCursor = repo.storageFor(Employer.class)
 - *     .query("name = ?").with(...).fetch();
 - *
 - * Cursor<Person> 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<Address, Person> factory = new JoinedCursorFactory<Address, Person>
 - *     (repo, Person.class, "employer.address", Address.class);
 - *
 - * Cursor<Address> addressCursor = repo.storageFor(Address.class)
 - *     .query("state = ?").with(...).fetch();
 - *
 - * Cursor<Person> 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;
              }
          }
 | 
