diff options
Diffstat (limited to 'src/test/java/com/amazon/carbonado/qe')
12 files changed, 4231 insertions, 0 deletions
diff --git a/src/test/java/com/amazon/carbonado/qe/TestFilteredQueryExecutor.java b/src/test/java/com/amazon/carbonado/qe/TestFilteredQueryExecutor.java new file mode 100644 index 0000000..1210bd4 --- /dev/null +++ b/src/test/java/com/amazon/carbonado/qe/TestFilteredQueryExecutor.java @@ -0,0 +1,57 @@ +/*
 + * 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 junit.framework.TestSuite;
 +
 +import com.amazon.carbonado.filter.Filter;
 +import com.amazon.carbonado.filter.FilterValues;
 +
 +import com.amazon.carbonado.stored.Address;
 +
 +/**
 + *
 + *
 + * @author Brian S O'Neill
 + */
 +public class TestFilteredQueryExecutor extends TestQueryExecutor {
 +    public static void main(String[] args) {
 +        junit.textui.TestRunner.run(suite());
 +    }
 +
 +    public static TestSuite suite() {
 +        return new TestSuite(TestFilteredQueryExecutor.class);
 +    }
 +
 +    public void testBasicFiltering() throws Exception {
 +        QueryExecutor<Address> unfiltered = createExecutor(1, 2, 3, 4);
 +        Filter<Address> filter = Filter.filterFor(Address.class, "addressCountry > ?");
 +        FilterValues<Address> values = filter.initialFilterValues();
 +
 +        QueryExecutor<Address> executor = new FilteredQueryExecutor<Address>(unfiltered, filter);
 +
 +        assertEquals(values.getFilter(), executor.getFilter());
 +
 +        assertEquals(2, executor.count(values.with("country_2")));
 +
 +        assertEquals(0, executor.getOrdering().size());
 +
 +        compareElements(executor.fetch(values.with("country_2")), 3, 4);
 +    }
 +}
 diff --git a/src/test/java/com/amazon/carbonado/qe/TestFilteringScore.java b/src/test/java/com/amazon/carbonado/qe/TestFilteringScore.java new file mode 100644 index 0000000..e6b4374 --- /dev/null +++ b/src/test/java/com/amazon/carbonado/qe/TestFilteringScore.java @@ -0,0 +1,746 @@ +/*
 + * Copyright 2006 Amazon Technologies, Inc. or its affiliates.
 + * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
 + * of Amazon Technologies, Inc. or its affiliates.  All rights reserved.
 + *
 + * Licensed under the Apache License, Version 2.0 (the "License");
 + * you may not use this file except in compliance with the License.
 + * You may obtain a copy of the License at
 + *
 + *     http://www.apache.org/licenses/LICENSE-2.0
 + *
 + * Unless required by applicable law or agreed to in writing, software
 + * distributed under the License is distributed on an "AS IS" BASIS,
 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 + * See the License for the specific language governing permissions and
 + * limitations under the License.
 + */
 +
 +package com.amazon.carbonado.qe;
 +
 +import java.util.Comparator;
 +import java.util.List;
 +
 +import junit.framework.TestCase;
 +import junit.framework.TestSuite;
 +
 +import com.amazon.carbonado.Storable;
 +
 +import com.amazon.carbonado.info.Direction;
 +import static 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;
 +
 +import com.amazon.carbonado.filter.Filter;
 +import com.amazon.carbonado.filter.PropertyFilter;
 +
 +import com.amazon.carbonado.stored.StorableTestBasic;
 +
 +/**
 + *
 + *
 + * @author Brian S O'Neill
 + */
 +public class TestFilteringScore extends TestCase {
 +    public static void main(String[] args) {
 +        junit.textui.TestRunner.run(suite());
 +    }
 +
 +    public static TestSuite suite() {
 +        return new TestSuite(TestFilteringScore.class);
 +    }
 +
 +    static <S extends Storable> StorableIndex<S> makeIndex(Class<S> type, String... props) {
 +        return TestOrderingScore.makeIndex(type, props);
 +    }
 +
 +    public TestFilteringScore(String name) {
 +        super(name);
 +    }
 +
 +    public void testNoFilter() throws Exception {
 +        StorableIndex<StorableTestBasic> ix = makeIndex(StorableTestBasic.class, "id");
 +        Filter<StorableTestBasic> filter = Filter.getOpenFilter(StorableTestBasic.class);
 +        FilteringScore score = FilteringScore.evaluate(ix, filter);
 +
 +        assertFalse(score.isIndexClustered());
 +        assertEquals(1, score.getIndexPropertyCount());
 +
 +        assertEquals(0, score.getIdentityCount());
 +        assertEquals(0, score.getIdentityFilters().size());
 +        assertFalse(score.hasRangeStart());
 +        assertEquals(0, score.getRangeStartFilters().size());
 +        assertFalse(score.hasRangeEnd());
 +        assertEquals(0, score.getRangeEndFilters().size());
 +        assertFalse(score.hasRangeMatch());
 +        assertFalse(score.hasAnyMatches());
 +        assertEquals(0, score.getRemainderCount());
 +        assertEquals(0, score.getRemainderFilters().size());
 +    }
 +
 +    public void testSimpleIndexMisses() throws Exception {
 +        StorableIndex<StorableTestBasic> ix = makeIndex(StorableTestBasic.class, "intProp");
 +        // Filter by a property not in index.
 +        Filter<StorableTestBasic> filter = Filter.filterFor(StorableTestBasic.class, "id = ?");
 +
 +        FilteringScore score = FilteringScore.evaluate(ix, filter);
 +
 +        assertFalse(score.isIndexClustered());
 +        assertEquals(1, score.getIndexPropertyCount());
 +
 +        assertEquals(0, score.getIdentityCount());
 +        assertEquals(0, score.getIdentityFilters().size());
 +        assertFalse(score.hasRangeStart());
 +        assertEquals(0, score.getRangeStartFilters().size());
 +        assertFalse(score.hasRangeEnd());
 +        assertEquals(0, score.getRangeEndFilters().size());
 +        assertFalse(score.hasRangeMatch());
 +        assertFalse(score.hasAnyMatches());
 +        assertEquals(1, score.getRemainderCount());
 +        assertEquals(1, score.getRemainderFilters().size());
 +        assertEquals(filter, score.getRemainderFilters().get(0));
 +        assertEquals(filter, score.getRemainderFilter());
 +
 +        // Try again with matching property, but with an operator that cannot be used by index.
 +        filter = Filter.filterFor(StorableTestBasic.class, "intProp != ?");
 +
 +        score = FilteringScore.evaluate(ix, filter);
 +
 +        assertFalse(score.isIndexClustered());
 +        assertEquals(1, score.getIndexPropertyCount());
 +
 +        assertEquals(0, score.getIdentityCount());
 +        assertEquals(0, score.getIdentityFilters().size());
 +        assertFalse(score.hasRangeStart());
 +        assertEquals(0, score.getRangeStartFilters().size());
 +        assertFalse(score.hasRangeEnd());
 +        assertEquals(0, score.getRangeEndFilters().size());
 +        assertFalse(score.hasRangeMatch());
 +        assertFalse(score.hasAnyMatches());
 +        assertEquals(1, score.getRemainderCount());
 +        assertEquals(1, score.getRemainderFilters().size());
 +        assertEquals(filter, score.getRemainderFilters().get(0));
 +        assertEquals(filter, score.getRemainderFilter());
 +    }
 +
 +    public void testSimpleIndexMatches() throws Exception {
 +        StorableIndex<StorableTestBasic> ix = makeIndex(StorableTestBasic.class, "id");
 +        // Filter by a property in index.
 +        Filter<StorableTestBasic> filter = Filter.filterFor(StorableTestBasic.class, "id = ?");
 +
 +        FilteringScore score = FilteringScore.evaluate(ix, filter);
 +
 +        assertFalse(score.isIndexClustered());
 +        assertEquals(1, score.getIndexPropertyCount());
 +
 +        assertEquals(1, score.getIdentityCount());
 +        assertEquals(1, score.getArrangementScore());
 +        assertEquals(filter, score.getIdentityFilters().get(0));
 +        assertFalse(score.hasRangeStart());
 +        assertEquals(0, score.getRangeStartFilters().size());
 +        assertFalse(score.hasRangeEnd());
 +        assertEquals(0, score.getRangeEndFilters().size());
 +        assertFalse(score.hasRangeMatch());
 +        assertTrue(score.hasAnyMatches());
 +        assertEquals(0, score.getRemainderCount());
 +        assertEquals(0, score.getRemainderFilters().size());
 +        assertEquals(null, score.getRemainderFilter());
 +
 +        // Try again with open ranges.
 +        for (int i=0; i<4; i++) {
 +            String expr;
 +            switch (i) {
 +            default: expr = "id > ?"; break;
 +            case 1: expr = "id >= ?"; break;
 +            case 2: expr = "id < ?"; break;
 +            case 3: expr = "id <= ?"; break;
 +            }
 +
 +            filter = Filter.filterFor(StorableTestBasic.class, expr);
 +
 +            score = FilteringScore.evaluate(ix, filter);
 +
 +            assertEquals(0, score.getIdentityCount());
 +            assertEquals(0, score.getIdentityFilters().size());
 +            assertFalse(score.hasRangeMatch());
 +            assertTrue(score.hasAnyMatches());
 +            assertEquals(0, score.getRemainderCount());
 +            assertEquals(0, score.getRemainderFilters().size());
 +            assertEquals(null, score.getRemainderFilter());
 +
 +            if (i < 2) {
 +                assertTrue(score.hasRangeStart());
 +                assertEquals(1, score.getRangeStartFilters().size());
 +                assertEquals(filter, score.getRangeStartFilters().get(0));
 +                assertFalse(score.hasRangeEnd());
 +                assertEquals(0, score.getRangeEndFilters().size());
 +            } else {
 +                assertFalse(score.hasRangeStart());
 +                assertEquals(0, score.getRangeStartFilters().size());
 +                assertTrue(score.hasRangeEnd());
 +                assertEquals(1, score.getRangeEndFilters().size());
 +                assertEquals(filter, score.getRangeEndFilters().get(0));
 +            }
 +        }
 +
 +        // Try with duplicate open ranges.
 +        filter = Filter.filterFor(StorableTestBasic.class, "id > ? & id > ?");
 +
 +        score = FilteringScore.evaluate(ix, filter);
 +
 +        assertEquals(0, score.getIdentityCount());
 +        assertEquals(0, score.getIdentityFilters().size());
 +        assertFalse(score.hasRangeMatch());
 +        assertTrue(score.hasAnyMatches());
 +        assertEquals(0, score.getRemainderCount());
 +        assertEquals(0, score.getRemainderFilters().size());
 +        assertEquals(null, score.getRemainderFilter());
 +
 +        assertTrue(score.hasRangeStart());
 +        assertEquals(2, score.getRangeStartFilters().size());
 +        assertEquals(Filter.filterFor(StorableTestBasic.class, "id > ?"),
 +                     score.getRangeStartFilters().get(0));
 +        assertEquals(Filter.filterFor(StorableTestBasic.class, "id > ?"),
 +                     score.getRangeStartFilters().get(1));
 +        assertFalse(score.hasRangeEnd());
 +        assertEquals(0, score.getRangeEndFilters().size());
 +
 +        // Try with duplicate end ranges and slightly different operator.
 +        filter = Filter.filterFor(StorableTestBasic.class, "id < ? & id <= ?");
 +
 +        score = FilteringScore.evaluate(ix, filter);
 +
 +        assertEquals(0, score.getIdentityCount());
 +        assertEquals(0, score.getIdentityFilters().size());
 +        assertFalse(score.hasRangeMatch());
 +        assertTrue(score.hasAnyMatches());
 +        assertEquals(0, score.getRemainderCount());
 +        assertEquals(0, score.getRemainderFilters().size());
 +        assertEquals(null, score.getRemainderFilter());
 +
 +        assertFalse(score.hasRangeStart());
 +        assertEquals(0, score.getRangeStartFilters().size());
 +        assertTrue(score.hasRangeEnd());
 +        assertEquals(2, score.getRangeEndFilters().size());
 +        assertEquals(Filter.filterFor(StorableTestBasic.class, "id < ?"),
 +                     score.getRangeEndFilters().get(0));
 +        assertEquals(Filter.filterFor(StorableTestBasic.class, "id <= ?"),
 +                     score.getRangeEndFilters().get(1));
 +
 +        // Try with complete range.
 +        filter = Filter.filterFor(StorableTestBasic.class, "id > ? & id < ?");
 +
 +        score = FilteringScore.evaluate(ix, filter);
 +
 +        assertEquals(0, score.getIdentityCount());
 +        assertEquals(0, score.getIdentityFilters().size());
 +        assertTrue(score.hasRangeMatch());
 +        assertTrue(score.hasAnyMatches());
 +        assertEquals(0, score.getRemainderCount());
 +        assertEquals(0, score.getRemainderFilters().size());
 +        assertEquals(null, score.getRemainderFilter());
 +
 +        assertTrue(score.hasRangeStart());
 +        assertEquals(1, score.getRangeStartFilters().size());
 +        assertEquals(Filter.filterFor(StorableTestBasic.class, "id > ?"),
 +                     score.getRangeStartFilters().get(0));
 +        assertTrue(score.hasRangeEnd());
 +        assertEquals(1, score.getRangeEndFilters().size());
 +        assertEquals(Filter.filterFor(StorableTestBasic.class, "id < ?"),
 +                     score.getRangeEndFilters().get(0));
 +
 +        // Try with an identity filter consuming others.
 +        filter = Filter.filterFor(StorableTestBasic.class, "id > ? & id = ? & id = ?");
 +
 +        score = FilteringScore.evaluate(ix, filter);
 +
 +        assertEquals(1, score.getIdentityCount());
 +        assertEquals(1, score.getIdentityFilters().size());
 +        assertEquals(1, score.getArrangementScore());
 +        assertEquals(Filter.filterFor(StorableTestBasic.class, "id = ?"),
 +                     score.getIdentityFilters().get(0));
 +
 +        assertFalse(score.hasRangeStart());
 +        assertFalse(score.hasRangeEnd());
 +        assertFalse(score.hasRangeMatch());
 +        assertTrue(score.hasAnyMatches());
 +        assertEquals(2, score.getRemainderCount());
 +        assertEquals(2, score.getRemainderFilters().size());
 +        // Order of properties in filter accounts for sorting by PropertyFilterList.
 +        assertEquals(Filter.filterFor(StorableTestBasic.class, "id = ? & id > ?"),
 +                     score.getRemainderFilter());
 +
 +        // Try with complex mix of non-identity matches.
 +        filter = Filter.filterFor
 +            (StorableTestBasic.class,
 +             "id > ? & id != ? & id <= ? & id >= ? & id != ? & id > ?");
 +
 +        score = FilteringScore.evaluate(ix, filter);
 +
 +        assertEquals(0, score.getIdentityCount());
 +        assertEquals(0, score.getIdentityFilters().size());
 +
 +        assertTrue(score.hasRangeMatch());
 +        assertTrue(score.hasAnyMatches());
 +
 +        assertTrue(score.hasRangeStart());
 +        assertEquals(3, score.getRangeStartFilters().size());
 +        assertEquals(Filter.filterFor(StorableTestBasic.class, "id > ?"),
 +                     score.getRangeStartFilters().get(0));
 +        assertEquals(Filter.filterFor(StorableTestBasic.class, "id >= ?"),
 +                     score.getRangeStartFilters().get(1));
 +        assertEquals(Filter.filterFor(StorableTestBasic.class, "id > ?"),
 +                     score.getRangeStartFilters().get(2));
 +
 +        assertTrue(score.hasRangeEnd());
 +        assertEquals(1, score.getRangeEndFilters().size());
 +        assertEquals(Filter.filterFor(StorableTestBasic.class, "id <= ?"),
 +                     score.getRangeEndFilters().get(0));
 +
 +        assertEquals(2, score.getRemainderCount());
 +        assertEquals(2, score.getRemainderFilters().size());
 +        assertEquals(Filter.filterFor(StorableTestBasic.class, "id != ? & id != ?"),
 +                     score.getRemainderFilter());
 +    }
 +
 +    public void testCompositeIndexMatches() throws Exception {
 +        StorableIndex<StorableTestBasic> ix = makeIndex(StorableTestBasic.class,
 +                                                        "id", "intProp", "stringProp");
 +        // Filter by a property in index.
 +        Filter<StorableTestBasic> filter = Filter.filterFor(StorableTestBasic.class, "id = ?");
 +
 +        FilteringScore score = FilteringScore.evaluate(ix, filter);
 +
 +        assertFalse(score.isIndexClustered());
 +        assertEquals(3, score.getIndexPropertyCount());
 +
 +        assertEquals(1, score.getIdentityCount());
 +        assertEquals(1, score.getArrangementScore());
 +        assertEquals(filter, score.getIdentityFilters().get(0));
 +        assertFalse(score.hasRangeStart());
 +        assertEquals(0, score.getRangeStartFilters().size());
 +        assertFalse(score.hasRangeEnd());
 +        assertEquals(0, score.getRangeEndFilters().size());
 +        assertFalse(score.hasRangeMatch());
 +        assertTrue(score.hasAnyMatches());
 +        assertEquals(0, score.getRemainderCount());
 +        assertEquals(0, score.getRemainderFilters().size());
 +        assertEquals(null, score.getRemainderFilter());
 +
 +        // Filter by a property with a gap. (filter must include "id" to use index)
 +        filter = Filter.filterFor(StorableTestBasic.class, "intProp = ?");
 +
 +        score = FilteringScore.evaluate(ix, filter);
 +
 +        assertEquals(0, score.getIdentityCount());
 +        assertFalse(score.hasAnyMatches());
 +        assertEquals(1, score.getRemainderCount());
 +        assertEquals(1, score.getRemainderFilters().size());
 +        assertEquals(filter, score.getRemainderFilter());
 +
 +        // Filter by a property with a gap and a range operator. (index still cannot be used)
 +        filter = Filter.filterFor(StorableTestBasic.class, "intProp = ? & stringProp < ?");
 +
 +        score = FilteringScore.evaluate(ix, filter);
 +
 +        assertEquals(0, score.getIdentityCount());
 +        assertFalse(score.hasAnyMatches());
 +        assertEquals(2, score.getRemainderCount());
 +        assertEquals(2, score.getRemainderFilters().size());
 +        assertEquals(filter, score.getRemainderFilter());
 +
 +        // Filter with range match before identity match. Identity cannot be used.
 +        filter = Filter.filterFor(StorableTestBasic.class, "intProp = ? & id < ?");
 +
 +        score = FilteringScore.evaluate(ix, filter);
 +
 +        assertEquals(0, score.getIdentityCount());
 +        assertTrue(score.hasAnyMatches());
 +
 +        assertTrue(score.hasRangeEnd());
 +        assertEquals(1, score.getRangeEndFilters().size());
 +        assertEquals(Filter.filterFor(StorableTestBasic.class, "id < ?"),
 +                     score.getRangeEndFilters().get(0));
 +
 +        assertEquals(1, score.getRemainderCount());
 +        assertEquals(1, score.getRemainderFilters().size());
 +        assertEquals(Filter.filterFor(StorableTestBasic.class, "intProp = ?"),
 +                     score.getRemainderFilter());
 +
 +        // Filter with fully specified identity match, but a few remaining.
 +        filter = Filter.filterFor
 +            (StorableTestBasic.class,
 +             "intProp = ? & id = ? & stringProp = ? & stringProp > ? & doubleProp = ?");
 +
 +        score = FilteringScore.evaluate(ix, filter);
 +
 +        assertTrue(score.hasAnyMatches());
 +        assertEquals(3, score.getIdentityCount());
 +        assertEquals(2, score.getArrangementScore());
 +
 +        assertEquals(Filter.filterFor(StorableTestBasic.class, "id = ?"),
 +                     score.getIdentityFilters().get(0));
 +        assertEquals(Filter.filterFor(StorableTestBasic.class, "intProp = ?"),
 +                     score.getIdentityFilters().get(1));
 +        assertEquals(Filter.filterFor(StorableTestBasic.class, "stringProp = ?"),
 +                     score.getIdentityFilters().get(2));
 +
 +        assertEquals(2, score.getRemainderCount());
 +        assertEquals(2, score.getRemainderFilters().size());
 +        // Order of remainder properties accounts for sorting by PropertyFilterList.
 +        assertEquals(Filter.filterFor(StorableTestBasic.class, "doubleProp = ?"),
 +                     score.getRemainderFilters().get(0));
 +        assertEquals(Filter.filterFor(StorableTestBasic.class, "stringProp > ?"),
 +                     score.getRemainderFilters().get(1));
 +
 +        // Filter with identity and range matches.
 +        filter = Filter.filterFor
 +            (StorableTestBasic.class,
 +             "intProp > ? & id = ? & stringProp = ? & intProp <= ? & " +
 +             "stringProp < ? & doubleProp = ?");
 +
 +        score = FilteringScore.evaluate(ix, filter);
 +
 +        assertTrue(score.hasAnyMatches());
 +        assertEquals(1, score.getIdentityCount());
 +        assertEquals(1, score.getArrangementScore());
 +
 +        assertEquals(3, score.getRemainderCount());
 +        assertEquals(3, score.getRemainderFilters().size());
 +        // Order of remainder properties accounts for sorting by PropertyFilterList.
 +        assertEquals(Filter.filterFor(StorableTestBasic.class, "stringProp = ?"),
 +                     score.getRemainderFilters().get(0));
 +        assertEquals(Filter.filterFor(StorableTestBasic.class, "doubleProp = ?"),
 +                     score.getRemainderFilters().get(1));
 +        assertEquals(Filter.filterFor(StorableTestBasic.class, "stringProp < ?"),
 +                     score.getRemainderFilters().get(2));
 +
 +        assertTrue(score.hasRangeMatch());
 +        assertTrue(score.hasRangeStart());
 +        assertEquals(1, score.getRangeStartFilters().size());
 +        assertEquals(Filter.filterFor(StorableTestBasic.class, "intProp > ?"),
 +                     score.getRangeStartFilters().get(0));
 +
 +        assertTrue(score.hasRangeEnd());
 +        assertEquals(1, score.getRangeEndFilters().size());
 +        assertEquals(Filter.filterFor(StorableTestBasic.class, "intProp <= ?"),
 +                     score.getRangeEndFilters().get(0));
 +
 +        // Filter with fully specified identity match, with backwards arrangement.
 +        filter = Filter.filterFor
 +            (StorableTestBasic.class, "stringProp = ? & intProp = ? & id = ?");
 +
 +        score = FilteringScore.evaluate(ix, filter);
 +
 +        assertTrue(score.hasAnyMatches());
 +        assertEquals(3, score.getIdentityCount());
 +        // Arrangement score is always at least one if identity count is not zero.
 +        assertEquals(1, score.getArrangementScore());
 +    }
 +
 +    public void testReverseRange() throws Exception {
 +        StorableIndex<StorableTestBasic> ix = makeIndex(StorableTestBasic.class,
 +                                                        "id", "intProp");
 +        Filter<StorableTestBasic> filter =
 +            Filter.filterFor(StorableTestBasic.class, "id = ? & intProp > ?");
 +
 +        FilteringScore score = FilteringScore.evaluate(ix, filter);
 +
 +        assertFalse(score.shouldReverseRange());
 +
 +        ix = makeIndex(StorableTestBasic.class, "id", "-intProp");
 +        score = FilteringScore.evaluate(ix, filter);
 +
 +        assertTrue(score.shouldReverseRange());
 +    }
 +
 +    public void testRangeComparator() throws Exception {
 +        StorableIndex<StorableTestBasic> ix_1, ix_2;
 +        Filter<StorableTestBasic> filter;
 +        FilteringScore score_1, score_2;
 +        Comparator<FilteringScore<?>> comp = FilteringScore.rangeComparator();
 +
 +        ix_1 = makeIndex(StorableTestBasic.class, "id", "intProp", "doubleProp", "-stringProp");
 +        ix_2 = makeIndex(StorableTestBasic.class, "id", "stringProp", "intProp");
 +
 +        filter = Filter.filterFor(StorableTestBasic.class, "id = ?");
 +        score_1 = FilteringScore.evaluate(ix_1, filter);
 +        score_2 = FilteringScore.evaluate(ix_2, filter);
 +
 +        assertEquals(0, comp.compare(score_1, score_2));
 +        assertEquals(0, comp.compare(score_2, score_1));
 +
 +        score_1 = FilteringScore.evaluate(ix_1.clustered(true), filter);
 +        score_2 = FilteringScore.evaluate(ix_2, filter);
 +
 +        assertEquals(-1, comp.compare(score_1, score_2));
 +
 +        filter = Filter.filterFor(StorableTestBasic.class, "stringProp = ?");
 +        score_1 = FilteringScore.evaluate(ix_1, filter);
 +        score_2 = FilteringScore.evaluate(ix_2, filter);
 +
 +        assertEquals(0, comp.compare(score_1, score_2));
 +        assertEquals(0, comp.compare(score_2, score_1));
 +
 +        filter = Filter.filterFor(StorableTestBasic.class, "stringProp = ?");
 +        score_1 = FilteringScore.evaluate(ix_1.clustered(true), filter);
 +        score_2 = FilteringScore.evaluate(ix_2, filter);
 +
 +        assertEquals(0, comp.compare(score_1, score_2));
 +        assertEquals(0, comp.compare(score_2, score_1));
 +
 +        filter = Filter.filterFor(StorableTestBasic.class, "id = ? & intProp = ?");
 +        score_1 = FilteringScore.evaluate(ix_1, filter);
 +        score_2 = FilteringScore.evaluate(ix_2, filter);
 +
 +        assertEquals(-1, comp.compare(score_1, score_2));
 +        assertEquals(1, comp.compare(score_2, score_1));
 +
 +        filter = Filter.filterFor(StorableTestBasic.class, "id = ? & stringProp != ?");
 +        score_1 = FilteringScore.evaluate(ix_1, filter);
 +        score_2 = FilteringScore.evaluate(ix_2, filter);
 +
 +        assertEquals(0, comp.compare(score_1, score_2));
 +        assertEquals(0, comp.compare(score_2, score_1));
 +
 +        filter = Filter.filterFor(StorableTestBasic.class, "id = ? & stringProp = ?");
 +        score_1 = FilteringScore.evaluate(ix_1, filter);
 +        score_2 = FilteringScore.evaluate(ix_2, filter);
 +
 +        assertEquals(1, comp.compare(score_1, score_2));
 +        assertEquals(-1, comp.compare(score_2, score_1));
 +
 +        // Both indexes are as good, since range is open.
 +        filter = Filter.filterFor(StorableTestBasic.class, "id = ? & stringProp > ?");
 +        score_1 = FilteringScore.evaluate(ix_1, filter);
 +        score_2 = FilteringScore.evaluate(ix_2, filter);
 +
 +        assertEquals(0, comp.compare(score_1, score_2));
 +        assertEquals(0, comp.compare(score_2, score_1));
 +
 +        filter = Filter.filterFor(StorableTestBasic.class,
 +                                  "id = ? & intProp = ? & stringProp > ?");
 +        score_1 = FilteringScore.evaluate(ix_1, filter);
 +        score_2 = FilteringScore.evaluate(ix_2, filter);
 +
 +        assertEquals(-1, comp.compare(score_1, score_2));
 +        assertEquals(1, comp.compare(score_2, score_1));
 +
 +        // Test range match with tie resolved by clustered index.
 +        filter = Filter.filterFor(StorableTestBasic.class, "id >= ? & id < ?");
 +        score_1 = FilteringScore.evaluate(ix_1, filter);
 +        score_2 = FilteringScore.evaluate(ix_2, filter);
 +
 +        assertEquals(0, comp.compare(score_1, score_2));
 +        assertEquals(0, comp.compare(score_2, score_1));
 +
 +        filter = Filter.filterFor(StorableTestBasic.class, "id >= ? & id < ?");
 +        score_1 = FilteringScore.evaluate(ix_1, filter);
 +        score_2 = FilteringScore.evaluate(ix_2.clustered(true), filter);
 +
 +        assertEquals(1, comp.compare(score_1, score_2));
 +        assertEquals(-1, comp.compare(score_2, score_1));
 +
 +        // Test open range match with tie still not resolved by clustered index.
 +        filter = Filter.filterFor(StorableTestBasic.class, "id >= ?");
 +        score_1 = FilteringScore.evaluate(ix_1, filter);
 +        score_2 = FilteringScore.evaluate(ix_2, filter);
 +
 +        assertEquals(0, comp.compare(score_1, score_2));
 +        assertEquals(0, comp.compare(score_2, score_1));
 +
 +        filter = Filter.filterFor(StorableTestBasic.class, "id >= ?");
 +        score_1 = FilteringScore.evaluate(ix_1, filter);
 +        score_2 = FilteringScore.evaluate(ix_2.clustered(true), filter);
 +
 +        assertEquals(0, comp.compare(score_1, score_2));
 +        assertEquals(0, comp.compare(score_2, score_1));
 +    }
 +
 +    public void testFullComparator() throws Exception {
 +        StorableIndex<StorableTestBasic> ix_1, ix_2;
 +        Filter<StorableTestBasic> filter;
 +        FilteringScore score_1, score_2;
 +        Comparator<FilteringScore<?>> comp = FilteringScore.fullComparator();
 +
 +        ix_1 = makeIndex(StorableTestBasic.class, "id", "intProp", "doubleProp", "-stringProp");
 +        ix_2 = makeIndex(StorableTestBasic.class, "id", "stringProp", "intProp");
 +
 +        filter = Filter.filterFor(StorableTestBasic.class, "id = ?");
 +        score_1 = FilteringScore.evaluate(ix_1, filter);
 +        score_2 = FilteringScore.evaluate(ix_2, filter);
 +
 +        // Second is better because it has fewer properties.
 +        assertEquals(1, comp.compare(score_1, score_2));
 +        assertEquals(-1, comp.compare(score_2, score_1));
 +
 +        score_1 = FilteringScore.evaluate(ix_1.clustered(true), filter);
 +        score_2 = FilteringScore.evaluate(ix_2, filter);
 +
 +        assertEquals(-1, comp.compare(score_1, score_2));
 +
 +        filter = Filter.filterFor(StorableTestBasic.class, "stringProp = ?");
 +        score_1 = FilteringScore.evaluate(ix_1, filter);
 +        score_2 = FilteringScore.evaluate(ix_2, filter);
 +
 +        // Although no property matches, second is better just because it has fewer properties.
 +        assertEquals(1, comp.compare(score_1, score_2));
 +        assertEquals(-1, comp.compare(score_2, score_1));
 +
 +        filter = Filter.filterFor(StorableTestBasic.class, "stringProp = ?");
 +        score_1 = FilteringScore.evaluate(ix_1.clustered(true), filter);
 +        score_2 = FilteringScore.evaluate(ix_2, filter);
 +
 +        // Although no property matches, first is better just because it is clustered.
 +        assertEquals(-1, comp.compare(score_1, score_2));
 +        assertEquals(1, comp.compare(score_2, score_1));
 +
 +        filter = Filter.filterFor(StorableTestBasic.class, "id = ? & intProp = ?");
 +        score_1 = FilteringScore.evaluate(ix_1, filter);
 +        score_2 = FilteringScore.evaluate(ix_2, filter);
 +
 +        assertEquals(-1, comp.compare(score_1, score_2));
 +        assertEquals(1, comp.compare(score_2, score_1));
 +
 +        filter = Filter.filterFor(StorableTestBasic.class, "id = ? & stringProp != ?");
 +        score_1 = FilteringScore.evaluate(ix_1, filter);
 +        score_2 = FilteringScore.evaluate(ix_2, filter);
 +
 +        // Second is better because it has fewer properties.
 +        assertEquals(1, comp.compare(score_1, score_2));
 +        assertEquals(-1, comp.compare(score_2, score_1));
 +
 +        filter = Filter.filterFor(StorableTestBasic.class, "id = ? & stringProp = ?");
 +        score_1 = FilteringScore.evaluate(ix_1, filter);
 +        score_2 = FilteringScore.evaluate(ix_2, filter);
 +
 +        assertEquals(1, comp.compare(score_1, score_2));
 +        assertEquals(-1, comp.compare(score_2, score_1));
 +
 +        // Second index is better since the open range matches.
 +        filter = Filter.filterFor(StorableTestBasic.class, "id = ? & stringProp > ?");
 +        score_1 = FilteringScore.evaluate(ix_1, filter);
 +        score_2 = FilteringScore.evaluate(ix_2, filter);
 +
 +        assertEquals(1, comp.compare(score_1, score_2));
 +        assertEquals(-1, comp.compare(score_2, score_1));
 +
 +        filter = Filter.filterFor(StorableTestBasic.class,
 +                                  "id = ? & intProp = ? & stringProp > ?");
 +        score_1 = FilteringScore.evaluate(ix_1, filter);
 +        score_2 = FilteringScore.evaluate(ix_2, filter);
 +
 +        assertEquals(-1, comp.compare(score_1, score_2));
 +        assertEquals(1, comp.compare(score_2, score_1));
 +
 +        // Test range match with tie resolved by clustered index.
 +        filter = Filter.filterFor(StorableTestBasic.class, "id >= ? & id < ?");
 +        score_1 = FilteringScore.evaluate(ix_1, filter);
 +        score_2 = FilteringScore.evaluate(ix_2, filter);
 +
 +        // Second is better because it has fewer properties.
 +        assertEquals(1, comp.compare(score_1, score_2));
 +        assertEquals(-1, comp.compare(score_2, score_1));
 +
 +        filter = Filter.filterFor(StorableTestBasic.class, "id >= ? & id < ?");
 +        score_1 = FilteringScore.evaluate(ix_1.clustered(true), filter);
 +        score_2 = FilteringScore.evaluate(ix_2, filter);
 +
 +        // First is better because it is clusted.
 +        assertEquals(-1, comp.compare(score_1, score_2));
 +        assertEquals(1, comp.compare(score_2, score_1));
 +
 +        // Test range match with tie resolved by clustered index.
 +        filter = Filter.filterFor(StorableTestBasic.class, "id >= ? & id < ?");
 +        score_1 = FilteringScore.evaluate(ix_1.clustered(true), filter);
 +        score_2 = FilteringScore.evaluate(ix_2.clustered(true), filter);
 +
 +        // Second is better because it has fewer properties.
 +        assertEquals(1, comp.compare(score_1, score_2));
 +        assertEquals(-1, comp.compare(score_2, score_1));
 +    }
 +
 +    public void testArrangementFullComparator() throws Exception {
 +        StorableIndex<StorableTestBasic> ix_1, ix_2;
 +        Filter<StorableTestBasic> filter;
 +        FilteringScore score_1, score_2;
 +
 +        ix_1 = makeIndex(StorableTestBasic.class, "id", "intProp", "-stringProp");
 +        ix_2 = makeIndex(StorableTestBasic.class, "id", "stringProp", "intProp");
 +
 +        filter = Filter.filterFor(StorableTestBasic.class,
 +                                  "id = ? & intProp = ? & stringProp = ?");
 +
 +        score_1 = FilteringScore.evaluate(ix_1, filter);
 +        score_2 = FilteringScore.evaluate(ix_2, filter);
 +
 +        Comparator<FilteringScore<?>> comp = FilteringScore.rangeComparator();
 +
 +        // With just range comparison, either index works.
 +        assertEquals(0, comp.compare(score_1, score_2));
 +        assertEquals(0, comp.compare(score_2, score_1));
 +
 +        comp = FilteringScore.fullComparator();
 +
 +        // First index is better because property arrangement matches.
 +        assertEquals(-1, comp.compare(score_1, score_2));
 +        assertEquals(1, comp.compare(score_2, score_1));
 +    }
 +
 +    public void testRangeFilterSubset() throws Exception {
 +        StorableIndex<StorableTestBasic> ix = makeIndex(StorableTestBasic.class, "id");
 +        Filter<StorableTestBasic> filter;
 +        FilteringScore score;
 +
 +        filter = Filter.filterFor(StorableTestBasic.class, "id >= ? & id >= ? & id < ? & id <= ?");
 +
 +        score = FilteringScore.evaluate(ix, filter);
 +
 +        assertEquals(2, score.getRangeStartFilters().size());
 +
 +        List<PropertyFilter<?>> exStart = score.getExclusiveRangeStartFilters();
 +        assertEquals(0, exStart.size());
 +
 +        List<PropertyFilter<?>> inStart = score.getInclusiveRangeStartFilters();
 +        assertEquals(2, inStart.size());
 +        assertEquals(Filter.filterFor(StorableTestBasic.class, "id >= ?"), inStart.get(0));
 +        assertEquals(Filter.filterFor(StorableTestBasic.class, "id >= ?"), inStart.get(1));
 +
 +        assertEquals(2, score.getRangeEndFilters().size());
 +
 +        List<PropertyFilter<?>> exEnd = score.getExclusiveRangeEndFilters();
 +        assertEquals(1, exEnd.size());
 +        assertEquals(Filter.filterFor(StorableTestBasic.class, "id < ?"), exEnd.get(0));
 +
 +        List<PropertyFilter<?>> inEnd = score.getInclusiveRangeEndFilters();
 +        assertEquals(1, inEnd.size());
 +        assertEquals(Filter.filterFor(StorableTestBasic.class, "id <= ?"), inEnd.get(0));
 +    }
 +
 +    public void testKeyMatch() throws Exception {
 +        StorableIndex<StorableTestBasic> ix = makeIndex(StorableTestBasic.class, "id", "intProp");
 +        ix = ix.unique(true);
 +        Filter<StorableTestBasic> filter;
 +        FilteringScore score;
 +
 +        filter = Filter.filterFor(StorableTestBasic.class, "id = ?");
 +        score = FilteringScore.evaluate(ix, filter);
 +        assertFalse(score.isKeyMatch());
 +
 +        filter = Filter.filterFor(StorableTestBasic.class, "id = ? & intProp > ?");
 +        score = FilteringScore.evaluate(ix, filter);
 +        assertFalse(score.isKeyMatch());
 +
 +        filter = Filter.filterFor(StorableTestBasic.class, "id = ? & intProp = ?");
 +        score = FilteringScore.evaluate(ix, filter);
 +        assertTrue(score.isKeyMatch());
 +
 +        filter = Filter.filterFor(StorableTestBasic.class,
 +                                  "id = ? & intProp = ? & doubleProp = ?");
 +        score = FilteringScore.evaluate(ix, filter);
 +        assertTrue(score.isKeyMatch());
 +    }
 +}
 diff --git a/src/test/java/com/amazon/carbonado/qe/TestIndexedQueryAnalyzer.java b/src/test/java/com/amazon/carbonado/qe/TestIndexedQueryAnalyzer.java new file mode 100644 index 0000000..6b1919a --- /dev/null +++ b/src/test/java/com/amazon/carbonado/qe/TestIndexedQueryAnalyzer.java @@ -0,0 +1,460 @@ +/*
 + * Copyright 2006 Amazon Technologies, Inc. or its affiliates.
 + * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
 + * of Amazon Technologies, Inc. or its affiliates.  All rights reserved.
 + *
 + * Licensed under the Apache License, Version 2.0 (the "License");
 + * you may not use this file except in compliance with the License.
 + * You may obtain a copy of the License at
 + *
 + *     http://www.apache.org/licenses/LICENSE-2.0
 + *
 + * Unless required by applicable law or agreed to in writing, software
 + * distributed under the License is distributed on an "AS IS" BASIS,
 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 + * See the License for the specific language governing permissions and
 + * limitations under the License.
 + */
 +
 +package com.amazon.carbonado.qe;
 +
 +import java.util.Arrays;
 +import java.util.Collection;
 +import java.util.Collections;
 +import java.util.List;
 +
 +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;
 +import com.amazon.carbonado.filter.FilterValues;
 +import com.amazon.carbonado.filter.PropertyFilter;
 +
 +import com.amazon.carbonado.repo.toy.ToyRepository;
 +
 +import com.amazon.carbonado.stored.Address;
 +import com.amazon.carbonado.stored.Order;
 +import com.amazon.carbonado.stored.Shipment;
 +import com.amazon.carbonado.stored.Shipper;
 +import com.amazon.carbonado.stored.StorableTestBasic;
 +
 +/**
 + * 
 + *
 + * @author Brian S O'Neill
 + */
 +public class TestIndexedQueryAnalyzer extends TestCase {
 +    public static void main(String[] args) {
 +        junit.textui.TestRunner.run(suite());
 +    }
 +
 +    public static TestSuite suite() {
 +        return new TestSuite(TestIndexedQueryAnalyzer.class);
 +    }
 +
 +    static <S extends Storable> StorableIndex<S> makeIndex(Class<S> type, String... props) {
 +        return TestOrderingScore.makeIndex(type, props);
 +    }
 +
 +    public TestIndexedQueryAnalyzer(String name) {
 +        super(name);
 +    }
 +
 +    // Note: these tests don't perform exhaustive tests to find the best index, as those tests
 +    // are performed by TestFilteringScore and TestOrderingScore.
 +
 +    public void testFullScan() throws Exception {
 +        IndexedQueryAnalyzer iqa = new IndexedQueryAnalyzer(Address.class, RepoAccess.INSTANCE);
 +        Filter<Address> filter = Filter.filterFor(Address.class, "addressZip = ?");
 +        filter = filter.bind();
 +        IndexedQueryAnalyzer.Result result = iqa.analyze(filter, null);
 +
 +        assertFalse(result.handlesAnything());
 +        assertEquals(filter, result.getCompositeScore().getFilteringScore().getRemainderFilter());
 +        assertEquals(makeIndex(Address.class, "addressID"), result.getLocalIndex());
 +        assertEquals(null, result.getForeignIndex());
 +        assertEquals(null, result.getForeignProperty());
 +    }
 +
 +    public void testIndexScan() throws Exception {
 +        IndexedQueryAnalyzer iqa = new IndexedQueryAnalyzer(Address.class, RepoAccess.INSTANCE);
 +        Filter<Address> filter = Filter.filterFor(Address.class, "addressID = ?");
 +        filter = filter.bind();
 +        IndexedQueryAnalyzer.Result result = iqa.analyze(filter, null);
 +
 +        assertTrue(result.handlesAnything());
 +        assertEquals(filter, result.getCompositeScore().getFilteringScore().getIdentityFilter());
 +        assertEquals(makeIndex(Address.class, "addressID"), result.getLocalIndex());
 +        assertEquals(null, result.getForeignIndex());
 +        assertEquals(null, result.getForeignProperty());
 +    }
 +
 +    public void testBasic() throws Exception {
 +        IndexedQueryAnalyzer iqa = new IndexedQueryAnalyzer(Shipment.class, RepoAccess.INSTANCE);
 +        Filter<Shipment> filter = Filter.filterFor(Shipment.class, "shipmentID = ?");
 +        filter = filter.bind();
 +        IndexedQueryAnalyzer.Result result = iqa.analyze(filter, null);
 +
 +        assertTrue(result.handlesAnything());
 +        assertEquals(filter, result.getCompositeScore().getFilteringScore().getIdentityFilter());
 +        assertEquals(makeIndex(Shipment.class, "shipmentID"), result.getLocalIndex());
 +        assertEquals(null, result.getForeignIndex());
 +        assertEquals(null, result.getForeignProperty());
 +
 +        filter = Filter.filterFor(Shipment.class, "orderID = ?");
 +        filter = filter.bind();
 +        result = iqa.analyze(filter, null);
 +
 +        assertTrue(result.handlesAnything());
 +        assertEquals(filter, result.getCompositeScore().getFilteringScore().getIdentityFilter());
 +        assertEquals(makeIndex(Shipment.class, "orderID", "shipmentNotes"),
 +                     result.getLocalIndex());
 +        assertEquals(null, result.getForeignIndex());
 +        assertEquals(null, result.getForeignProperty());
 +
 +        filter = Filter.filterFor(Shipment.class, "orderID > ?");
 +        filter = filter.bind();
 +        result = iqa.analyze(filter, null);
 +
 +        assertTrue(result.handlesAnything());
 +        assertTrue(result.getCompositeScore().getFilteringScore().hasRangeStart());
 +        assertFalse(result.getCompositeScore().getFilteringScore().hasRangeEnd());
 +        List<PropertyFilter<Shipment>> rangeFilters =
 +            result.getCompositeScore().getFilteringScore().getRangeStartFilters();
 +        assertEquals(1, rangeFilters.size());
 +        assertEquals(filter, rangeFilters.get(0));
 +        assertEquals(makeIndex(Shipment.class, "orderID", "shipmentNotes"),
 +                     result.getLocalIndex());
 +        assertEquals(null, result.getForeignIndex());
 +        assertEquals(null, result.getForeignProperty());
 +    }
 +
 +    public void testSimpleJoin() throws Exception {
 +        IndexedQueryAnalyzer iqa = new IndexedQueryAnalyzer(Shipment.class, RepoAccess.INSTANCE);
 +        Filter<Shipment> filter = Filter.filterFor(Shipment.class, "order.orderTotal >= ?");
 +        filter = filter.bind();
 +        IndexedQueryAnalyzer.Result result = iqa.analyze(filter, null);
 +
 +        assertTrue(result.handlesAnything());
 +        assertTrue(result.getCompositeScore().getFilteringScore().hasRangeStart());
 +        assertEquals(null, result.getLocalIndex());
 +        assertEquals(makeIndex(Order.class, "orderTotal"), result.getForeignIndex());
 +        assertEquals("order", result.getForeignProperty().toString());
 +    }
 +
 +    public void testJoinPriority() throws Exception {
 +        // Selects foreign index because filter score is better.
 +
 +        IndexedQueryAnalyzer iqa = new IndexedQueryAnalyzer(Shipment.class, RepoAccess.INSTANCE);
 +        Filter<Shipment> filter = Filter.filterFor
 +            (Shipment.class, "shipmentNotes = ? & order.orderTotal >= ?");
 +        filter = filter.bind();
 +        IndexedQueryAnalyzer.Result result = iqa.analyze(filter, null);
 +
 +        assertTrue(result.handlesAnything());
 +        assertTrue(result.getCompositeScore().getFilteringScore().hasRangeStart());
 +        assertEquals(Filter.filterFor(Shipment.class, "shipmentNotes = ?").bind(),
 +                     result.getCompositeScore().getFilteringScore().getRemainderFilter());
 +        assertEquals(null, result.getLocalIndex());
 +        assertEquals(makeIndex(Order.class, "orderTotal"), result.getForeignIndex());
 +        assertEquals("order", result.getForeignProperty().toString());
 +    }
 +
 +    public void testJoinNonPriority() throws Exception {
 +        // Selects local index because filter score is just as good and local
 +        // indexes are preferred.
 +
 +        IndexedQueryAnalyzer iqa = new IndexedQueryAnalyzer(Shipment.class, RepoAccess.INSTANCE);
 +        Filter<Shipment> filter = Filter.filterFor
 +            (Shipment.class, "orderID >= ? & order.orderTotal >= ?");
 +        filter = filter.bind();
 +        IndexedQueryAnalyzer.Result result = iqa.analyze(filter, null);
 +
 +        assertTrue(result.handlesAnything());
 +        assertTrue(result.getCompositeScore().getFilteringScore().hasRangeStart());
 +        assertEquals(Filter.filterFor(Shipment.class, "order.orderTotal >= ?").bind(),
 +                     result.getCompositeScore().getFilteringScore().getRemainderFilter());
 +        assertEquals(makeIndex(Shipment.class, "orderID", "shipmentNotes"),
 +                     result.getLocalIndex());
 +        assertEquals(null, result.getForeignIndex());
 +        assertEquals(null, result.getForeignProperty());
 +    }
 +
 +    public void testChainedJoin() throws Exception {
 +        IndexedQueryAnalyzer iqa = new IndexedQueryAnalyzer(Shipment.class, RepoAccess.INSTANCE);
 +        Filter<Shipment> filter = Filter.filterFor
 +            (Shipment.class, "order.address.addressState = ?");
 +        filter = filter.bind();
 +        IndexedQueryAnalyzer.Result result = iqa.analyze(filter, null);
 +
 +        assertTrue(result.handlesAnything());
 +        assertEquals(filter, result.getCompositeScore().getFilteringScore().getIdentityFilter());
 +        assertEquals(null, result.getLocalIndex());
 +        assertEquals(makeIndex(Address.class, "addressState", "-addressCity"),
 +                     result.getForeignIndex());
 +        assertEquals("order.address", result.getForeignProperty().toString());
 +    }
 +
 +    public void testChainedJoinExecutor() throws Exception {
 +        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<Shipment>.Result result = iqa.analyze(filter, null);
 +
 +        assertTrue(result.handlesAnything());
 +        assertEquals(Filter.filterFor(Shipment.class, "order.address.addressState = ?").bind(),
 +                     result.getCompositeScore().getFilteringScore().getIdentityFilter());
 +        assertEquals(Filter.filterFor(Shipment.class, "order.address.addressZip = ?").bind(),
 +                     result.getCompositeScore().getFilteringScore().getRemainderFilter());
 +        assertEquals(null, result.getLocalIndex());
 +        assertEquals(makeIndex(Address.class, "addressState", "-addressCity"),
 +                     result.getForeignIndex());
 +        assertEquals("order.address", result.getForeignProperty().toString());
 +
 +        QueryExecutor<Shipment> joinExec = JoinedQueryExecutor.build
 +            (RepoAccess.INSTANCE,
 +             result.getForeignProperty(), result.getFilter(), result.getOrdering());
 +
 +        FilterValues<Shipment> fv = values.with("WA").with("12345");
 +
 +        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 =
 +            "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";
 +
 +        assertEquals(expected, plan);
 +
 +        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());
 +
 +        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 {
 +        static final RepoAccess INSTANCE = new RepoAccess();
 +
 +        public Repository getRootRepository() {
 +            throw new UnsupportedOperationException();
 +        }
 +
 +        public <S extends Storable> StorageAccess<S> storageAccessFor(Class<S> type) {
 +            return new StoreAccess<S>(type);
 +        }
 +    }
 +
 +    /**
 +     * Partially implemented StorageAccess which only supplies information
 +     * about indexes.
 +     */
 +    static 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;
 +        }
 +
 +        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", "-addressCity")
 +                };
 +            } else if (Order.class.isAssignableFrom(mType)) {
 +                indexes = new StorableIndex[] {
 +                    makeIndex(mType, "orderID"),
 +                    makeIndex(mType, "orderTotal"),
 +                    makeIndex(mType, "addressID", "orderTotal")
 +                };
 +            } else if (Shipment.class.isAssignableFrom(mType)) {
 +                indexes = new StorableIndex[] {
 +                    makeIndex(mType, "shipmentID"),
 +                    makeIndex(mType, "orderID", "shipmentNotes"),
 +                };
 +            } else if (Shipper.class.isAssignableFrom(mType)) {
 +                indexes = new StorableIndex[] {
 +                    makeIndex(mType, "shipperID")
 +                };
 +            } else if (StorableTestBasic.class.isAssignableFrom(mType)) {
 +                indexes = new StorableIndex[] {
 +                    makeIndex(mType, "id").unique(true).clustered(true),
 +                    makeIndex(mType, "stringProp", "doubleProp").unique(true),
 +                    makeIndex(mType, "-stringProp", "-intProp", "~id").unique(true),
 +                    makeIndex(mType, "+intProp", "stringProp", "~id").unique(true),
 +                    makeIndex(mType, "-doubleProp", "+longProp", "~id").unique(true),
 +                };
 +            } else {
 +                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();
 +        }
 +    }
 +}
 diff --git a/src/test/java/com/amazon/carbonado/qe/TestIndexedQueryExecutor.java b/src/test/java/com/amazon/carbonado/qe/TestIndexedQueryExecutor.java new file mode 100644 index 0000000..a68fbac --- /dev/null +++ b/src/test/java/com/amazon/carbonado/qe/TestIndexedQueryExecutor.java @@ -0,0 +1,754 @@ +/*
 + * Copyright 2006 Amazon Technologies, Inc. or its affiliates.
 + * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
 + * of Amazon Technologies, Inc. or its affiliates.  All rights reserved.
 + *
 + * Licensed under the Apache License, Version 2.0 (the "License");
 + * you may not use this file except in compliance with the License.
 + * You may obtain a copy of the License at
 + *
 + *     http://www.apache.org/licenses/LICENSE-2.0
 + *
 + * Unless required by applicable law or agreed to in writing, software
 + * distributed under the License is distributed on an "AS IS" BASIS,
 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 + * See the License for the specific language governing permissions and
 + * limitations under the License.
 + */
 +
 +package com.amazon.carbonado.qe;
 +
 +import java.util.Arrays;
 +import java.util.Collection;
 +import java.util.Collections;
 +import java.util.List;
 +
 +import junit.framework.TestCase;
 +import junit.framework.TestSuite;
 +
 +import com.amazon.carbonado.Cursor;
 +import com.amazon.carbonado.Storable;
 +
 +import com.amazon.carbonado.cursor.IteratorCursor;
 +
 +import com.amazon.carbonado.filter.Filter;
 +import com.amazon.carbonado.filter.FilterValues;
 +
 +import com.amazon.carbonado.info.OrderedProperty;
 +import com.amazon.carbonado.info.StorableIndex;
 +
 +import com.amazon.carbonado.stored.StorableTestBasic;
 +
 +/**
 + *
 + *
 + * @author Brian S O'Neill
 + */
 +public class TestIndexedQueryExecutor extends TestCase {
 +    public static void main(String[] args) {
 +        junit.textui.TestRunner.run(suite());
 +    }
 +
 +    public static TestSuite suite() {
 +        return new TestSuite(TestIndexedQueryExecutor.class);
 +    }
 +
 +    static <S extends Storable> StorableIndex<S> makeIndex(Class<S> type, String... props) {
 +        return TestOrderingScore.makeIndex(type, props);
 +    }
 +
 +    static <S extends Storable> OrderingList<S> makeOrdering(Class<S> type, String... props) {
 +        return TestOrderingScore.makeOrdering(type, props);
 +    }
 +
 +    public TestIndexedQueryExecutor(String name) {
 +        super(name);
 +    }
 +
 +    public void testIdentityMatch() throws Exception {
 +        StorableIndex<StorableTestBasic> index =
 +            makeIndex(StorableTestBasic.class, "id", "-intProp", "doubleProp");
 +
 +        Filter<StorableTestBasic> filter =
 +            Filter.filterFor(StorableTestBasic.class, "id = ?");
 +        FilterValues<StorableTestBasic> values = filter.initialFilterValues();
 +        filter = values.getFilter();
 +
 +        CompositeScore<StorableTestBasic> score = CompositeScore.evaluate(index, filter, null);
 +
 +        Mock<StorableTestBasic> executor = new Mock<StorableTestBasic>(index, score);
 +
 +        executor.fetch(values.with(100));
 +
 +        assertEquals(1, executor.mIdentityValues.length);
 +        assertEquals(100, executor.mIdentityValues[0]);
 +        assertEquals(BoundaryType.OPEN, executor.mRangeStartBoundary);
 +        assertEquals(null, executor.mRangeStartValue);
 +        assertEquals(BoundaryType.OPEN, executor.mRangeEndBoundary);
 +        assertEquals(null, executor.mRangeEndValue);
 +        assertFalse(executor.mReverseRange);
 +        assertFalse(executor.mReverseOrder);
 +
 +        ///////
 +        filter = Filter.filterFor(StorableTestBasic.class, "id = ? & intProp = ?");
 +        values = filter.initialFilterValues();
 +        filter = values.getFilter();
 +
 +        score = CompositeScore.evaluate(index, filter, null);
 +
 +        executor = new Mock<StorableTestBasic>(index, score);
 +
 +        executor.fetch(values.with(100).with(5));
 +
 +        assertEquals(2, executor.mIdentityValues.length);
 +        assertEquals(100, executor.mIdentityValues[0]);
 +        assertEquals(5, executor.mIdentityValues[1]);
 +        assertEquals(BoundaryType.OPEN, executor.mRangeStartBoundary);
 +        assertEquals(null, executor.mRangeStartValue);
 +        assertEquals(BoundaryType.OPEN, executor.mRangeEndBoundary);
 +        assertEquals(null, executor.mRangeEndValue);
 +        assertFalse(executor.mReverseRange);
 +        assertFalse(executor.mReverseOrder);
 +
 +        ///////
 +        filter = Filter.filterFor(StorableTestBasic.class, "intProp = ?");
 +        values = filter.initialFilterValues();
 +        filter = values.getFilter();
 +
 +        score = CompositeScore.evaluate(index, filter, null);
 +
 +        executor = new Mock<StorableTestBasic>(index, score);
 +
 +        executor.fetch(values.with(200));
 +
 +        assertEquals(null, executor.mIdentityValues);
 +        assertEquals(BoundaryType.OPEN, executor.mRangeStartBoundary);
 +        assertEquals(null, executor.mRangeStartValue);
 +        assertEquals(BoundaryType.OPEN, executor.mRangeEndBoundary);
 +        assertEquals(null, executor.mRangeEndValue);
 +        assertFalse(executor.mReverseRange);
 +        assertFalse(executor.mReverseOrder);
 +    }
 +
 +    public void testOpenRangeStartMatch() throws Exception {
 +        StorableIndex<StorableTestBasic> index = makeIndex(StorableTestBasic.class, "intProp");
 +
 +        Filter<StorableTestBasic> filter =
 +            Filter.filterFor(StorableTestBasic.class, "intProp > ?");
 +        FilterValues<StorableTestBasic> values = filter.initialFilterValues();
 +        filter = values.getFilter();
 +
 +        CompositeScore<StorableTestBasic> score = CompositeScore.evaluate(index, filter, null);
 +
 +        Mock<StorableTestBasic> executor = new Mock<StorableTestBasic>(index, score);
 +
 +        executor.fetch(values.with(100));
 +
 +        assertEquals(null, executor.mIdentityValues);
 +        assertEquals(BoundaryType.EXCLUSIVE, executor.mRangeStartBoundary);
 +        assertEquals(100, executor.mRangeStartValue);
 +        assertEquals(BoundaryType.OPEN, executor.mRangeEndBoundary);
 +        assertEquals(null, executor.mRangeEndValue);
 +        assertFalse(executor.mReverseRange);
 +        assertFalse(executor.mReverseOrder);
 +
 +        ///////
 +        filter = Filter.filterFor(StorableTestBasic.class, "intProp >= ?");
 +        values = filter.initialFilterValues();
 +        filter = values.getFilter();
 +
 +        score = CompositeScore.evaluate(index, filter, null);
 +
 +        executor = new Mock<StorableTestBasic>(index, score);
 +
 +        executor.fetch(values.with(100));
 +
 +        assertEquals(null, executor.mIdentityValues);
 +        assertEquals(BoundaryType.INCLUSIVE, executor.mRangeStartBoundary);
 +        assertEquals(100, executor.mRangeStartValue);
 +        assertEquals(BoundaryType.OPEN, executor.mRangeEndBoundary);
 +        assertEquals(null, executor.mRangeEndValue);
 +        assertFalse(executor.mReverseRange);
 +        assertFalse(executor.mReverseOrder);
 +
 +        ///////
 +        filter = Filter.filterFor(StorableTestBasic.class, "intProp > ? & intProp > ?");
 +        values = filter.initialFilterValues();
 +        filter = values.getFilter();
 +
 +        score = CompositeScore.evaluate(index, filter, null);
 +
 +        executor = new Mock<StorableTestBasic>(index, score);
 +
 +        executor.fetch(values.with(10).with(30));
 +
 +        assertEquals(null, executor.mIdentityValues);
 +        assertEquals(BoundaryType.EXCLUSIVE, executor.mRangeStartBoundary);
 +        assertEquals(30, executor.mRangeStartValue);
 +        assertEquals(BoundaryType.OPEN, executor.mRangeEndBoundary);
 +        assertEquals(null, executor.mRangeEndValue);
 +        assertFalse(executor.mReverseRange);
 +        assertFalse(executor.mReverseOrder);
 +
 +        ///////
 +        filter = Filter.filterFor(StorableTestBasic.class, "intProp >= ? & intProp > ?");
 +        values = filter.initialFilterValues();
 +        filter = values.getFilter();
 +
 +        score = CompositeScore.evaluate(index, filter, null);
 +
 +        executor = new Mock<StorableTestBasic>(index, score);
 +
 +        executor.fetch(values.with(10).with(30));
 +
 +        assertEquals(null, executor.mIdentityValues);
 +        assertEquals(BoundaryType.EXCLUSIVE, executor.mRangeStartBoundary);
 +        assertEquals(30, executor.mRangeStartValue);
 +        assertEquals(BoundaryType.OPEN, executor.mRangeEndBoundary);
 +        assertEquals(null, executor.mRangeEndValue);
 +        assertFalse(executor.mReverseRange);
 +        assertFalse(executor.mReverseOrder);
 +
 +        ///////
 +        filter = Filter.filterFor(StorableTestBasic.class, "intProp >= ? & intProp > ?");
 +        values = filter.initialFilterValues();
 +        filter = values.getFilter();
 +
 +        score = CompositeScore.evaluate(index, filter, null);
 +
 +        executor = new Mock<StorableTestBasic>(index, score);
 +
 +        executor.fetch(values.with(10).with(10));
 +
 +        assertEquals(null, executor.mIdentityValues);
 +        assertEquals(BoundaryType.EXCLUSIVE, executor.mRangeStartBoundary);
 +        assertEquals(10, executor.mRangeStartValue);
 +        assertEquals(BoundaryType.OPEN, executor.mRangeEndBoundary);
 +        assertEquals(null, executor.mRangeEndValue);
 +        assertFalse(executor.mReverseRange);
 +        assertFalse(executor.mReverseOrder);
 +
 +        ///////
 +        filter = Filter.filterFor(StorableTestBasic.class, "intProp >= ? & intProp > ?");
 +        values = filter.initialFilterValues();
 +        filter = values.getFilter();
 +
 +        score = CompositeScore.evaluate(index, filter, null);
 +
 +        executor = new Mock<StorableTestBasic>(index, score);
 +
 +        executor.fetch(values.with(30).with(10));
 +
 +        assertEquals(null, executor.mIdentityValues);
 +        assertEquals(BoundaryType.INCLUSIVE, executor.mRangeStartBoundary);
 +        assertEquals(30, executor.mRangeStartValue);
 +        assertEquals(BoundaryType.OPEN, executor.mRangeEndBoundary);
 +        assertEquals(null, executor.mRangeEndValue);
 +        assertFalse(executor.mReverseRange);
 +        assertFalse(executor.mReverseOrder);
 +
 +        ///////
 +        filter = Filter.filterFor(StorableTestBasic.class, "intProp > ? & intProp > ?");
 +        values = filter.initialFilterValues();
 +        filter = values.getFilter();
 +
 +        score = CompositeScore.evaluate(index, filter,
 +                                        makeOrdering(StorableTestBasic.class, "-intProp"));
 +
 +        executor = new Mock<StorableTestBasic>(index, score);
 +
 +        executor.fetch(values.with(100).with(30));
 +
 +        assertEquals(null, executor.mIdentityValues);
 +        assertEquals(BoundaryType.EXCLUSIVE, executor.mRangeStartBoundary);
 +        assertEquals(100, executor.mRangeStartValue);
 +        assertEquals(BoundaryType.OPEN, executor.mRangeEndBoundary);
 +        assertEquals(null, executor.mRangeEndValue);
 +        assertFalse(executor.mReverseRange);
 +        assertTrue(executor.mReverseOrder);
 +
 +        ///////
 +        index = makeIndex(StorableTestBasic.class, "-intProp");
 +
 +        filter = Filter.filterFor(StorableTestBasic.class, "intProp > ?");
 +        values = filter.initialFilterValues();
 +        filter = values.getFilter();
 +
 +        score = CompositeScore.evaluate(index, filter, null);
 +
 +        executor = new Mock<StorableTestBasic>(index, score);
 +
 +        executor.fetch(values.with(100));
 +
 +        assertEquals(null, executor.mIdentityValues);
 +        assertEquals(BoundaryType.EXCLUSIVE, executor.mRangeStartBoundary);
 +        assertEquals(100, executor.mRangeStartValue);
 +        assertEquals(BoundaryType.OPEN, executor.mRangeEndBoundary);
 +        assertEquals(null, executor.mRangeEndValue);
 +        assertTrue(executor.mReverseRange);
 +        assertFalse(executor.mReverseOrder);
 +
 +        ///////
 +        filter = Filter.filterFor(StorableTestBasic.class, "intProp > ?");
 +        values = filter.initialFilterValues();
 +        filter = values.getFilter();
 +
 +        score = CompositeScore.evaluate(index, filter,
 +                                        makeOrdering(StorableTestBasic.class, "-intProp"));
 +
 +        executor = new Mock<StorableTestBasic>(index, score);
 +
 +        executor.fetch(values.with(100));
 +
 +        assertEquals(null, executor.mIdentityValues);
 +        assertEquals(BoundaryType.EXCLUSIVE, executor.mRangeStartBoundary);
 +        assertEquals(100, executor.mRangeStartValue);
 +        assertEquals(BoundaryType.OPEN, executor.mRangeEndBoundary);
 +        assertEquals(null, executor.mRangeEndValue);
 +        assertTrue(executor.mReverseRange);
 +        assertFalse(executor.mReverseOrder);
 +
 +        ///////
 +        filter = Filter.filterFor(StorableTestBasic.class, "intProp > ?");
 +        values = filter.initialFilterValues();
 +        filter = values.getFilter();
 +
 +        score = CompositeScore.evaluate(index, filter,
 +                                        makeOrdering(StorableTestBasic.class, "intProp"));
 +
 +        executor = new Mock<StorableTestBasic>(index, score);
 +
 +        executor.fetch(values.with(100));
 +
 +        assertEquals(null, executor.mIdentityValues);
 +        assertEquals(BoundaryType.EXCLUSIVE, executor.mRangeStartBoundary);
 +        assertEquals(100, executor.mRangeStartValue);
 +        assertEquals(BoundaryType.OPEN, executor.mRangeEndBoundary);
 +        assertEquals(null, executor.mRangeEndValue);
 +        assertTrue(executor.mReverseRange);
 +        assertTrue(executor.mReverseOrder);
 +    }
 +
 +    public void testOpenRangeEndMatch() throws Exception {
 +        StorableIndex<StorableTestBasic> index = makeIndex(StorableTestBasic.class, "intProp");
 +
 +        Filter<StorableTestBasic> filter =
 +            Filter.filterFor(StorableTestBasic.class, "intProp < ?");
 +        FilterValues<StorableTestBasic> values = filter.initialFilterValues();
 +        filter = values.getFilter();
 +
 +        CompositeScore<StorableTestBasic> score = CompositeScore.evaluate(index, filter, null);
 +
 +        Mock<StorableTestBasic> executor = new Mock<StorableTestBasic>(index, score);
 +
 +        executor.fetch(values.with(100));
 +
 +        assertEquals(null, executor.mIdentityValues);
 +        assertEquals(BoundaryType.OPEN, executor.mRangeStartBoundary);
 +        assertEquals(null, executor.mRangeStartValue);
 +        assertEquals(BoundaryType.EXCLUSIVE, executor.mRangeEndBoundary);
 +        assertEquals(100, executor.mRangeEndValue);
 +        assertFalse(executor.mReverseRange);
 +        assertFalse(executor.mReverseOrder);
 +
 +        ///////
 +        filter = Filter.filterFor(StorableTestBasic.class, "intProp <= ?");
 +        values = filter.initialFilterValues();
 +        filter = values.getFilter();
 +
 +        score = CompositeScore.evaluate(index, filter, null);
 +
 +        executor = new Mock<StorableTestBasic>(index, score);
 +
 +        executor.fetch(values.with(100));
 +
 +        assertEquals(null, executor.mIdentityValues);
 +        assertEquals(BoundaryType.OPEN, executor.mRangeStartBoundary);
 +        assertEquals(null, executor.mRangeStartValue);
 +        assertEquals(BoundaryType.INCLUSIVE, executor.mRangeEndBoundary);
 +        assertEquals(100, executor.mRangeEndValue);
 +        assertFalse(executor.mReverseRange);
 +        assertFalse(executor.mReverseOrder);
 +
 +        ///////
 +        filter = Filter.filterFor(StorableTestBasic.class, "intProp < ? & intProp < ?");
 +        values = filter.initialFilterValues();
 +        filter = values.getFilter();
 +
 +        score = CompositeScore.evaluate(index, filter, null);
 +
 +        executor = new Mock<StorableTestBasic>(index, score);
 +
 +        executor.fetch(values.with(10).with(30));
 +
 +        assertEquals(null, executor.mIdentityValues);
 +        assertEquals(BoundaryType.OPEN, executor.mRangeStartBoundary);
 +        assertEquals(null, executor.mRangeStartValue);
 +        assertEquals(BoundaryType.EXCLUSIVE, executor.mRangeEndBoundary);
 +        assertEquals(10, executor.mRangeEndValue);
 +        assertFalse(executor.mReverseRange);
 +        assertFalse(executor.mReverseOrder);
 +
 +        ///////
 +        filter = Filter.filterFor(StorableTestBasic.class, "intProp <= ? & intProp < ?");
 +        values = filter.initialFilterValues();
 +        filter = values.getFilter();
 +
 +        score = CompositeScore.evaluate(index, filter, null);
 +
 +        executor = new Mock<StorableTestBasic>(index, score);
 +
 +        executor.fetch(values.with(10).with(30));
 +
 +        assertEquals(null, executor.mIdentityValues);
 +        assertEquals(BoundaryType.OPEN, executor.mRangeStartBoundary);
 +        assertEquals(null, executor.mRangeStartValue);
 +        assertEquals(BoundaryType.INCLUSIVE, executor.mRangeEndBoundary);
 +        assertEquals(10, executor.mRangeEndValue);
 +        assertFalse(executor.mReverseRange);
 +        assertFalse(executor.mReverseOrder);
 +
 +        ///////
 +        filter = Filter.filterFor(StorableTestBasic.class, "intProp <= ? & intProp < ?");
 +        values = filter.initialFilterValues();
 +        filter = values.getFilter();
 +
 +        score = CompositeScore.evaluate(index, filter, null);
 +
 +        executor = new Mock<StorableTestBasic>(index, score);
 +
 +        executor.fetch(values.with(10).with(10));
 +
 +        assertEquals(null, executor.mIdentityValues);
 +        assertEquals(BoundaryType.OPEN, executor.mRangeStartBoundary);
 +        assertEquals(null, executor.mRangeStartValue);
 +        assertEquals(BoundaryType.EXCLUSIVE, executor.mRangeEndBoundary);
 +        assertEquals(10, executor.mRangeEndValue);
 +        assertFalse(executor.mReverseRange);
 +        assertFalse(executor.mReverseOrder);
 +
 +        ///////
 +        filter = Filter.filterFor(StorableTestBasic.class, "intProp <= ? & intProp < ?");
 +        values = filter.initialFilterValues();
 +        filter = values.getFilter();
 +
 +        score = CompositeScore.evaluate(index, filter, null);
 +
 +        executor = new Mock<StorableTestBasic>(index, score);
 +
 +        executor.fetch(values.with(30).with(10));
 +
 +        assertEquals(null, executor.mIdentityValues);
 +        assertEquals(BoundaryType.OPEN, executor.mRangeStartBoundary);
 +        assertEquals(null, executor.mRangeStartValue);
 +        assertEquals(BoundaryType.EXCLUSIVE, executor.mRangeEndBoundary);
 +        assertEquals(10, executor.mRangeEndValue);
 +        assertFalse(executor.mReverseRange);
 +        assertFalse(executor.mReverseOrder);
 +
 +        ///////
 +        filter = Filter.filterFor(StorableTestBasic.class, "intProp < ? & intProp < ?");
 +        values = filter.initialFilterValues();
 +        filter = values.getFilter();
 +
 +        score = CompositeScore.evaluate(index, filter,
 +                                        makeOrdering(StorableTestBasic.class, "-intProp"));
 +
 +        executor = new Mock<StorableTestBasic>(index, score);
 +
 +        executor.fetch(values.with(100).with(30));
 +
 +        assertEquals(null, executor.mIdentityValues);
 +        assertEquals(BoundaryType.OPEN, executor.mRangeStartBoundary);
 +        assertEquals(null, executor.mRangeStartValue);
 +        assertEquals(BoundaryType.EXCLUSIVE, executor.mRangeEndBoundary);
 +        assertEquals(30, executor.mRangeEndValue);
 +        assertFalse(executor.mReverseRange);
 +        assertTrue(executor.mReverseOrder);
 +
 +        ///////
 +        index = makeIndex(StorableTestBasic.class, "-intProp");
 +
 +        filter = Filter.filterFor(StorableTestBasic.class, "intProp < ?");
 +        values = filter.initialFilterValues();
 +        filter = values.getFilter();
 +
 +        score = CompositeScore.evaluate(index, filter, null);
 +
 +        executor = new Mock<StorableTestBasic>(index, score);
 +
 +        executor.fetch(values.with(100));
 +
 +        assertEquals(null, executor.mIdentityValues);
 +        assertEquals(BoundaryType.OPEN, executor.mRangeStartBoundary);
 +        assertEquals(null, executor.mRangeStartValue);
 +        assertEquals(BoundaryType.EXCLUSIVE, executor.mRangeEndBoundary);
 +        assertEquals(100, executor.mRangeEndValue);
 +        assertTrue(executor.mReverseRange);
 +        assertFalse(executor.mReverseOrder);
 +
 +        ///////
 +        filter = Filter.filterFor(StorableTestBasic.class, "intProp < ?");
 +        values = filter.initialFilterValues();
 +        filter = values.getFilter();
 +
 +        score = CompositeScore.evaluate(index, filter,
 +                                        makeOrdering(StorableTestBasic.class, "-intProp"));
 +
 +        executor = new Mock<StorableTestBasic>(index, score);
 +
 +        executor.fetch(values.with(100));
 +
 +        assertEquals(null, executor.mIdentityValues);
 +        assertEquals(BoundaryType.OPEN, executor.mRangeStartBoundary);
 +        assertEquals(null, executor.mRangeStartValue);
 +        assertEquals(BoundaryType.EXCLUSIVE, executor.mRangeEndBoundary);
 +        assertEquals(100, executor.mRangeEndValue);
 +        assertTrue(executor.mReverseRange);
 +        assertFalse(executor.mReverseOrder);
 +
 +        ///////
 +        filter = Filter.filterFor(StorableTestBasic.class, "intProp < ?");
 +        values = filter.initialFilterValues();
 +        filter = values.getFilter();
 +
 +        score = CompositeScore.evaluate(index, filter,
 +                                        makeOrdering(StorableTestBasic.class, "intProp"));
 +
 +        executor = new Mock<StorableTestBasic>(index, score);
 +
 +        executor.fetch(values.with(100));
 +
 +        assertEquals(null, executor.mIdentityValues);
 +        assertEquals(BoundaryType.OPEN, executor.mRangeStartBoundary);
 +        assertEquals(null, executor.mRangeStartValue);
 +        assertEquals(BoundaryType.EXCLUSIVE, executor.mRangeEndBoundary);
 +        assertEquals(100, executor.mRangeEndValue);
 +        assertTrue(executor.mReverseRange);
 +        assertTrue(executor.mReverseOrder);
 +    }
 +
 +    public void testClosedRangeMatch() throws Exception {
 +        // These tests are not as exhaustive, as I don't expect the combination
 +        // of start and end ranges to interfere with each other.
 +
 +        StorableIndex<StorableTestBasic> index = makeIndex(StorableTestBasic.class, "intProp");
 +
 +        Filter<StorableTestBasic> filter =
 +            Filter.filterFor(StorableTestBasic.class, "intProp > ? & intProp < ?");
 +        FilterValues<StorableTestBasic> values = filter.initialFilterValues();
 +        filter = values.getFilter();
 +
 +        CompositeScore<StorableTestBasic> score = CompositeScore.evaluate(index, filter, null);
 +
 +        Mock<StorableTestBasic> executor = new Mock<StorableTestBasic>(index, score);
 +
 +        executor.fetch(values.with(100).with(200));
 +
 +        assertEquals(null, executor.mIdentityValues);
 +        assertEquals(BoundaryType.EXCLUSIVE, executor.mRangeStartBoundary);
 +        assertEquals(100, executor.mRangeStartValue);
 +        assertEquals(BoundaryType.EXCLUSIVE, executor.mRangeEndBoundary);
 +        assertEquals(200, executor.mRangeEndValue);
 +        assertFalse(executor.mReverseRange);
 +        assertFalse(executor.mReverseOrder);
 +
 +        ///////
 +        filter = Filter.filterFor(StorableTestBasic.class, "intProp >= ? & intProp <= ?");
 +        values = filter.initialFilterValues();
 +        filter = values.getFilter();
 +
 +        score = CompositeScore.evaluate(index, filter, null);
 +
 +        executor = new Mock<StorableTestBasic>(index, score);
 +
 +        executor.fetch(values.with(100).with(10));
 +
 +        assertEquals(null, executor.mIdentityValues);
 +        assertEquals(BoundaryType.INCLUSIVE, executor.mRangeStartBoundary);
 +        assertEquals(100, executor.mRangeStartValue);
 +        assertEquals(BoundaryType.INCLUSIVE, executor.mRangeEndBoundary);
 +        assertEquals(10, executor.mRangeEndValue);
 +        assertFalse(executor.mReverseRange);
 +        assertFalse(executor.mReverseOrder);
 +
 +        ///////
 +        filter = Filter.filterFor(StorableTestBasic.class,
 +                                  "intProp > ? & intProp < ? & intProp > ?");
 +        values = filter.initialFilterValues();
 +        filter = values.getFilter();
 +
 +        score = CompositeScore.evaluate(index, filter, null);
 +
 +        executor = new Mock<StorableTestBasic>(index, score);
 +
 +        executor.fetch(values.with(10).with(100).with(30));
 +
 +        assertEquals(null, executor.mIdentityValues);
 +        assertEquals(BoundaryType.EXCLUSIVE, executor.mRangeStartBoundary);
 +        assertEquals(30, executor.mRangeStartValue);
 +        assertEquals(BoundaryType.EXCLUSIVE, executor.mRangeEndBoundary);
 +        assertEquals(100, executor.mRangeEndValue);
 +        assertFalse(executor.mReverseRange);
 +        assertFalse(executor.mReverseOrder);
 +    }
 +
 +    public void testIdentityAndRangeMatch() throws Exception {
 +        // These tests are not as exhaustive, as I don't expect the combination
 +        // of identity and ranges to interfere with each other.
 +
 +        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 = ? & doubleProp > ? & doubleProp < ?");
 +        values = filter.initialFilterValues();
 +        filter = values.getFilter();
 +
 +        score = CompositeScore.evaluate(index, filter, null);
 +
 +        executor = new Mock<StorableTestBasic>(index, score);
 +
 +        executor.fetch(values.with(3).with(56.5).with(200.2));
 +
 +        assertEquals(3, executor.mIdentityValues[0]);
 +        assertEquals(BoundaryType.EXCLUSIVE, executor.mRangeStartBoundary);
 +        assertEquals(56.5, executor.mRangeStartValue);
 +        assertEquals(BoundaryType.EXCLUSIVE, executor.mRangeEndBoundary);
 +        assertEquals(200.2, executor.mRangeEndValue);
 +        assertTrue(executor.mReverseRange);
 +        assertFalse(executor.mReverseOrder);
 +
 +        ///////
 +        score = CompositeScore.evaluate(index, filter,
 +                                        makeOrdering(StorableTestBasic.class, "doubleProp"));
 +
 +        executor = new Mock<StorableTestBasic>(index, score);
 +
 +        executor.fetch(values.with(3).with(56.5).with(200.2));
 +
 +        assertEquals(3, executor.mIdentityValues[0]);
 +        assertEquals(BoundaryType.EXCLUSIVE, executor.mRangeStartBoundary);
 +        assertEquals(56.5, executor.mRangeStartValue);
 +        assertEquals(BoundaryType.EXCLUSIVE, executor.mRangeEndBoundary);
 +        assertEquals(200.2, executor.mRangeEndValue);
 +        assertTrue(executor.mReverseRange);
 +        assertTrue(executor.mReverseOrder);
 +
 +        ///////
 +        score = CompositeScore.evaluate(index, filter,
 +                                        makeOrdering(StorableTestBasic.class, "stringProp"));
 +
 +        executor = new Mock<StorableTestBasic>(index, score);
 +
 +        executor.fetch(values.with(3).with(56.5).with(200.2));
 +
 +        assertEquals(3, executor.mIdentityValues[0]);
 +        assertEquals(BoundaryType.EXCLUSIVE, executor.mRangeStartBoundary);
 +        assertEquals(56.5, executor.mRangeStartValue);
 +        assertEquals(BoundaryType.EXCLUSIVE, executor.mRangeEndBoundary);
 +        assertEquals(200.2, executor.mRangeEndValue);
 +        assertTrue(executor.mReverseRange);
 +        assertFalse(executor.mReverseOrder);
 +
 +        ///////
 +        filter = Filter.filterFor(StorableTestBasic.class,
 +                                  "intProp = ? & doubleProp = ? & stringProp < ?");
 +        values = filter.initialFilterValues();
 +        filter = values.getFilter();
 +
 +        score = CompositeScore.evaluate(index, filter,
 +                                        makeOrdering(StorableTestBasic.class, "-stringProp"));
 +
 +        executor = new Mock<StorableTestBasic>(index, score);
 +
 +        executor.fetch(values.with(3).with(56.5).with("foo"));
 +
 +        assertEquals(3, executor.mIdentityValues[0]);
 +        assertEquals(56.5, executor.mIdentityValues[1]);
 +        assertEquals(BoundaryType.OPEN, executor.mRangeStartBoundary);
 +        assertEquals(null, executor.mRangeStartValue);
 +        assertEquals(BoundaryType.EXCLUSIVE, executor.mRangeEndBoundary);
 +        assertEquals("foo", executor.mRangeEndValue);
 +        assertFalse(executor.mReverseRange);
 +        assertTrue(executor.mReverseOrder);
 +
 +        assertEquals(values.getFilter(), executor.getFilter());
 +        List<OrderedProperty<StorableTestBasic>> expectedOrdering =
 +            makeOrdering(StorableTestBasic.class, "-stringProp");
 +        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>
 +        implements IndexedQueryExecutor.Support<S>
 +    {
 +        Object[] mIdentityValues;
 +        BoundaryType mRangeStartBoundary;
 +        Object mRangeStartValue;
 +        BoundaryType mRangeEndBoundary;
 +        Object mRangeEndValue;
 +        boolean mReverseRange;
 +        boolean mReverseOrder;
 +
 +        public Mock(StorableIndex<S> index, CompositeScore<S> score) {
 +            super(null, index, score);
 +        }
 +
 +        public Cursor<S> fetchSubset(StorableIndex<S> index,
 +                                     Object[] identityValues,
 +                                     BoundaryType rangeStartBoundary,
 +                                     Object rangeStartValue,
 +                                     BoundaryType rangeEndBoundary,
 +                                     Object rangeEndValue,
 +                                     boolean reverseRange,
 +                                     boolean 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 new file mode 100644 index 0000000..e1abca9 --- /dev/null +++ b/src/test/java/com/amazon/carbonado/qe/TestJoinedQueryExecutor.java @@ -0,0 +1,298 @@ +/*
 + * Copyright 2006 Amazon Technologies, Inc. or its affiliates.
 + * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
 + * of Amazon Technologies, Inc. or its affiliates.  All rights reserved.
 + *
 + * Licensed under the Apache License, Version 2.0 (the "License");
 + * you may not use this file except in compliance with the License.
 + * You may obtain a copy of the License at
 + *
 + *     http://www.apache.org/licenses/LICENSE-2.0
 + *
 + * Unless required by applicable law or agreed to in writing, software
 + * distributed under the License is distributed on an "AS IS" BASIS,
 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 + * See the License for the specific language governing permissions and
 + * limitations under the License.
 + */
 +
 +package com.amazon.carbonado.qe;
 +
 +import java.util.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;
 +
 +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;
 +
 +import com.amazon.carbonado.repo.toy.ToyRepository;
 +
 +import com.amazon.carbonado.stored.UserAddress;
 +import com.amazon.carbonado.stored.UserInfo;
 +
 +/**
 + * 
 + *
 + * @author Brian S O'Neill
 + */
 +public class TestJoinedQueryExecutor extends TestQueryExecutor {
 +    public static void main(String[] args) {
 +        junit.textui.TestRunner.run(suite());
 +    }
 +
 +    public static TestSuite suite() {
 +        return new TestSuite(TestJoinedQueryExecutor.class);
 +    }
 +
 +    private Repository mRepository;
 +
 +    protected void setUp() throws Exception {
 +        super.setUp();
 +        mRepository = new ToyRepository();
 +    }
 +
 +    protected void tearDown() throws Exception {
 +        super.tearDown();
 +    }
 +
 +    public void testJoin() throws Exception {
 +        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);
 +
 +        //System.out.println();
 +        //userExecutor.printPlan(System.out, 0, null);
 +
 +        assertEquals("address.state = ?", userExecutor.getFilter().toString());
 +        assertEquals("+address.country", userExecutor.getOrdering().get(0).toString());
 +
 +        // Create some addresses
 +        Storage<UserAddress> addressStorage = mRepository.storageFor(UserAddress.class);
 +        UserAddress addr = addressStorage.prepare();
 +        addr.setAddressID(1);
 +        addr.setLine1("4567, 123 Street");
 +        addr.setCity("Springfield");
 +        addr.setState("IL");
 +        addr.setCountry("USA");
 +        addr.insert();
 +
 +        addr = addressStorage.prepare();
 +        addr.setAddressID(2);
 +        addr.setLine1("1111 Apt 1, 1st Ave");
 +        addr.setCity("Somewhere");
 +        addr.setState("AA");
 +        addr.setCountry("USA");
 +        addr.setNeighborAddressID(1);
 +        addr.insert();
 +
 +        addr = addressStorage.prepare();
 +        addr.setAddressID(3);
 +        addr.setLine1("9999");
 +        addr.setCity("Chicago");
 +        addr.setState("IL");
 +        addr.setCountry("USA");
 +        addr.insert();
 +
 +        // Create some users
 +        Storage<UserInfo> userStorage = mRepository.storageFor(UserInfo.class);
 +        UserInfo user = userStorage.prepare();
 +        user.setUserID(1);
 +        user.setStateID(1);
 +        user.setFirstName("Bob");
 +        user.setLastName("Loblaw");
 +        user.setAddressID(1);
 +        user.insert();
 +
 +        user = userStorage.prepare();
 +        user.setUserID(2);
 +        user.setStateID(1);
 +        user.setFirstName("Deb");
 +        user.setLastName("Loblaw");
 +        user.setAddressID(1);
 +        user.insert();
 +
 +        user = userStorage.prepare();
 +        user.setUserID(3);
 +        user.setStateID(1);
 +        user.setFirstName("No");
 +        user.setLastName("Body");
 +        user.setAddressID(2);
 +        user.insert();
 +
 +        // Now do a basic join, finding everyone in IL.
 +
 +        FilterValues<UserInfo> values = Filter
 +            .filterFor(UserInfo.class, "address.state = ?").initialFilterValues().with("IL");
 +
 +        Cursor<UserInfo> cursor = userExecutor.fetch(values);
 +        assertTrue(cursor.hasNext());
 +        assertEquals(1, cursor.next().getUserID());
 +        assertEquals(2, cursor.next().getUserID());
 +        assertFalse(cursor.hasNext());
 +        cursor.close();
 +
 +        assertEquals(2L, userExecutor.count(values));
 +
 +        // Now do a multi join, finding everyone with an explicit neighbor in IL.
 +
 +        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());
 +
 +        values = Filter
 +            .filterFor(UserInfo.class, "address.neighbor.state = ?")
 +            .initialFilterValues().with("IL");
 +
 +        cursor = userExecutor.fetch(values);
 +        assertTrue(cursor.hasNext());
 +        assertEquals(3, cursor.next().getUserID());
 +        assertFalse(cursor.hasNext());
 +        cursor.close();
 +
 +        assertEquals(1L, userExecutor.count(values));
 +    }
 +
 +    class RepoAccess implements RepositoryAccess {
 +        public Repository getRootRepository() {
 +            return mRepository;
 +        }
 +
 +        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;
 +        }
 +
 +        public QueryExecutor<S> executor(Filter<S> filter, OrderingList<S> ordering)
 +            throws RepositoryException
 +        {
 +            Storage<S> storage = mRepository.storageFor(mType);
 +
 +            QueryExecutor<S> exec = new FullScanQueryExecutor<S>
 +                (new ScanQuerySupport<S>(storage.query()));
 +
 +            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 = 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> {
 +        private final Query<S> mQuery;
 +
 +        ScanQuerySupport(Query<S> query) {
 +            mQuery = query;
 +        }
 +
 +        public Class<S> getStorableType() {
 +            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 new file mode 100644 index 0000000..8af7a2e --- /dev/null +++ b/src/test/java/com/amazon/carbonado/qe/TestOrderingList.java @@ -0,0 +1,225 @@ +/*
 + * Copyright 2006 Amazon Technologies, Inc. or its affiliates.
 + * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
 + * of Amazon Technologies, Inc. or its affiliates.  All rights reserved.
 + *
 + * Licensed under the Apache License, Version 2.0 (the "License");
 + * you may not use this file except in compliance with the License.
 + * You may obtain a copy of the License at
 + *
 + *     http://www.apache.org/licenses/LICENSE-2.0
 + *
 + * Unless required by applicable law or agreed to in writing, software
 + * distributed under the License is distributed on an "AS IS" BASIS,
 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 + * See the License for the specific language governing permissions and
 + * limitations under the License.
 + */
 +
 +package com.amazon.carbonado.qe;
 +
 +import java.util.List;
 +
 +import junit.framework.TestCase;
 +import junit.framework.TestSuite;
 +
 +import com.amazon.carbonado.info.OrderedProperty;
 +
 +import com.amazon.carbonado.stored.StorableTestBasic;
 +
 +/**
 + * 
 + *
 + * @author Brian S O'Neill
 + */
 +public class TestOrderingList extends TestCase {
 +    public static void main(String[] args) {
 +        junit.textui.TestRunner.run(suite());
 +    }
 +
 +    public static TestSuite suite() {
 +        return new TestSuite(TestOrderingList.class);
 +    }
 +
 +    public TestOrderingList(String name) {
 +        super(name);
 +    }
 +
 +    public void testEmpty() throws Exception {
 +        assertEquals(0, OrderingList.get(StorableTestBasic.class).size());
 +    }
 +
 +    public void testSingle() throws Exception {
 +        List<OrderedProperty<StorableTestBasic>> list_1 =
 +            OrderingList.get(StorableTestBasic.class, "date");
 +
 +        assertEquals(1, list_1.size());
 +        assertEquals("+date", list_1.get(0).toString());
 +
 +        List<OrderedProperty<StorableTestBasic>> list_2 =
 +            OrderingList.get(StorableTestBasic.class, "+date");
 +
 +        assertEquals(1, list_2.size());
 +        assertEquals("+date", list_2.get(0).toString());
 +        assertEquals(list_1, list_2);
 +        assertTrue(list_1 == list_2);
 +
 +        List<OrderedProperty<StorableTestBasic>> list_3 =
 +            OrderingList.get(StorableTestBasic.class, "-date");
 +
 +        assertEquals(1, list_3.size());
 +        assertEquals("-date", list_3.get(0).toString());
 +        assertFalse(list_2.equals(list_3));
 +        assertFalse(list_2 == list_3);
 +    }
 +
 +    public void testDouble() throws Exception {
 +        List<OrderedProperty<StorableTestBasic>> list_1 =
 +            OrderingList.get(StorableTestBasic.class, "date", "intProp");
 +
 +        assertEquals(2, list_1.size());
 +        assertEquals("+date", list_1.get(0).toString());
 +        assertEquals("+intProp", list_1.get(1).toString());
 +
 +        List<OrderedProperty<StorableTestBasic>> list_2 =
 +            OrderingList.get(StorableTestBasic.class, "+date", "+intProp");
 +
 +        assertEquals(2, list_2.size());
 +        assertEquals("+date", list_2.get(0).toString());
 +        assertEquals("+intProp", list_2.get(1).toString());
 +        assertEquals(list_1, list_2);
 +        assertTrue(list_1 == list_2);
 +
 +        List<OrderedProperty<StorableTestBasic>> list_3 =
 +            OrderingList.get(StorableTestBasic.class, "-date", "-intProp");
 +
 +        assertEquals(2, list_3.size());
 +        assertEquals("-date", list_3.get(0).toString());
 +        assertEquals("-intProp", list_3.get(1).toString());
 +        assertFalse(list_2.equals(list_3));
 +        assertFalse(list_2 == list_3);
 +    }
 +
 +    public void testIllegal() throws Exception {
 +        try {
 +            OrderingList.get(StorableTestBasic.class, "foo");
 +            fail();
 +        } catch (IllegalArgumentException e) {
 +        }
 +    }
 +
 +    public void testImmutable() throws Exception {
 +        List<OrderedProperty<StorableTestBasic>> list =
 +            OrderingList.get(StorableTestBasic.class, "~date");
 +        try {
 +            list.set(0, list.get(0).reverse());
 +            fail();
 +        } catch (UnsupportedOperationException e) {
 +        }
 +    }
 +
 +    public void testConcatList() throws Exception {
 +        OrderingList<StorableTestBasic> list_1 =
 +            OrderingList.get(StorableTestBasic.class, "date", "-intProp", "~stringProp");
 +
 +        OrderingList<StorableTestBasic> list_2 =
 +            OrderingList.get(StorableTestBasic.class, "longProp", "doubleProp");
 +
 +        OrderingList<StorableTestBasic> list_3 = list_1.concat(list_2);
 +
 +        assertEquals(5, list_3.size());
 +        assertEquals("+date", list_3.get(0).toString());
 +        assertEquals("-intProp", list_3.get(1).toString());
 +        assertEquals("~stringProp", list_3.get(2).toString());
 +        assertEquals("+longProp", list_3.get(3).toString());
 +        assertEquals("+doubleProp", list_3.get(4).toString());
 +
 +        OrderingList<StorableTestBasic> list_4 =
 +            OrderingList.get(StorableTestBasic.class,
 +                             "+date", "-intProp", "~stringProp", "longProp", "+doubleProp");
 +
 +        assertEquals(list_3, list_4);
 +        assertTrue(list_3 == list_4);
 +    }
 +
 +    public void testReverseDirections() throws Exception {
 +        OrderingList<StorableTestBasic> list_1 =
 +            OrderingList.get(StorableTestBasic.class, "date", "-intProp", "~stringProp");
 +
 +        list_1 = list_1.reverseDirections();
 +
 +        assertEquals(3, list_1.size());
 +        assertEquals("-date", list_1.get(0).toString());
 +        assertEquals("+intProp", list_1.get(1).toString());
 +        assertEquals("~stringProp", list_1.get(2).toString());
 +
 +        OrderingList<StorableTestBasic> list_2 =
 +            OrderingList.get(StorableTestBasic.class, "-date", "intProp", "~stringProp");
 +
 +        assertEquals(list_1, list_2);
 +        assertTrue(list_1 == list_2);
 +    }
 +
 +    public void testReplace() throws Exception {
 +        OrderingList<StorableTestBasic> list_1 =
 +            OrderingList.get(StorableTestBasic.class, "date", "-intProp", "~stringProp");
 +
 +        OrderedProperty<StorableTestBasic> op_0 = list_1.get(0);
 +        OrderedProperty<StorableTestBasic> op_1 = list_1.get(1);
 +        OrderedProperty<StorableTestBasic> op_2 = list_1.get(2);
 +
 +        list_1 = list_1.replace(0, op_1);
 +        list_1 = list_1.replace(1, op_2);
 +        list_1 = list_1.replace(2, op_0);
 +
 +        assertEquals(3, list_1.size());
 +        assertEquals("-intProp", list_1.get(0).toString());
 +        assertEquals("~stringProp", list_1.get(1).toString());
 +        assertEquals("+date", list_1.get(2).toString());
 +
 +        OrderingList<StorableTestBasic> list_2 =
 +            OrderingList.get(StorableTestBasic.class, "-intProp", "~stringProp", "+date");
 +
 +        assertEquals(list_1, list_2);
 +        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");
 +
 +        OrderedProperty<StorableTestBasic>[] array = list.asArray();
 +
 +        assertEquals(3, array.length);
 +        assertEquals("+date", array[0].toString());
 +        assertEquals("+intProp", array[1].toString());
 +        assertEquals("+stringProp", array[2].toString());
 +    }
 +
 +    public void testAsStringArray() throws Exception {
 +        OrderingList<StorableTestBasic> list =
 +            OrderingList.get(StorableTestBasic.class, "date", "intProp", "stringProp");
 +
 +        String[] array = list.asStringArray();
 +
 +        assertEquals(3, array.length);
 +        assertEquals("+date", array[0]);
 +        assertEquals("+intProp", array[1]);
 +        assertEquals("+stringProp", array[2]);
 +    }
 +}
 diff --git a/src/test/java/com/amazon/carbonado/qe/TestOrderingScore.java b/src/test/java/com/amazon/carbonado/qe/TestOrderingScore.java new file mode 100644 index 0000000..785ef69 --- /dev/null +++ b/src/test/java/com/amazon/carbonado/qe/TestOrderingScore.java @@ -0,0 +1,673 @@ +/*
 + * Copyright 2006 Amazon Technologies, Inc. or its affiliates.
 + * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
 + * of Amazon Technologies, Inc. or its affiliates.  All rights reserved.
 + *
 + * Licensed under the Apache License, Version 2.0 (the "License");
 + * you may not use this file except in compliance with the License.
 + * You may obtain a copy of the License at
 + *
 + *     http://www.apache.org/licenses/LICENSE-2.0
 + *
 + * Unless required by applicable law or agreed to in writing, software
 + * distributed under the License is distributed on an "AS IS" BASIS,
 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 + * See the License for the specific language governing permissions and
 + * limitations under the License.
 + */
 +
 +package com.amazon.carbonado.qe;
 +
 +import java.util.ArrayList;
 +import java.util.Collections;
 +import java.util.Comparator;
 +import java.util.List;
 +
 +import junit.framework.TestCase;
 +import junit.framework.TestSuite;
 +
 +import com.amazon.carbonado.Storable;
 +
 +import com.amazon.carbonado.info.Direction;
 +import static 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;
 +
 +import com.amazon.carbonado.filter.Filter;
 +
 +import com.amazon.carbonado.stored.StorableTestBasic;
 +
 +/**
 + *
 + *
 + * @author Brian S O'Neill
 + */
 +public class TestOrderingScore extends TestCase {
 +    public static void main(String[] args) {
 +        junit.textui.TestRunner.run(suite());
 +    }
 +
 +    public static TestSuite suite() {
 +        return new TestSuite(TestOrderingScore.class);
 +    }
 +
 +    static <S extends Storable> StorableIndex<S> makeIndex(Class<S> type, String... props) {
 +        return new StorableIndex<S>(makeOrdering(type, props).asArray(), UNSPECIFIED);
 +    }
 +
 +    static <S extends Storable> OrderingList<S> makeOrdering(Class<S> type, String... props) {
 +        return OrderingList.get(type, props);
 +    }
 +
 +    public TestOrderingScore(String name) {
 +        super(name);
 +    }
 +
 +    public void testEmpty() throws Exception {
 +        StorableIndex<StorableTestBasic> ix = makeIndex(StorableTestBasic.class, "id");
 +
 +        OrderingScore score = OrderingScore.evaluate(ix, null, null);
 +
 +        assertEquals(0, score.getHandledCount());
 +        assertEquals(0, score.getRemainderCount());
 +        assertEquals(false, score.shouldReverseOrder());
 +    }
 +
 +    public void testOneProp() throws Exception {
 +        StorableIndex<StorableTestBasic> ix;
 +        OrderingList<StorableTestBasic> ops;
 +        OrderingScore<StorableTestBasic> score;
 +
 +        /////////////
 +        ix = makeIndex(StorableTestBasic.class, "id");
 +
 +        ops = makeOrdering(StorableTestBasic.class, "id");
 +        score = OrderingScore.evaluate(ix, null, ops);
 +
 +        assertEquals(1, score.getHandledCount());
 +        assertEquals("+id", score.getHandledOrdering().get(0).toString());
 +        assertEquals(0, score.getRemainderCount());
 +        assertEquals(false, score.shouldReverseOrder());
 +
 +        ops = makeOrdering(StorableTestBasic.class, "+id");
 +        score = OrderingScore.evaluate(ix, null, ops);
 +
 +        assertEquals(1, score.getHandledCount());
 +        assertEquals("+id", score.getHandledOrdering().get(0).toString());
 +        assertEquals(0, score.getRemainderCount());
 +        assertEquals(false, score.shouldReverseOrder());
 +
 +        ops = makeOrdering(StorableTestBasic.class, "-id");
 +        score = OrderingScore.evaluate(ix, null, ops);
 +
 +        assertEquals(1, score.getHandledCount());
 +        assertEquals("-id", score.getHandledOrdering().get(0).toString());
 +        assertEquals(0, score.getRemainderCount());
 +        assertEquals(true, score.shouldReverseOrder());
 +
 +        /////////////
 +        ix = makeIndex(StorableTestBasic.class, "+id");
 +
 +        ops = makeOrdering(StorableTestBasic.class, "id");
 +        score = OrderingScore.evaluate(ix, null, ops);
 +
 +        assertEquals(1, score.getHandledCount());
 +        assertEquals("+id", score.getHandledOrdering().get(0).toString());
 +        assertEquals(0, score.getRemainderCount());
 +        assertEquals(false, score.shouldReverseOrder());
 +
 +        ops = makeOrdering(StorableTestBasic.class, "+id");
 +        score = OrderingScore.evaluate(ix, null, ops);
 +
 +        assertEquals(1, score.getHandledCount());
 +        assertEquals("+id", score.getHandledOrdering().get(0).toString());
 +        assertEquals(0, score.getRemainderCount());
 +        assertEquals(false, score.shouldReverseOrder());
 +
 +        ops = makeOrdering(StorableTestBasic.class, "-id");
 +        score = OrderingScore.evaluate(ix, null, ops);
 +
 +        assertEquals(1, score.getHandledCount());
 +        assertEquals("-id", score.getHandledOrdering().get(0).toString());
 +        assertEquals(0, score.getRemainderCount());
 +        assertEquals(true, score.shouldReverseOrder());
 +
 +        /////////////
 +        ix = makeIndex(StorableTestBasic.class, "-id");
 +
 +        ops = makeOrdering(StorableTestBasic.class, "id");
 +        score = OrderingScore.evaluate(ix, null, ops);
 +
 +        assertEquals(1, score.getHandledCount());
 +        assertEquals("+id", score.getHandledOrdering().get(0).toString());
 +        assertEquals(0, score.getRemainderCount());
 +        assertEquals(true, score.shouldReverseOrder());
 +
 +        ops = makeOrdering(StorableTestBasic.class, "+id");
 +        score = OrderingScore.evaluate(ix, null, ops);
 +
 +        assertEquals(1, score.getHandledCount());
 +        assertEquals("+id", score.getHandledOrdering().get(0).toString());
 +        assertEquals(0, score.getRemainderCount());
 +        assertEquals(true, score.shouldReverseOrder());
 +
 +        ops = makeOrdering(StorableTestBasic.class, "-id");
 +        score = OrderingScore.evaluate(ix, null, ops);
 +
 +        assertEquals(1, score.getHandledCount());
 +        assertEquals("-id", score.getHandledOrdering().get(0).toString());
 +        assertEquals(0, score.getRemainderCount());
 +        assertEquals(false, score.shouldReverseOrder());
 +
 +        /////////////
 +        ix = makeIndex(StorableTestBasic.class, "intProp");
 +
 +        ops = makeOrdering(StorableTestBasic.class, "id");
 +        score = OrderingScore.evaluate(ix, null, ops);
 +
 +        assertEquals(0, score.getHandledCount());
 +        assertEquals(1, score.getRemainderCount());
 +        assertEquals("+id", score.getRemainderOrdering().get(0).toString());
 +        assertEquals(false, score.shouldReverseOrder());
 +    }
 +
 +    public void testMultipleProps() throws Exception {
 +        final StorableIndex<StorableTestBasic> ix;
 +        OrderingList<StorableTestBasic> ops;
 +        OrderingScore<StorableTestBasic> score;
 +
 +        ix = makeIndex(StorableTestBasic.class, "id", "intProp");
 +
 +        ops = makeOrdering(StorableTestBasic.class, "id");
 +        score = OrderingScore.evaluate(ix, null, ops);
 +        assertEquals(1, score.getHandledCount());
 +        assertEquals("+id", score.getHandledOrdering().get(0).toString());
 +        assertEquals(0, score.getRemainderCount());
 +        assertEquals(false, score.shouldReverseOrder());
 +
 +        ops = makeOrdering(StorableTestBasic.class, "-id");
 +        score = OrderingScore.evaluate(ix, null, ops);
 +        assertEquals(1, score.getHandledCount());
 +        assertEquals("-id", score.getHandledOrdering().get(0).toString());
 +        assertEquals(0, score.getRemainderCount());
 +        assertEquals(true, score.shouldReverseOrder());
 +
 +        ops = makeOrdering(StorableTestBasic.class, "id", "intProp");
 +        score = OrderingScore.evaluate(ix, null, ops);
 +        assertEquals(2, score.getHandledCount());
 +        assertEquals("+id", score.getHandledOrdering().get(0).toString());
 +        assertEquals("+intProp", score.getHandledOrdering().get(1).toString());
 +        assertEquals(0, score.getRemainderCount());
 +        assertEquals(false, score.shouldReverseOrder());
 +
 +        ops = makeOrdering(StorableTestBasic.class, "-id", "-intProp");
 +        score = OrderingScore.evaluate(ix, null, ops);
 +        assertEquals(2, score.getHandledCount());
 +        assertEquals("-id", score.getHandledOrdering().get(0).toString());
 +        assertEquals("-intProp", score.getHandledOrdering().get(1).toString());
 +        assertEquals(0, score.getRemainderCount());
 +        assertEquals(true, score.shouldReverseOrder());
 +
 +        ops = makeOrdering(StorableTestBasic.class, "-id", "+intProp");
 +        score = OrderingScore.evaluate(ix, null, ops);
 +        assertEquals(1, score.getHandledCount());
 +        assertEquals("-id", score.getHandledOrdering().get(0).toString());
 +        assertEquals(1, score.getRemainderCount());
 +        assertEquals("+intProp", score.getRemainderOrdering().get(0).toString());
 +        assertEquals(true, score.shouldReverseOrder());
 +
 +        ops = makeOrdering(StorableTestBasic.class, "+id", "-intProp");
 +        score = OrderingScore.evaluate(ix, null, ops);
 +        assertEquals(1, score.getHandledCount());
 +        assertEquals("+id", score.getHandledOrdering().get(0).toString());
 +        assertEquals(1, score.getRemainderCount());
 +        assertEquals("-intProp", score.getRemainderOrdering().get(0).toString());
 +        assertEquals(false, score.shouldReverseOrder());
 +
 +        ops = makeOrdering(StorableTestBasic.class, "intProp");
 +        score = OrderingScore.evaluate(ix, null, ops);
 +        assertEquals(0, score.getHandledCount());
 +        assertEquals(1, score.getRemainderCount());
 +        assertEquals("+intProp", score.getRemainderOrdering().get(0).toString());
 +        assertEquals(false, score.shouldReverseOrder());
 +
 +        // Gap is allowed if identity filtered.
 +
 +        Filter<StorableTestBasic> filter;
 +
 +        filter = Filter.filterFor(StorableTestBasic.class, "id = ?");
 +
 +        ops = makeOrdering(StorableTestBasic.class, "intProp");
 +        score = OrderingScore.evaluate(ix, filter, ops);
 +        assertEquals(1, score.getHandledCount());
 +        assertEquals("+intProp", score.getHandledOrdering().get(0).toString());
 +        assertEquals(0, score.getRemainderCount());
 +        assertEquals(false, score.shouldReverseOrder());
 +
 +        ops = makeOrdering(StorableTestBasic.class, "-intProp");
 +        score = OrderingScore.evaluate(ix, filter, ops);
 +        assertEquals(1, score.getHandledCount());
 +        assertEquals("-intProp", score.getHandledOrdering().get(0).toString());
 +        assertEquals(0, score.getRemainderCount());
 +        assertEquals(true, score.shouldReverseOrder());
 +
 +        ops = makeOrdering(StorableTestBasic.class, "intProp", "id");
 +        score = OrderingScore.evaluate(ix, filter, ops);
 +        assertEquals(1, score.getHandledCount());
 +        assertEquals("+intProp", score.getHandledOrdering().get(0).toString());
 +        // Since "id" is filtered, don't count as remainder.
 +        assertEquals(0, score.getRemainderCount());
 +        assertEquals(false, score.shouldReverseOrder());
 +
 +        ops = makeOrdering(StorableTestBasic.class, "-intProp", "id");
 +        score = OrderingScore.evaluate(ix, filter, ops);
 +        assertEquals(1, score.getHandledCount());
 +        assertEquals("-intProp", score.getHandledOrdering().get(0).toString());
 +        // Since "id" is filtered, don't count as remainder.
 +        assertEquals(0, score.getRemainderCount());
 +        assertEquals(true, score.shouldReverseOrder());
 +
 +        ops = makeOrdering(StorableTestBasic.class, "intProp", "doubleProp");
 +        score = OrderingScore.evaluate(ix, filter, ops);
 +        assertEquals(1, score.getHandledCount());
 +        assertEquals("+intProp", score.getHandledOrdering().get(0).toString());
 +        assertEquals(1, score.getRemainderCount());
 +        assertEquals("+doubleProp", score.getRemainderOrdering().get(0).toString());
 +        assertEquals(false, score.shouldReverseOrder());
 +
 +        ops = makeOrdering(StorableTestBasic.class, "intProp", "-doubleProp");
 +        score = OrderingScore.evaluate(ix, filter, ops);
 +        assertEquals(1, score.getHandledCount());
 +        assertEquals("+intProp", score.getHandledOrdering().get(0).toString());
 +        assertEquals(1, score.getRemainderCount());
 +        assertEquals("-doubleProp", score.getRemainderOrdering().get(0).toString());
 +        assertEquals(false, score.shouldReverseOrder());
 +
 +        filter = Filter.filterFor(StorableTestBasic.class, "id > ? & doubleProp = ?");
 +
 +        ops = makeOrdering(StorableTestBasic.class, "intProp");
 +        score = OrderingScore.evaluate(ix, filter, ops);
 +        assertEquals(0, score.getHandledCount());
 +        assertEquals(1, score.getRemainderCount());
 +        assertEquals("+intProp", score.getRemainderOrdering().get(0).toString());
 +        assertEquals(false, score.shouldReverseOrder());
 +
 +        ops = makeOrdering(StorableTestBasic.class, "doubleProp");
 +        score = OrderingScore.evaluate(ix, filter, ops);
 +        assertEquals(0, score.getHandledCount());
 +        assertEquals(0, score.getRemainderCount());
 +        assertEquals(false, score.shouldReverseOrder());
 +
 +        filter = Filter.filterFor(StorableTestBasic.class, "doubleProp = ? & id = ?");
 +
 +        ops = makeOrdering(StorableTestBasic.class, "doubleProp", "-intProp");
 +        score = OrderingScore.evaluate(ix, filter, ops);
 +        assertEquals(1, score.getHandledCount());
 +        assertEquals("-intProp", score.getHandledOrdering().get(0).toString());
 +        assertEquals(0, score.getRemainderCount());
 +        assertEquals(true, score.shouldReverseOrder());
 +    }
 +
 +    public void testMidGap() throws Exception {
 +        final StorableIndex<StorableTestBasic> ix;
 +        OrderingList<StorableTestBasic> ops;
 +        OrderingScore score;
 +        Filter<StorableTestBasic> filter;
 +
 +        ix = makeIndex(StorableTestBasic.class, "id", "intProp", "doubleProp", "-stringProp");
 +
 +        ops = makeOrdering(StorableTestBasic.class, "id", "intProp", "doubleProp", "-stringProp");
 +        score = OrderingScore.evaluate(ix, null, ops);
 +        assertEquals(4, score.getHandledCount());
 +        assertEquals("+id", score.getHandledOrdering().get(0).toString());
 +        assertEquals("+intProp", score.getHandledOrdering().get(1).toString());
 +        assertEquals("+doubleProp", score.getHandledOrdering().get(2).toString());
 +        assertEquals("-stringProp", score.getHandledOrdering().get(3).toString());
 +        assertEquals(0, score.getRemainderCount());
 +        assertEquals(false, score.shouldReverseOrder());
 +
 +        // Now ignore mid index properties, creating a gap.
 +
 +        ops = makeOrdering(StorableTestBasic.class, "id", "-stringProp");
 +        score = OrderingScore.evaluate(ix, null, ops);
 +        assertEquals(1, score.getHandledCount());
 +        assertEquals("+id", score.getHandledOrdering().get(0).toString());
 +        assertEquals(1, score.getRemainderCount());
 +        assertEquals("-stringProp", score.getRemainderOrdering().get(0).toString());
 +        assertEquals(false, score.shouldReverseOrder());
 +
 +        // Gap can be bridged if property is filtered out. First test with
 +        // incomplete bridges.
 +
 +        filter = Filter.filterFor(StorableTestBasic.class, "doubleProp = ? & intProp > ?");
 +
 +        ops = makeOrdering(StorableTestBasic.class, "id", "-stringProp");
 +        score = OrderingScore.evaluate(ix, filter, ops);
 +        assertEquals(1, score.getHandledCount());
 +        assertEquals("+id", score.getHandledOrdering().get(0).toString());
 +        assertEquals(1, score.getRemainderCount());
 +        assertEquals("-stringProp", score.getRemainderOrdering().get(0).toString());
 +        assertEquals(false, score.shouldReverseOrder());
 +
 +        filter = Filter.filterFor(StorableTestBasic.class, "doubleProp >= ? & intProp = ?");
 +
 +        ops = makeOrdering(StorableTestBasic.class, "id", "-stringProp");
 +        score = OrderingScore.evaluate(ix, filter, ops);
 +        assertEquals(1, score.getHandledCount());
 +        assertEquals("+id", score.getHandledOrdering().get(0).toString());
 +        assertEquals(1, score.getRemainderCount());
 +        assertEquals("-stringProp", score.getRemainderOrdering().get(0).toString());
 +        assertEquals(false, score.shouldReverseOrder());
 +
 +        // Now a complete bridge.
 +
 +        filter = Filter.filterFor(StorableTestBasic.class, "doubleProp = ? & intProp = ?");
 +
 +        ops = makeOrdering(StorableTestBasic.class, "id", "-stringProp");
 +        score = OrderingScore.evaluate(ix, filter, ops);
 +        assertEquals(2, score.getHandledCount());
 +        assertEquals("+id", score.getHandledOrdering().get(0).toString());
 +        assertEquals("-stringProp", score.getHandledOrdering().get(1).toString());
 +        assertEquals(0, score.getRemainderCount());
 +        assertEquals(false, score.shouldReverseOrder());
 +
 +        // Again in reverse.
 +
 +        ops = makeOrdering(StorableTestBasic.class, "-id", "stringProp");
 +        score = OrderingScore.evaluate(ix, filter, ops);
 +        assertEquals(2, score.getHandledCount());
 +        assertEquals("-id", score.getHandledOrdering().get(0).toString());
 +        assertEquals("+stringProp", score.getHandledOrdering().get(1).toString());
 +        assertEquals(0, score.getRemainderCount());
 +        assertEquals(true, score.shouldReverseOrder());
 +
 +        // Failed double reverse.
 +
 +        ops = makeOrdering(StorableTestBasic.class, "-id", "-stringProp");
 +        score = OrderingScore.evaluate(ix, filter, ops);
 +        assertEquals(1, score.getHandledCount());
 +        assertEquals("-id", score.getHandledOrdering().get(0).toString());
 +        assertEquals(1, score.getRemainderCount());
 +        assertEquals("-stringProp", score.getRemainderOrdering().get(0).toString());
 +        assertEquals(true, score.shouldReverseOrder());
 +    }
 +
 +    public void testComparator() throws Exception {
 +        StorableIndex<StorableTestBasic> ix_1, ix_2;
 +        OrderingList<StorableTestBasic> ops;
 +        OrderingScore score_1, score_2;
 +        Filter<StorableTestBasic> filter;
 +        Comparator<OrderingScore<?>> comp = OrderingScore.fullComparator();
 +
 +        ix_1 = makeIndex(StorableTestBasic.class, "id", "intProp", "doubleProp", "-stringProp");
 +        ix_2 = makeIndex(StorableTestBasic.class, "intProp", "doubleProp", "id");
 +
 +        ops = makeOrdering(StorableTestBasic.class, "-id", "-intProp");
 +        score_1 = OrderingScore.evaluate(ix_1, null, ops);
 +        score_2 = OrderingScore.evaluate(ix_2, null, ops);
 +
 +        assertEquals(-1, comp.compare(score_1, score_2));
 +        assertEquals(1, comp.compare(score_2, score_1));
 +
 +        filter = Filter.filterFor(StorableTestBasic.class, "id = ?");
 +        score_1 = OrderingScore.evaluate(ix_1, filter, ops);
 +        score_2 = OrderingScore.evaluate(ix_2, filter, ops);
 +
 +        // Index 2 has less properties, so it is better.
 +        assertEquals(1, comp.compare(score_1, score_2));
 +        assertEquals(-1, comp.compare(score_2, score_1));
 +
 +        // Keep ix_2 slightly better by matching desired order.
 +        ix_2 = makeIndex(StorableTestBasic.class, "-intProp", "doubleProp", "id", "stringProp");
 +
 +        score_1 = OrderingScore.evaluate(ix_1, filter, ops);
 +        score_2 = OrderingScore.evaluate(ix_2, filter, ops);
 +
 +        assertEquals(1, comp.compare(score_1, score_2));
 +        assertEquals(-1, comp.compare(score_2, score_1));
 +
 +        // Make ix_1 slightly better by making it clustered.
 +        ix_1 = ix_1.clustered(true);
 +
 +        score_1 = OrderingScore.evaluate(ix_1, filter, ops);
 +        score_2 = OrderingScore.evaluate(ix_2, filter, ops);
 +
 +        assertEquals(-1, comp.compare(score_1, score_2));
 +        assertEquals(1, comp.compare(score_2, score_1));
 +
 +        // Make ix_2 better when clustered.
 +        ix_2 = ix_2.clustered(true);
 +
 +        score_1 = OrderingScore.evaluate(ix_1, filter, ops);
 +        score_2 = OrderingScore.evaluate(ix_2, filter, ops);
 +
 +        assertEquals(1, comp.compare(score_1, score_2));
 +        assertEquals(-1, comp.compare(score_2, score_1));
 +
 +        // Make ix_1 same by reversing order.
 +        ix_1 = ix_1.reverse();
 +
 +        score_1 = OrderingScore.evaluate(ix_1, filter, ops);
 +        score_2 = OrderingScore.evaluate(ix_2, filter, ops);
 +
 +        assertEquals(0, comp.compare(score_1, score_2));
 +        assertEquals(0, comp.compare(score_2, score_1));
 +    }
 +
 +    public void testIndexNotNeeded() throws Exception {
 +        // Test an index which matches desired orderings, but ordering
 +        // properties are filtered out. Thus the index is not needed.
 +
 +        final StorableIndex<StorableTestBasic> ix;
 +        OrderingList<StorableTestBasic> ops;
 +        OrderingScore score;
 +        Filter<StorableTestBasic> filter;
 +
 +        ix = makeIndex(StorableTestBasic.class, "id", "intProp", "doubleProp");
 +
 +        ops = makeOrdering(StorableTestBasic.class, "id", "intProp", "doubleProp");
 +        score = OrderingScore.evaluate(ix, null, ops);
 +        assertEquals(3, score.getHandledCount());
 +        assertEquals("+id", score.getHandledOrdering().get(0).toString());
 +        assertEquals("+intProp", score.getHandledOrdering().get(1).toString());
 +        assertEquals("+doubleProp", score.getHandledOrdering().get(2).toString());
 +        assertEquals(0, score.getRemainderCount());
 +        assertEquals(false, score.shouldReverseOrder());
 +
 +        filter = Filter.filterFor(StorableTestBasic.class, "id = ? & intProp = ?");
 +
 +        score = OrderingScore.evaluate(ix, filter, ops);
 +        assertEquals(1, score.getHandledCount());
 +        assertEquals("+doubleProp", score.getHandledOrdering().get(0).toString());
 +        assertEquals(0, score.getRemainderCount());
 +        assertEquals(false, score.shouldReverseOrder());
 +
 +        filter = Filter.filterFor(StorableTestBasic.class, "id = ? & intProp = ? & doubleProp =?");
 +
 +        score = OrderingScore.evaluate(ix, filter, ops);
 +        assertEquals(0, score.getHandledCount());
 +        assertEquals(0, score.getRemainderCount());
 +        assertEquals(false, score.shouldReverseOrder());
 +    }
 +
 +    public void testUniqueIndexNotNeeded() throws Exception {
 +        // Test a unique index which has been fully specified. Ordering is not
 +        // needed at all.
 +        final StorableIndex<StorableTestBasic> ix;
 +        OrderingList<StorableTestBasic> ops;
 +        OrderingScore score;
 +        Filter<StorableTestBasic> filter;
 +
 +        ix = makeIndex(StorableTestBasic.class, "id", "intProp", "doubleProp").unique(true);
 +        ops = makeOrdering(StorableTestBasic.class, "stringProp", "doubleProp");
 +        filter = Filter.filterFor(StorableTestBasic.class, "id = ? & intProp = ? & doubleProp =?");
 +
 +        score = OrderingScore.evaluate(ix, filter, ops);
 +        assertEquals(0, score.getHandledCount());
 +        assertEquals(0, score.getRemainderCount());
 +        assertEquals(false, score.shouldReverseOrder());
 +    }
 +
 +    public void testReduce() throws Exception {
 +        // Tests that redundant ordering properties are removed.
 +        final StorableIndex<StorableTestBasic> ix;
 +        OrderingList<StorableTestBasic> ops;
 +        OrderingScore score;
 +        Filter<StorableTestBasic> filter;
 +
 +        ix = makeIndex(StorableTestBasic.class, "id", "intProp", "doubleProp");
 +        ops = makeOrdering(StorableTestBasic.class, 
 +                            "intProp", "intProp", "id", "doubleProp", "intProp", "doubleProp",
 +                            "longProp", "longProp", "id", "intProp", "doubleProp");
 +        filter = Filter.filterFor(StorableTestBasic.class, "id = ?");
 +
 +        score = OrderingScore.evaluate(ix, filter, ops);
 +        assertEquals(2, score.getHandledCount());
 +        assertEquals(1, score.getRemainderCount());
 +        assertEquals(false, score.shouldReverseOrder());
 +
 +        assertEquals("+intProp", score.getHandledOrdering().get(0).toString());
 +        assertEquals("+doubleProp", score.getHandledOrdering().get(1).toString());
 +        assertEquals("+longProp", score.getRemainderOrdering().get(0).toString());
 +    }
 +
 +    public void testUnspecifiedDirection() throws Exception {
 +        // Tests that an originally unspecified ordering direction is determined.
 +        final StorableIndex<StorableTestBasic> ix;
 +        OrderingList<StorableTestBasic> ops;
 +        OrderingScore score;
 +        Filter<StorableTestBasic> filter;
 +
 +        ix = makeIndex(StorableTestBasic.class, "id", "intProp", "doubleProp");
 +        ops = makeOrdering(StorableTestBasic.class, "~intProp", "-doubleProp");
 +        filter = Filter.filterFor(StorableTestBasic.class, "id = ?");
 +
 +        score = OrderingScore.evaluate(ix, filter, ops);
 +        assertEquals(2, score.getHandledCount());
 +        assertEquals(0, score.getRemainderCount());
 +        assertEquals(true, score.shouldReverseOrder());
 +
 +        assertEquals("-intProp", score.getHandledOrdering().get(0).toString());
 +        assertEquals("-doubleProp", score.getHandledOrdering().get(1).toString());
 +
 +        ops = makeOrdering(StorableTestBasic.class, "~id", "intProp", "~doubleProp");
 +
 +        score = OrderingScore.evaluate(ix, null, ops);
 +        assertEquals(3, score.getHandledCount());
 +        assertEquals(0, score.getRemainderCount());
 +        assertEquals(false, score.shouldReverseOrder());
 +
 +        assertEquals("+id", score.getHandledOrdering().get(0).toString());
 +        assertEquals("+intProp", score.getHandledOrdering().get(1).toString());
 +        assertEquals("+doubleProp", score.getHandledOrdering().get(2).toString());
 +
 +        ops = makeOrdering(StorableTestBasic.class, "~id", "-intProp", "~doubleProp");
 +
 +        score = OrderingScore.evaluate(ix, null, ops);
 +        assertEquals(3, score.getHandledCount());
 +        assertEquals(0, score.getRemainderCount());
 +        assertEquals(true, score.shouldReverseOrder());
 +
 +        assertEquals("-id", score.getHandledOrdering().get(0).toString());
 +        assertEquals("-intProp", score.getHandledOrdering().get(1).toString());
 +        assertEquals("-doubleProp", score.getHandledOrdering().get(2).toString());
 +
 +        ops = makeOrdering(StorableTestBasic.class, "~id", "-intProp", "~longProp");
 +
 +        score = OrderingScore.evaluate(ix, null, ops);
 +        assertEquals(2, score.getHandledCount());
 +        assertEquals(1, score.getRemainderCount());
 +        assertEquals(true, score.shouldReverseOrder());
 +
 +        assertEquals("-id", score.getHandledOrdering().get(0).toString());
 +        assertEquals("-intProp", score.getHandledOrdering().get(1).toString());
 +        assertEquals("~longProp", score.getRemainderOrdering().get(0).toString());
 +    }
 +
 +    public void testFreeOrdering() throws Exception {
 +        final StorableIndex<StorableTestBasic> ix;
 +        OrderingList<StorableTestBasic> ops;
 +        OrderingScore score;
 +        Filter<StorableTestBasic> filter = null;
 +
 +        ix = makeIndex(StorableTestBasic.class, "id", "intProp", "doubleProp");
 +        ops = makeOrdering(StorableTestBasic.class, "~id");
 +
 +        score = OrderingScore.evaluate(ix, filter, ops);
 +        assertEquals(1, score.getHandledCount());
 +        assertEquals(0, score.getRemainderCount());
 +        assertEquals(false, score.shouldReverseOrder());
 +        assertEquals(2, score.getFreeOrdering().size());
 +        assertEquals("~intProp", score.getFreeOrdering().get(0).toString());
 +        assertEquals("~doubleProp", score.getFreeOrdering().get(1).toString());
 +        assertEquals(0, score.getUnusedOrdering().size());
 +
 +        ops = makeOrdering(StorableTestBasic.class, "~id", "-intProp");
 +
 +        score = OrderingScore.evaluate(ix, filter, ops);
 +        assertEquals(2, score.getHandledCount());
 +        assertEquals(0, score.getRemainderCount());
 +        assertEquals(true, score.shouldReverseOrder());
 +        assertEquals(1, score.getFreeOrdering().size());
 +        assertEquals("-doubleProp", score.getFreeOrdering().get(0).toString());
 +        assertEquals(0, score.getUnusedOrdering().size());
 +
 +        ops = makeOrdering(StorableTestBasic.class, "~id", "-intProp", "+doubleProp");
 +
 +        score = OrderingScore.evaluate(ix, filter, ops);
 +        assertEquals(2, score.getHandledCount());
 +        assertEquals(1, score.getRemainderCount());
 +        assertEquals("+doubleProp", score.getRemainderOrdering().get(0).toString());
 +        assertEquals(true, score.shouldReverseOrder());
 +        assertEquals(1, score.getFreeOrdering().size());
 +        assertEquals("-doubleProp", score.getFreeOrdering().get(0).toString());
 +        assertEquals(0, score.getUnusedOrdering().size());
 +    }
 +
 +    public void testFreeAndUnusedOrdering() throws Exception {
 +        final StorableIndex<StorableTestBasic> ix;
 +        OrderingList<StorableTestBasic> ops;
 +        OrderingScore score;
 +        Filter<StorableTestBasic> filter;
 +
 +        ix = makeIndex(StorableTestBasic.class, "stringProp", "id", "intProp", "doubleProp");
 +        ops = makeOrdering(StorableTestBasic.class, "~id");
 +        filter = Filter.filterFor(StorableTestBasic.class, "stringProp = ?");
 +
 +        score = OrderingScore.evaluate(ix, filter, ops);
 +        assertEquals(1, score.getHandledCount());
 +        assertEquals(0, score.getRemainderCount());
 +        assertEquals(false, score.shouldReverseOrder());
 +        assertEquals(2, score.getFreeOrdering().size());
 +        assertEquals("~intProp", score.getFreeOrdering().get(0).toString());
 +        assertEquals("~doubleProp", score.getFreeOrdering().get(1).toString());
 +        assertEquals(1, score.getUnusedOrdering().size());
 +        assertEquals("~stringProp", score.getUnusedOrdering().get(0).toString());
 +
 +        ops = makeOrdering(StorableTestBasic.class, "~id", "-intProp");
 +
 +        score = OrderingScore.evaluate(ix, filter, ops);
 +        assertEquals(2, score.getHandledCount());
 +        assertEquals(0, score.getRemainderCount());
 +        assertEquals(true, score.shouldReverseOrder());
 +        assertEquals(1, score.getFreeOrdering().size());
 +        assertEquals("-doubleProp", score.getFreeOrdering().get(0).toString());
 +        assertEquals(1, score.getUnusedOrdering().size());
 +        assertEquals("~stringProp", score.getUnusedOrdering().get(0).toString());
 +
 +        ops = makeOrdering(StorableTestBasic.class, "~id", "-intProp", "+doubleProp");
 +
 +        score = OrderingScore.evaluate(ix, filter, ops);
 +        assertEquals(2, score.getHandledCount());
 +        assertEquals(1, score.getRemainderCount());
 +        assertEquals("+doubleProp", score.getRemainderOrdering().get(0).toString());
 +        assertEquals(true, score.shouldReverseOrder());
 +        assertEquals(1, score.getFreeOrdering().size());
 +        assertEquals("-doubleProp", score.getFreeOrdering().get(0).toString());
 +        assertEquals(1, score.getUnusedOrdering().size());
 +        assertEquals("~stringProp", score.getUnusedOrdering().get(0).toString());
 +    }
 +}
 diff --git a/src/test/java/com/amazon/carbonado/qe/TestPropertyFilterList.java b/src/test/java/com/amazon/carbonado/qe/TestPropertyFilterList.java new file mode 100644 index 0000000..b0af52f --- /dev/null +++ b/src/test/java/com/amazon/carbonado/qe/TestPropertyFilterList.java @@ -0,0 +1,108 @@ +/*
 + * Copyright 2006 Amazon Technologies, Inc. or its affiliates.
 + * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
 + * of Amazon Technologies, Inc. or its affiliates.  All rights reserved.
 + *
 + * Licensed under the Apache License, Version 2.0 (the "License");
 + * you may not use this file except in compliance with the License.
 + * You may obtain a copy of the License at
 + *
 + *     http://www.apache.org/licenses/LICENSE-2.0
 + *
 + * Unless required by applicable law or agreed to in writing, software
 + * distributed under the License is distributed on an "AS IS" BASIS,
 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 + * See the License for the specific language governing permissions and
 + * limitations under the License.
 + */
 +
 +package com.amazon.carbonado.qe;
 +
 +import java.util.List;
 +
 +import junit.framework.TestCase;
 +import junit.framework.TestSuite;
 +
 +import com.amazon.carbonado.filter.Filter;
 +import com.amazon.carbonado.filter.PropertyFilter;
 +
 +import com.amazon.carbonado.stored.StorableTestBasic;
 +
 +/**
 + *
 + *
 + * @author Brian S O'Neill
 + */
 +public class TestPropertyFilterList extends TestCase {
 +    public static void main(String[] args) {
 +        junit.textui.TestRunner.run(suite());
 +    }
 +
 +    public static TestSuite suite() {
 +        return new TestSuite(TestPropertyFilterList.class);
 +    }
 +
 +    public TestPropertyFilterList(String name) {
 +        super(name);
 +    }
 +
 +    public void testNull() throws Exception {
 +        assertEquals(0, PropertyFilterList.get(null).size());
 +    }
 +
 +    public void testOpen() throws Exception {
 +        Filter<StorableTestBasic> filter = Filter.getOpenFilter(StorableTestBasic.class);
 +        assertEquals(0, PropertyFilterList.get(filter).size());
 +    }
 +
 +    public void testClosed() throws Exception {
 +        Filter<StorableTestBasic> filter = Filter.getClosedFilter(StorableTestBasic.class);
 +        assertEquals(0, PropertyFilterList.get(filter).size());
 +    }
 +
 +    public void testSingleton() throws Exception {
 +        Filter<StorableTestBasic> filter = Filter.filterFor(StorableTestBasic.class, "id = ?");
 +
 +        List<PropertyFilter<StorableTestBasic>> list = PropertyFilterList.get(filter);
 +
 +        assertEquals(1, list.size());
 +        assertEquals(filter, list.get(0));
 +
 +        List<PropertyFilter<StorableTestBasic>> list2 = PropertyFilterList.get(filter);
 +
 +        assertTrue(list == list2);
 +    }
 +
 +    public void testMultiple() throws Exception {
 +        Filter<StorableTestBasic> filter =
 +            Filter.filterFor(StorableTestBasic.class, "id = ? & intProp > ?");
 +
 +        List<PropertyFilter<StorableTestBasic>> list = PropertyFilterList.get(filter);
 +
 +        assertEquals(2, list.size());
 +
 +        Filter<StorableTestBasic> subFilter =
 +            Filter.filterFor(StorableTestBasic.class, "id = ?");
 +
 +        assertEquals(subFilter, list.get(0));
 +
 +        subFilter = Filter.filterFor(StorableTestBasic.class, "intProp > ?");
 +
 +        assertEquals(subFilter, list.get(1));
 +
 +        List<PropertyFilter<StorableTestBasic>> list2 = PropertyFilterList.get(filter);
 +
 +        assertTrue(list == list2);
 +    }
 +
 +    public void testIllegal() throws Exception {
 +        Filter<StorableTestBasic> filter =
 +            Filter.filterFor(StorableTestBasic.class, "id = ? | intProp > ?");
 +
 +        try {
 +            List<PropertyFilter<StorableTestBasic>> list = PropertyFilterList.get(filter);
 +            fail();
 +        } catch (IllegalArgumentException e) {
 +        }
 +    }
 +}
 diff --git a/src/test/java/com/amazon/carbonado/qe/TestQueryExecutor.java b/src/test/java/com/amazon/carbonado/qe/TestQueryExecutor.java new file mode 100644 index 0000000..07558f1 --- /dev/null +++ b/src/test/java/com/amazon/carbonado/qe/TestQueryExecutor.java @@ -0,0 +1,186 @@ +/*
 + * Copyright 2006 Amazon Technologies, Inc. or its affiliates.
 + * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
 + * of Amazon Technologies, Inc. or its affiliates.  All rights reserved.
 + *
 + * Licensed under the Apache License, Version 2.0 (the "License");
 + * you may not use this file except in compliance with the License.
 + * You may obtain a copy of the License at
 + *
 + *     http://www.apache.org/licenses/LICENSE-2.0
 + *
 + * Unless required by applicable law or agreed to in writing, software
 + * distributed under the License is distributed on an "AS IS" BASIS,
 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 + * See the License for the specific language governing permissions and
 + * limitations under the License.
 + */
 +
 +package com.amazon.carbonado.qe;
 +
 +import java.io.IOException;
 +
 +import java.util.ArrayList;
 +import java.util.Collection;
 +import java.util.Comparator;
 +import java.util.List;
 +import java.util.Map;
 +
 +import junit.framework.TestCase;
 +
 +import com.amazon.carbonado.Cursor;
 +import com.amazon.carbonado.FetchException;
 +
 +import com.amazon.carbonado.info.Direction;
 +import com.amazon.carbonado.info.OrderedProperty;
 +import com.amazon.carbonado.info.StorableInfo;
 +import com.amazon.carbonado.info.StorableIntrospector;
 +import com.amazon.carbonado.info.StorableProperty;
 +
 +import com.amazon.carbonado.stored.Dummy;
 +import com.amazon.carbonado.stored.Address;
 +
 +/**
 + *
 + *
 + * @author Brian S O'Neill
 + */
 +public abstract class TestQueryExecutor extends TestCase {
 +
 +    protected QueryExecutor<Address> createExecutor(int... ids) {
 +        return new IterableQueryExecutor<Address>(Address.class, createCollection(ids));
 +    }
 +
 +    protected Collection<Address> createCollection(int... ids) {
 +        Collection<Address> elements = new ArrayList<Address>(ids.length);
 +        for (int i=0; i<ids.length; i++) {
 +            elements.add(new DummyAddress(ids[i]));
 +        }
 +        return elements;
 +    }
 +
 +    protected void compareElements(Cursor<Address> elements, int... expectedIDs)
 +        throws FetchException
 +    {
 +        for (int id : expectedIDs) {
 +            if (elements.hasNext()) {
 +                Address e = elements.next();
 +                if (e.getAddressID() != id) {
 +                    fail("Element mismatch: expected=" + id + ", actual=" + e.getAddressID());
 +                    elements.close();
 +                    return;
 +                }
 +            } else {
 +                fail("Too few elements in cursor");
 +                return;
 +            }
 +        }
 +
 +        if (elements.hasNext()) {
 +            Address e = elements.next();
 +            fail("Too many elements in cursor: " + e.getAddressID());
 +            elements.close();
 +        }
 +    }
 +
 +    protected OrderingList<Address> createOrdering(String... properties) {
 +        return OrderingList.get(Address.class, properties);
 +    }
 +
 +    static void printPlan(QueryExecutor executor) {
 +        try {
 +            executor.printPlan(System.out, 0, null);
 +        } catch (IOException e) {
 +        }
 +    }
 +
 +    private static class DummyAddress extends Dummy implements Address {
 +        private long mID;
 +        private String mLine1;
 +        private String mLine2;
 +        private String mCity;
 +        private String mState;
 +        private String mZip;
 +        private String mCountry;
 +        private String mData;
 +
 +        DummyAddress(long id) {
 +            mID = id;
 +            mLine1 = "line1_" + id;
 +            mLine2 = "line2_" + id;
 +            mCity = "city_" + id;
 +            mState = "state_" + id;
 +            mZip = "zip_" + id;
 +            mCountry = "country_" + id;
 +            mData = "data_" + id;
 +        }
 +
 +        public long getAddressID() {
 +            return mID;
 +        }
 +
 +        public void setAddressID(long id) {
 +            mID = id;
 +        }
 +
 +        public String getAddressLine1() {
 +            return mLine1;
 +        }
 +
 +        public void setAddressLine1(String value) {
 +            mLine1 = value;
 +        }
 +
 +        public String getAddressLine2() {
 +            return mLine2;
 +        }
 +
 +        public void setAddressLine2(String value) {
 +            mLine2 = value;
 +        }
 +
 +        public String getAddressCity() {
 +            return mCity;
 +        }
 +
 +        public void setAddressCity(String value) {
 +            mCity = value;
 +        }
 +
 +        public String getAddressState() {
 +            return mState;
 +        }
 +
 +        public void setAddressState(String value) {
 +            mState = value;
 +        }
 +
 +        public String getAddressZip() {
 +            return mZip;
 +        }
 +
 +        public void setAddressZip(String value) {
 +            mZip = value;
 +        }
 +
 +        public String getAddressCountry() {
 +            return mCountry;
 +        }
 +
 +        public void setAddressCountry(String value) {
 +            mCountry = value;
 +        }
 +
 +        public String getCustomData() {
 +            return mData;
 +        }
 +
 +        public void setCustomData(String str) {
 +            mData = str;
 +        }
 +
 +        public String toString() {
 +            return "address " + mID;
 +        }
 +    }
 +}
 diff --git a/src/test/java/com/amazon/carbonado/qe/TestSortedQueryExecutor.java b/src/test/java/com/amazon/carbonado/qe/TestSortedQueryExecutor.java new file mode 100644 index 0000000..53c47d6 --- /dev/null +++ b/src/test/java/com/amazon/carbonado/qe/TestSortedQueryExecutor.java @@ -0,0 +1,84 @@ +/*
 + * Copyright 2006 Amazon Technologies, Inc. or its affiliates.
 + * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
 + * of Amazon Technologies, Inc. or its affiliates.  All rights reserved.
 + *
 + * Licensed under the Apache License, Version 2.0 (the "License");
 + * you may not use this file except in compliance with the License.
 + * You may obtain a copy of the License at
 + *
 + *     http://www.apache.org/licenses/LICENSE-2.0
 + *
 + * Unless required by applicable law or agreed to in writing, software
 + * distributed under the License is distributed on an "AS IS" BASIS,
 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 + * See the License for the specific language governing permissions and
 + * limitations under the License.
 + */
 +
 +package com.amazon.carbonado.qe;
 +
 +import java.util.List;
 +
 +import junit.framework.TestSuite;
 +
 +import com.amazon.carbonado.filter.Filter;
 +import com.amazon.carbonado.filter.FilterValues;
 +
 +import com.amazon.carbonado.info.OrderedProperty;
 +
 +import com.amazon.carbonado.stored.Address;
 +
 +/**
 + *
 + *
 + * @author Brian S O'Neill
 + */
 +public class TestSortedQueryExecutor extends TestQueryExecutor {
 +    public static void main(String[] args) {
 +        junit.textui.TestRunner.run(suite());
 +    }
 +
 +    public static TestSuite suite() {
 +        return new TestSuite(TestSortedQueryExecutor.class);
 +    }
 +
 +    public void testBasicSorting() throws Exception {
 +        QueryExecutor<Address> unsorted = createExecutor(4, 2, 3, 1);
 +        Filter<Address> filter = Filter.getOpenFilter(Address.class);
 +        FilterValues<Address> values = filter.initialFilterValues();
 +        OrderingList<Address> ordered = createOrdering("addressCountry");
 +
 +        QueryExecutor<Address> executor =
 +            new SortedQueryExecutor<Address>(null, unsorted, null, ordered);
 +
 +        assertEquals(filter, executor.getFilter());
 +
 +        assertEquals(4, executor.count(values));
 +
 +        assertEquals(ordered, executor.getOrdering());
 +
 +        compareElements(executor.fetch(values), 1, 2, 3, 4);
 +    }
 +
 +    public void testBasicFinisherSorting() throws Exception {
 +        QueryExecutor<Address> unsorted = createExecutor(1, 2, 3, 4);
 +        Filter<Address> filter = Filter.getOpenFilter(Address.class);
 +        FilterValues<Address> values = filter.initialFilterValues();
 +        OrderingList<Address> handled = createOrdering("addressCountry");
 +        OrderingList<Address> finisher = createOrdering("addressState");
 +
 +        QueryExecutor<Address> executor =
 +            new SortedQueryExecutor<Address>(null, unsorted, handled, finisher);
 +
 +        assertEquals(filter, executor.getFilter());
 +
 +        assertEquals(4, executor.count(values));
 +
 +        assertEquals(2, executor.getOrdering().size());
 +        assertEquals(handled.get(0), executor.getOrdering().get(0));
 +        assertEquals(finisher.get(0), executor.getOrdering().get(1));
 +
 +        compareElements(executor.fetch(values), 1, 2, 3, 4);
 +    }
 +}
 diff --git a/src/test/java/com/amazon/carbonado/qe/TestUnionQueryAnalyzer.java b/src/test/java/com/amazon/carbonado/qe/TestUnionQueryAnalyzer.java new file mode 100644 index 0000000..21d0b0c --- /dev/null +++ b/src/test/java/com/amazon/carbonado/qe/TestUnionQueryAnalyzer.java @@ -0,0 +1,565 @@ +/*
 + * Copyright 2006 Amazon Technologies, Inc. or its affiliates.
 + * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
 + * of Amazon Technologies, Inc. or its affiliates.  All rights reserved.
 + *
 + * Licensed under the Apache License, Version 2.0 (the "License");
 + * you may not use this file except in compliance with the License.
 + * You may obtain a copy of the License at
 + *
 + *     http://www.apache.org/licenses/LICENSE-2.0
 + *
 + * Unless required by applicable law or agreed to in writing, software
 + * distributed under the License is distributed on an "AS IS" BASIS,
 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 + * See the License for the specific language governing permissions and
 + * limitations under the License.
 + */
 +
 +package com.amazon.carbonado.qe;
 +
 +import java.util.List;
 +
 +import junit.framework.TestCase;
 +import junit.framework.TestSuite;
 +
 +import com.amazon.carbonado.Repository;
 +import com.amazon.carbonado.Storable;
 +
 +import com.amazon.carbonado.info.OrderedProperty;
 +import com.amazon.carbonado.info.StorableIndex;
 +
 +import com.amazon.carbonado.filter.Filter;
 +import com.amazon.carbonado.filter.FilterValues;
 +import com.amazon.carbonado.filter.PropertyFilter;
 +
 +import com.amazon.carbonado.repo.toy.ToyRepository;
 +
 +import com.amazon.carbonado.stored.Address;
 +import com.amazon.carbonado.stored.Order;
 +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;
 +
 +/**
 + * 
 + *
 + * @author Brian S O'Neill
 + */
 +public class TestUnionQueryAnalyzer extends TestCase {
 +    public static void main(String[] args) {
 +        junit.textui.TestRunner.run(suite());
 +    }
 +
 +    public static TestSuite suite() {
 +        return new TestSuite(TestUnionQueryAnalyzer.class);
 +    }
 +
 +    static <S extends Storable> StorableIndex<S> makeIndex(Class<S> type, String... props) {
 +        return TestOrderingScore.makeIndex(type, props);
 +    }
 +
 +    static <S extends Storable> OrderingList<S> makeOrdering(Class<S> type, String... props) {
 +        return TestOrderingScore.makeOrdering(type, props);
 +    }
 +
 +    public TestUnionQueryAnalyzer(String name) {
 +        super(name);
 +    }
 +
 +    public void testNullFilter() throws Exception {
 +        UnionQueryAnalyzer uqa =
 +            new UnionQueryAnalyzer(Shipment.class, TestIndexedQueryAnalyzer.RepoAccess.INSTANCE);
 +        UnionQueryAnalyzer.Result result = uqa.analyze(null, null);
 +        List<IndexedQueryAnalyzer<Shipment>.Result> subResults = result.getSubResults();
 +
 +        assertEquals(1, subResults.size());
 +    }
 +
 +    public void testSingleSubResult() throws Exception {
 +        UnionQueryAnalyzer uqa =
 +            new UnionQueryAnalyzer(Shipment.class, TestIndexedQueryAnalyzer.RepoAccess.INSTANCE);
 +        Filter<Shipment> filter = Filter.filterFor(Shipment.class, "shipmentID = ?");
 +        filter = filter.bind();
 +        UnionQueryAnalyzer.Result result = uqa.analyze(filter, null);
 +        List<IndexedQueryAnalyzer<Shipment>.Result> subResults = result.getSubResults();
 +
 +        assertEquals(1, subResults.size());
 +    }
 +
 +    public void testSingleSubResultUnspecifiedDirection() throws Exception {
 +        UnionQueryAnalyzer uqa =
 +            new UnionQueryAnalyzer(Shipment.class, TestIndexedQueryAnalyzer.RepoAccess.INSTANCE);
 +        Filter<Shipment> filter = Filter.filterFor(Shipment.class, "shipmentID > ?");
 +        filter = filter.bind();
 +        OrderingList<Shipment> orderings =
 +            makeOrdering(Shipment.class, "~shipmentID", "~orderID");
 +        UnionQueryAnalyzer.Result result = uqa.analyze(filter, orderings);
 +        List<IndexedQueryAnalyzer<Shipment>.Result> subResults = result.getSubResults();
 +
 +        assertEquals(1, subResults.size());
 +        List<OrderedProperty<Shipment>> handled = 
 +            subResults.get(0).getCompositeScore().getOrderingScore().getHandledOrdering();
 +        assertEquals(1, handled.size());
 +        assertEquals("+shipmentID", handled.get(0).toString());
 +    }
 +
 +    public void testSimpleUnion() throws Exception {
 +        UnionQueryAnalyzer uqa =
 +            new UnionQueryAnalyzer(Shipment.class, TestIndexedQueryAnalyzer.RepoAccess.INSTANCE);
 +        Filter<Shipment> filter = Filter.filterFor(Shipment.class,
 +                                                   "shipmentID = ? | orderID = ?");
 +        filter = filter.bind();
 +        UnionQueryAnalyzer.Result result = uqa.analyze(filter, null);
 +        assertEquals(OrderingList.get(Shipment.class, "+shipmentID"), result.getTotalOrdering());
 +        List<IndexedQueryAnalyzer<Shipment>.Result> subResults = result.getSubResults();
 +
 +        assertEquals(2, subResults.size());
 +        IndexedQueryAnalyzer<Shipment>.Result res_0 = subResults.get(0);
 +        IndexedQueryAnalyzer<Shipment>.Result res_1 = subResults.get(1);
 +
 +        assertTrue(res_0.handlesAnything());
 +        assertEquals(Filter.filterFor(Shipment.class, "shipmentID = ?").bind(),
 +                     res_0.getCompositeScore().getFilteringScore().getIdentityFilter());
 +        assertEquals(makeIndex(Shipment.class, "shipmentID"), res_0.getLocalIndex());
 +        assertEquals(null, res_0.getForeignIndex());
 +        assertEquals(null, res_0.getForeignProperty());
 +        assertEquals(0, res_0.getRemainderOrdering().size());
 +
 +        assertTrue(res_1.handlesAnything());
 +        assertEquals(Filter.filterFor(Shipment.class, "orderID = ?").bind(),
 +                     res_1.getCompositeScore().getFilteringScore().getIdentityFilter());
 +        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());
 +        assertEquals("+shipmentID", res_1.getRemainderOrdering().get(0).toString());
 +    }
 + 
 +    public void testSimpleUnion2() throws Exception {
 +        UnionQueryAnalyzer uqa =
 +            new UnionQueryAnalyzer(Shipment.class, TestIndexedQueryAnalyzer.RepoAccess.INSTANCE);
 +        Filter<Shipment> filter = Filter.filterFor(Shipment.class,
 +                                                   "shipmentID = ? | orderID > ?");
 +        filter = filter.bind();
 +        UnionQueryAnalyzer.Result result = uqa.analyze(filter, null);
 +        assertEquals(OrderingList.get(Shipment.class, "+shipmentID"), result.getTotalOrdering());
 +        List<IndexedQueryAnalyzer<Shipment>.Result> subResults = result.getSubResults();
 +
 +        assertEquals(2, subResults.size());
 +        IndexedQueryAnalyzer<Shipment>.Result res_0 = subResults.get(0);
 +        IndexedQueryAnalyzer<Shipment>.Result res_1 = subResults.get(1);
 +
 +        assertTrue(res_0.handlesAnything());
 +        assertEquals(Filter.filterFor(Shipment.class, "shipmentID = ?").bind(),
 +                     res_0.getCompositeScore().getFilteringScore().getIdentityFilter());
 +        assertEquals(makeIndex(Shipment.class, "shipmentID"), res_0.getLocalIndex());
 +        assertEquals(null, res_0.getForeignIndex());
 +        assertEquals(null, res_0.getForeignProperty());
 +        assertEquals(0, res_0.getRemainderOrdering().size());
 +
 +        assertTrue(res_1.handlesAnything());
 +        assertTrue(res_1.getCompositeScore().getFilteringScore().hasRangeStart());
 +        assertFalse(res_1.getCompositeScore().getFilteringScore().hasRangeEnd());
 +        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());
 +        assertEquals("+shipmentID", res_1.getRemainderOrdering().get(0).toString());
 +    }
 +
 +    public void testSimpleUnion3() throws Exception {
 +        UnionQueryAnalyzer uqa =
 +            new UnionQueryAnalyzer(Shipment.class, TestIndexedQueryAnalyzer.RepoAccess.INSTANCE);
 +        Filter<Shipment> filter = Filter.filterFor(Shipment.class,
 +                                                   "shipmentID = ? | orderID > ? & orderID <= ?");
 +        filter = filter.bind();
 +        UnionQueryAnalyzer.Result result = uqa.analyze(filter, null);
 +        assertEquals(OrderingList.get(Shipment.class, "+shipmentID"), result.getTotalOrdering());
 +        List<IndexedQueryAnalyzer<Shipment>.Result> subResults = result.getSubResults();
 +
 +        assertEquals(2, subResults.size());
 +        IndexedQueryAnalyzer<Shipment>.Result res_0 = subResults.get(0);
 +        IndexedQueryAnalyzer<Shipment>.Result res_1 = subResults.get(1);
 +
 +        assertTrue(res_0.handlesAnything());
 +        assertEquals(Filter.filterFor(Shipment.class, "shipmentID = ?").bind(),
 +                     res_0.getCompositeScore().getFilteringScore().getIdentityFilter());
 +        assertEquals(makeIndex(Shipment.class, "shipmentID"), res_0.getLocalIndex());
 +        assertEquals(null, res_0.getForeignIndex());
 +        assertEquals(null, res_0.getForeignProperty());
 +        assertEquals(0, res_0.getRemainderOrdering().size());
 +
 +        // Note: index that has proper filtering is preferred because
 +        // "orderId > ? & orderID <= ?" filter specifies a complete range.
 +        // We'll have to do a sort, but it isn't expected to be over that many records.
 +        assertTrue(res_1.handlesAnything());
 +        assertTrue(res_1.getCompositeScore().getFilteringScore().hasRangeStart());
 +        assertTrue(res_1.getCompositeScore().getFilteringScore().hasRangeEnd());
 +        List<PropertyFilter<Shipment>> rangeFilters =
 +            res_1.getCompositeScore().getFilteringScore().getRangeStartFilters();
 +        assertEquals(1, rangeFilters.size());
 +        assertEquals(Filter.filterFor(Shipment.class, "orderID > ?").bind(), rangeFilters.get(0));
 +        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", "shipmentNotes"), res_1.getLocalIndex());
 +        assertEquals(null, res_1.getForeignIndex());
 +        assertEquals(null, res_1.getForeignProperty());
 +        // Sort operation required because the "shipmentID" index was not chosen.
 +        assertEquals("+shipmentID", res_1.getRemainderOrdering().get(0).toString());
 +    }
 +
 +    public void testSimpleUnionUnspecifiedDirection() throws Exception {
 +        UnionQueryAnalyzer uqa =
 +            new UnionQueryAnalyzer(Shipment.class, TestIndexedQueryAnalyzer.RepoAccess.INSTANCE);
 +        Filter<Shipment> filter = Filter.filterFor(Shipment.class,
 +                                                   "shipmentID > ? | orderID = ?");
 +        filter = filter.bind();
 +        OrderingList<Shipment> orderings =
 +            makeOrdering(Shipment.class, "~shipmentID", "~orderID");
 +        UnionQueryAnalyzer.Result result = uqa.analyze(filter, orderings);
 +        assertEquals(OrderingList.get(Shipment.class, "+shipmentID", "+orderID"),
 +                     result.getTotalOrdering());
 +        List<IndexedQueryAnalyzer<Shipment>.Result> subResults = result.getSubResults();
 +
 +        assertEquals(2, subResults.size());
 +        IndexedQueryAnalyzer<Shipment>.Result res_0 = subResults.get(0);
 +        IndexedQueryAnalyzer<Shipment>.Result res_1 = subResults.get(1);
 +
 +        List<OrderedProperty<Shipment>> handled =
 +            res_0.getCompositeScore().getOrderingScore().getHandledOrdering();
 +        assertEquals(1, handled.size());
 +        assertEquals("+shipmentID", handled.get(0).toString());
 +
 +        handled = res_1.getCompositeScore().getOrderingScore().getHandledOrdering();
 +        assertEquals(0, handled.size());
 +
 +        assertTrue(res_0.handlesAnything());
 +        assertTrue(res_0.getCompositeScore().getFilteringScore().hasRangeStart());
 +        assertFalse(res_0.getCompositeScore().getFilteringScore().hasRangeEnd());
 +        assertEquals(makeIndex(Shipment.class, "shipmentID"), res_0.getLocalIndex());
 +        assertEquals(null, res_0.getForeignIndex());
 +        assertEquals(null, res_0.getForeignProperty());
 +        assertEquals(1, res_0.getRemainderOrdering().size());
 +        assertEquals("+orderID", res_0.getRemainderOrdering().get(0).toString());
 +
 +        assertTrue(res_1.handlesAnything());
 +        assertEquals(Filter.filterFor(Shipment.class, "orderID = ?").bind(),
 +                     res_1.getCompositeScore().getFilteringScore().getIdentityFilter());
 +        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());
 +        assertEquals("+shipmentID", res_1.getRemainderOrdering().get(0).toString());
 +    }
 +
 +    public void testSimpleMerge() throws Exception {
 +        // Because query has an 'or' operation, the analyzer will initially
 +        // split this into a union. After futher analysis, it should decide
 +        // that this offers no benefit and will merge them back.
 +        UnionQueryAnalyzer uqa =
 +            new UnionQueryAnalyzer(Shipment.class, TestIndexedQueryAnalyzer.RepoAccess.INSTANCE);
 +        Filter<Shipment> filter = Filter.filterFor
 +            (Shipment.class,
 +             "shipmentID = ? & (shipmentID = ? | orderID = ?)");
 +        filter = filter.bind();
 +        UnionQueryAnalyzer.Result result = uqa.analyze(filter, null);
 +        List<IndexedQueryAnalyzer<Shipment>.Result> subResults = result.getSubResults();
 +
 +        assertEquals(1, subResults.size());
 +        IndexedQueryAnalyzer<Shipment>.Result res_0 = subResults.get(0);
 +
 +        assertTrue(res_0.handlesAnything());
 +        assertEquals(Filter.filterFor(Shipment.class, "shipmentID = ?").bind(),
 +                     res_0.getCompositeScore().getFilteringScore().getIdentityFilter());
 +        assertEquals(makeIndex(Shipment.class, "shipmentID"), res_0.getLocalIndex());
 +        assertEquals(null, res_0.getForeignIndex());
 +        assertEquals(null, res_0.getForeignProperty());
 +        assertEquals(0, res_0.getRemainderOrdering().size());
 +        assertEquals(Filter.filterFor(Shipment.class, "shipmentID = ? | orderID = ?"),
 +                     res_0.getRemainderFilter().unbind());
 +    }
 +
 +    public void testFullScan() throws Exception {
 +        // Because no indexes were selected, there's no union to perform.
 +        UnionQueryAnalyzer uqa =
 +            new UnionQueryAnalyzer(Shipment.class, TestIndexedQueryAnalyzer.RepoAccess.INSTANCE);
 +        Filter<Shipment> filter = Filter.filterFor
 +            (Shipment.class, "shipmentNotes = ? | shipperID = ?");
 +        filter = filter.bind();
 +        UnionQueryAnalyzer.Result result = uqa.analyze(filter, null);
 +        List<IndexedQueryAnalyzer<Shipment>.Result> subResults = result.getSubResults();
 +
 +        assertEquals(1, subResults.size());
 +        IndexedQueryAnalyzer<Shipment>.Result res_0 = subResults.get(0);
 +
 +        assertFalse(res_0.handlesAnything());
 +        assertEquals(null, res_0.getForeignIndex());
 +        assertEquals(null, res_0.getForeignProperty());
 +        assertEquals(0, res_0.getRemainderOrdering().size());
 +        assertEquals(Filter.filterFor(Shipment.class, "shipmentNotes = ? | shipperID = ?").bind(),
 +                     res_0.getRemainderFilter());
 +    }
 +
 +    public void testFullScanFallback() throws Exception {
 +        // Because not all sub-results of union use an index, just fallback to
 +        // doing a full scan.
 +        UnionQueryAnalyzer uqa =
 +            new UnionQueryAnalyzer(Shipment.class, TestIndexedQueryAnalyzer.RepoAccess.INSTANCE);
 +        Filter<Shipment> filter = Filter.filterFor
 +            (Shipment.class, "shipmentNotes = ? | orderID = ?");
 +        filter = filter.bind();
 +        UnionQueryAnalyzer.Result result = uqa.analyze(filter, null);
 +        List<IndexedQueryAnalyzer<Shipment>.Result> subResults = result.getSubResults();
 +
 +        assertEquals(1, subResults.size());
 +        IndexedQueryAnalyzer<Shipment>.Result res_0 = subResults.get(0);
 +
 +        assertFalse(res_0.handlesAnything());
 +        assertEquals(null, res_0.getForeignIndex());
 +        assertEquals(null, res_0.getForeignProperty());
 +        assertEquals(0, res_0.getRemainderOrdering().size());
 +        assertEquals(Filter.filterFor(Shipment.class, "shipmentNotes = ? | orderID = ?").bind(),
 +                     res_0.getRemainderFilter());
 +    }
 +
 +    public void testFullScanExempt() throws Exception {
 +        // Although not all sub-results use an index, one that does has a join
 +        // so it is exempt from folding into the full scan.
 +        UnionQueryAnalyzer uqa =
 +            new UnionQueryAnalyzer(Shipment.class, TestIndexedQueryAnalyzer.RepoAccess.INSTANCE);
 +        Filter<Shipment> filter = Filter.filterFor
 +            (Shipment.class, "shipmentNotes = ? | orderID = ? & order.orderTotal > ?");
 +        filter = filter.bind();
 +        UnionQueryAnalyzer.Result result = uqa.analyze(filter, null);
 +        List<IndexedQueryAnalyzer<Shipment>.Result> subResults = result.getSubResults();
 +
 +        assertEquals(2, subResults.size());
 +        IndexedQueryAnalyzer<Shipment>.Result res_0 = subResults.get(0);
 +        IndexedQueryAnalyzer<Shipment>.Result res_1 = subResults.get(1);
 +
 +        assertTrue(res_0.handlesAnything());
 +        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());
 +        assertEquals("+shipmentID", res_0.getRemainderOrdering().get(0).toString());
 +        assertEquals(Filter.filterFor(Shipment.class, "order.orderTotal > ?").bind(),
 +                     res_0.getRemainderFilter());
 +
 +        assertTrue(res_1.handlesAnything());
 +        assertEquals(makeIndex(Shipment.class, "shipmentID"), res_1.getLocalIndex());
 +        assertEquals(null, res_1.getForeignIndex());
 +        assertEquals(null, res_1.getForeignProperty());
 +        assertEquals(0, res_1.getRemainderOrdering().size());
 +        assertEquals(Filter.filterFor(Shipment.class, "shipmentNotes = ?").bind(),
 +                     res_1.getRemainderFilter());
 +
 +    }
 +
 +    public void testComplexUnionPlan() throws Exception {
 +        UnionQueryAnalyzer uqa =
 +            new UnionQueryAnalyzer(StorableTestBasic.class,
 +                                   TestIndexedQueryAnalyzer.RepoAccess.INSTANCE);
 +        Filter<StorableTestBasic> filter = Filter.filterFor
 +            (StorableTestBasic.class, "doubleProp = ? | (stringProp = ? & intProp = ?) | id > ?");
 +        filter = filter.bind();
 +        UnionQueryAnalyzer.Result result = uqa.analyze(filter, null);
 +        assertEquals(OrderingList.get(StorableTestBasic.class, "+id"), result.getTotalOrdering());
 +
 +        QueryExecutor<StorableTestBasic> exec = result.createExecutor();
 +
 +        assertEquals(filter, exec.getFilter());
 +        assertEquals(OrderingList.get(StorableTestBasic.class, "+id"), exec.getOrdering());
 +
 +        List<IndexedQueryAnalyzer<Shipment>.Result> subResults = result.getSubResults();
 +
 +        assertEquals(3, subResults.size());
 +
 +        StringBuffer buf = new StringBuffer();
 +        exec.printPlan(buf, 0, null);
 +        String plan = buf.toString();
 +
 +        String expected =
 +            "union\n" +
 +            "  sort: [+id]\n" +
 +            "    index scan: com.amazon.carbonado.stored.StorableTestBasic\n" +
 +            "    ...index: {properties=[-doubleProp, +longProp, ~id], unique=true}\n" +
 +            "    ...identity filter: doubleProp = ?\n" +
 +            "  index scan: com.amazon.carbonado.stored.StorableTestBasic\n" +
 +            "  ...index: {properties=[-stringProp, -intProp, ~id], unique=true}\n" +
 +            "  ...identity filter: stringProp = ? & intProp = ?\n" +
 +            "  clustered index scan: com.amazon.carbonado.stored.StorableTestBasic\n" +
 +            "  ...index: {properties=[+id], unique=true}\n" +
 +            "  ...range filter: id > ?\n";
 +
 +        // Test test will fail if the format of the plan changes.
 +        assertEquals(expected, plan);
 +    }
 +
 +    public void testComplexUnionPlan2() throws Exception {
 +        UnionQueryAnalyzer uqa =
 +            new UnionQueryAnalyzer(StorableTestBasic.class,
 +                                   TestIndexedQueryAnalyzer.RepoAccess.INSTANCE);
 +        Filter<StorableTestBasic> filter = Filter.filterFor
 +            (StorableTestBasic.class, "doubleProp = ? | stringProp = ?");
 +        filter = filter.bind();
 +        UnionQueryAnalyzer.Result result = uqa.analyze(filter, null);
 +        assertEquals(OrderingList.get(StorableTestBasic.class, "+doubleProp", "+stringProp"),
 +                     result.getTotalOrdering());
 +
 +        QueryExecutor<StorableTestBasic> exec = result.createExecutor();
 +
 +        assertEquals(filter, exec.getFilter());
 +        assertEquals(OrderingList.get(StorableTestBasic.class, "+doubleProp", "+stringProp"),
 +                     exec.getOrdering());
 +
 +        List<IndexedQueryAnalyzer<Shipment>.Result> subResults = result.getSubResults();
 +
 +        assertEquals(2, subResults.size());
 +
 +        StringBuffer buf = new StringBuffer();
 +        exec.printPlan(buf, 0, null);
 +        String plan = buf.toString();
 +
 +        String expected =
 +            "union\n" +
 +            "  sort: [+stringProp]\n" +
 +            "    index scan: com.amazon.carbonado.stored.StorableTestBasic\n" +
 +            "    ...index: {properties=[-doubleProp, +longProp, ~id], unique=true}\n" +
 +            "    ...identity filter: doubleProp = ?\n" +
 +            "  index scan: com.amazon.carbonado.stored.StorableTestBasic\n" +
 +            "  ...index: {properties=[+stringProp, +doubleProp], unique=true}\n" +
 +            "  ...identity filter: stringProp = ?\n";
 +
 +        // Test test will fail if the format of the plan changes.
 +        assertEquals(expected, plan);
 +    }
 +
 +    public void testComplexUnionPlan3() throws Exception {
 +        UnionQueryAnalyzer uqa =
 +            new UnionQueryAnalyzer(StorableTestBasic.class,
 +                                   TestIndexedQueryAnalyzer.RepoAccess.INSTANCE);
 +        Filter<StorableTestBasic> filter = Filter.filterFor
 +            (StorableTestBasic.class, "stringProp = ? | stringProp = ?");
 +        filter = filter.bind();
 +        UnionQueryAnalyzer.Result result = uqa.analyze(filter, null);
 +        assertEquals(OrderingList.get(StorableTestBasic.class, "+stringProp", "+doubleProp"),
 +                     result.getTotalOrdering());
 +
 +        QueryExecutor<StorableTestBasic> exec = result.createExecutor();
 +        assertEquals(OrderingList.get(StorableTestBasic.class, "+stringProp", "+doubleProp"),
 +                     exec.getOrdering());
 +
 +        List<IndexedQueryAnalyzer<Shipment>.Result> subResults = result.getSubResults();
 +
 +        assertEquals(2, subResults.size());
 +
 +        StringBuffer buf = new StringBuffer();
 +        exec.printPlan(buf, 0, null);
 +        String plan = buf.toString();
 +
 +        String expected =
 +            "union\n" +
 +            "  index scan: com.amazon.carbonado.stored.StorableTestBasic\n" +
 +            "  ...index: {properties=[+stringProp, +doubleProp], unique=true}\n" +
 +            "  ...identity filter: stringProp = ?\n" +
 +            "  index scan: com.amazon.carbonado.stored.StorableTestBasic\n" +
 +            "  ...index: {properties=[+stringProp, +doubleProp], unique=true}\n" +
 +            "  ...identity filter: stringProp = ?[2]\n";
 +
 +        // Test test will fail if the format of the plan changes.
 +        assertEquals(expected, plan);
 +    }
 +
 +    public void testComplexUnionPlan4() throws Exception {
 +        UnionQueryAnalyzer uqa =
 +            new UnionQueryAnalyzer(StorableTestBasic.class,
 +                                   TestIndexedQueryAnalyzer.RepoAccess.INSTANCE);
 +        Filter<StorableTestBasic> filter = Filter.filterFor
 +            (StorableTestBasic.class, "doubleProp = ? | stringProp = ? | id > ?");
 +        filter = filter.bind();
 +        UnionQueryAnalyzer.Result result = uqa.analyze(filter, null);
 +        assertEquals(OrderingList.get(StorableTestBasic.class, "+doubleProp", "+id"),
 +                     result.getTotalOrdering());
 +
 +        QueryExecutor<StorableTestBasic> exec = result.createExecutor();
 +
 +        assertEquals(filter, exec.getFilter());
 +        assertEquals(OrderingList.get(StorableTestBasic.class, "+doubleProp", "+id"),
 +                     exec.getOrdering());
 +
 +        List<IndexedQueryAnalyzer<Shipment>.Result> subResults = result.getSubResults();
 +
 +        assertEquals(3, subResults.size());
 +
 +        StringBuffer buf = new StringBuffer();
 +        exec.printPlan(buf, 0, null);
 +        String plan = buf.toString();
 +
 +        String expected =
 +            "union\n" +
 +            "  sort: [+id]\n" +
 +            "    index scan: com.amazon.carbonado.stored.StorableTestBasic\n" +
 +            "    ...index: {properties=[-doubleProp, +longProp, ~id], unique=true}\n" +
 +            "    ...identity filter: doubleProp = ?\n" +
 +            "  sort: [+doubleProp], [+id]\n" +
 +            "    index scan: com.amazon.carbonado.stored.StorableTestBasic\n" +
 +            "    ...index: {properties=[+stringProp, +doubleProp], unique=true}\n" +
 +            "    ...identity filter: stringProp = ?\n" +
 +            "  sort: [+doubleProp, +id]\n" +
 +            "    clustered index scan: com.amazon.carbonado.stored.StorableTestBasic\n" +
 +            "    ...index: {properties=[+id], unique=true}\n" +
 +            "    ...range filter: id > ?\n";
 +
 +        // Test test will fail if the format of the plan changes.
 +        assertEquals(expected, plan);
 +    }
 +
 +    public void testComplexUnionPlan5() throws Exception {
 +        UnionQueryAnalyzer uqa =
 +            new UnionQueryAnalyzer(StorableTestBasic.class,
 +                                   TestIndexedQueryAnalyzer.RepoAccess.INSTANCE);
 +        Filter<StorableTestBasic> filter = Filter.filterFor
 +            (StorableTestBasic.class, "stringProp = ? | stringProp = ? | id > ?");
 +        filter = filter.bind();
 +        UnionQueryAnalyzer.Result result = uqa.analyze(filter, null);
 +        assertEquals(OrderingList.get(StorableTestBasic.class, "+stringProp", "+doubleProp"),
 +                     result.getTotalOrdering());
 +
 +        QueryExecutor<StorableTestBasic> exec = result.createExecutor();
 +
 +        assertEquals(filter, exec.getFilter());
 +        assertEquals(OrderingList.get(StorableTestBasic.class, "+stringProp", "+doubleProp"),
 +                     exec.getOrdering());
 +
 +        List<IndexedQueryAnalyzer<Shipment>.Result> subResults = result.getSubResults();
 +
 +        assertEquals(3, subResults.size());
 +
 +        StringBuffer buf = new StringBuffer();
 +        exec.printPlan(buf, 0, null);
 +        String plan = buf.toString();
 +
 +        String expected =
 +            "union\n" +
 +            "  index scan: com.amazon.carbonado.stored.StorableTestBasic\n" +
 +            "  ...index: {properties=[+stringProp, +doubleProp], unique=true}\n" +
 +            "  ...identity filter: stringProp = ?\n" +
 +            "  index scan: com.amazon.carbonado.stored.StorableTestBasic\n" +
 +            "  ...index: {properties=[+stringProp, +doubleProp], unique=true}\n" +
 +            "  ...identity filter: stringProp = ?[2]\n" +
 +            "  sort: [+stringProp, +doubleProp]\n" +
 +            "    clustered index scan: com.amazon.carbonado.stored.StorableTestBasic\n" +
 +            "    ...index: {properties=[+id], unique=true}\n" +
 +            "    ...range filter: id > ?\n";
 +
 +        // Test test will fail if the format of the plan changes.
 +        assertEquals(expected, plan);
 +    }
 +}
 diff --git a/src/test/java/com/amazon/carbonado/qe/TestUnionQueryExecutor.java b/src/test/java/com/amazon/carbonado/qe/TestUnionQueryExecutor.java new file mode 100644 index 0000000..9fccbc7 --- /dev/null +++ b/src/test/java/com/amazon/carbonado/qe/TestUnionQueryExecutor.java @@ -0,0 +1,75 @@ +/*
 + * Copyright 2006 Amazon Technologies, Inc. or its affiliates.
 + * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
 + * of Amazon Technologies, Inc. or its affiliates.  All rights reserved.
 + *
 + * Licensed under the Apache License, Version 2.0 (the "License");
 + * you may not use this file except in compliance with the License.
 + * You may obtain a copy of the License at
 + *
 + *     http://www.apache.org/licenses/LICENSE-2.0
 + *
 + * Unless required by applicable law or agreed to in writing, software
 + * distributed under the License is distributed on an "AS IS" BASIS,
 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 + * See the License for the specific language governing permissions and
 + * limitations under the License.
 + */
 +
 +package com.amazon.carbonado.qe;
 +
 +import java.util.List;
 +
 +import junit.framework.TestSuite;
 +
 +import com.amazon.carbonado.filter.Filter;
 +import com.amazon.carbonado.filter.FilterValues;
 +
 +import com.amazon.carbonado.info.OrderedProperty;
 +
 +import com.amazon.carbonado.stored.Address;
 +
 +/**
 + *
 + *
 + * @author Brian S O'Neill
 + */
 +public class TestUnionQueryExecutor extends TestQueryExecutor {
 +    public static void main(String[] args) {
 +        junit.textui.TestRunner.run(suite());
 +    }
 +
 +    public static TestSuite suite() {
 +        return new TestSuite(TestUnionQueryExecutor.class);
 +    }
 +
 +    public void testBasicUnion() throws Exception {
 +        QueryExecutor<Address> primary = new SortedQueryExecutor<Address>
 +            (null, createExecutor(1, 2, 3, 4, 5, 6, 7, 8), null, createOrdering("addressID"));
 +
 +        Filter<Address> filter_1 = Filter.filterFor(Address.class, "addressCountry > ?");
 +        FilterValues<Address> values_1 = filter_1.initialFilterValues();
 +        QueryExecutor<Address> executor_1 = new FilteredQueryExecutor<Address>(primary, filter_1);
 +
 +        Filter<Address> filter_2 = Filter.filterFor(Address.class, "addressState <= ?");
 +        FilterValues<Address> values_2 = filter_2.initialFilterValues();
 +        QueryExecutor<Address> executor_2 = new FilteredQueryExecutor<Address>(primary, filter_2);
 +
 +        QueryExecutor<Address> union = new UnionQueryExecutor<Address>(executor_1, executor_2);
 +
 +        Filter<Address> filter = Filter
 +            .filterFor(Address.class, "addressCountry > ? | addressState <= ?");
 +        FilterValues<Address> values = filter.initialFilterValues();
 +        filter = values.getFilter();
 +
 +        assertEquals(filter, union.getFilter());
 +
 +        values = values.with("country_6").with("state_3");
 +
 +        assertEquals(5, union.count(values));
 +
 +        assertEquals(primary.getOrdering(), union.getOrdering());
 +
 +        compareElements(union.fetch(values), 1, 2, 3, 7, 8);
 +    }
 +}
  | 
