From 3df73d90a65115d9fdcf4b8596971e957c6bce2d Mon Sep 17 00:00:00 2001 From: "Brian S. O'Neill" Date: Sat, 14 Oct 2006 16:47:22 +0000 Subject: Moved tests to separate project. --- .../carbonado/qe/TestFilteredQueryExecutor.java | 57 ++ .../amazon/carbonado/qe/TestFilteringScore.java | 746 ++++++++++++++++++++ .../carbonado/qe/TestIndexedQueryAnalyzer.java | 460 +++++++++++++ .../carbonado/qe/TestIndexedQueryExecutor.java | 754 +++++++++++++++++++++ .../carbonado/qe/TestJoinedQueryExecutor.java | 298 ++++++++ .../com/amazon/carbonado/qe/TestOrderingList.java | 225 ++++++ .../com/amazon/carbonado/qe/TestOrderingScore.java | 673 ++++++++++++++++++ .../carbonado/qe/TestPropertyFilterList.java | 108 +++ .../com/amazon/carbonado/qe/TestQueryExecutor.java | 186 +++++ .../carbonado/qe/TestSortedQueryExecutor.java | 84 +++ .../carbonado/qe/TestUnionQueryAnalyzer.java | 565 +++++++++++++++ .../carbonado/qe/TestUnionQueryExecutor.java | 75 ++ 12 files changed, 4231 insertions(+) create mode 100644 src/test/java/com/amazon/carbonado/qe/TestFilteredQueryExecutor.java create mode 100644 src/test/java/com/amazon/carbonado/qe/TestFilteringScore.java create mode 100644 src/test/java/com/amazon/carbonado/qe/TestIndexedQueryAnalyzer.java create mode 100644 src/test/java/com/amazon/carbonado/qe/TestIndexedQueryExecutor.java create mode 100644 src/test/java/com/amazon/carbonado/qe/TestJoinedQueryExecutor.java create mode 100644 src/test/java/com/amazon/carbonado/qe/TestOrderingList.java create mode 100644 src/test/java/com/amazon/carbonado/qe/TestOrderingScore.java create mode 100644 src/test/java/com/amazon/carbonado/qe/TestPropertyFilterList.java create mode 100644 src/test/java/com/amazon/carbonado/qe/TestQueryExecutor.java create mode 100644 src/test/java/com/amazon/carbonado/qe/TestSortedQueryExecutor.java create mode 100644 src/test/java/com/amazon/carbonado/qe/TestUnionQueryAnalyzer.java create mode 100644 src/test/java/com/amazon/carbonado/qe/TestUnionQueryExecutor.java (limited to 'src/test/java/com/amazon/carbonado/qe') 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
unfiltered = createExecutor(1, 2, 3, 4); + Filter
filter = Filter.filterFor(Address.class, "addressCountry > ?"); + FilterValues
values = filter.initialFilterValues(); + + QueryExecutor
executor = new FilteredQueryExecutor
(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 StorableIndex makeIndex(Class type, String... props) { + return TestOrderingScore.makeIndex(type, props); + } + + public TestFilteringScore(String name) { + super(name); + } + + public void testNoFilter() throws Exception { + StorableIndex ix = makeIndex(StorableTestBasic.class, "id"); + Filter 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 ix = makeIndex(StorableTestBasic.class, "intProp"); + // Filter by a property not in index. + Filter 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 ix = makeIndex(StorableTestBasic.class, "id"); + // Filter by a property in index. + Filter 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 ix = makeIndex(StorableTestBasic.class, + "id", "intProp", "stringProp"); + // Filter by a property in index. + Filter 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 ix = makeIndex(StorableTestBasic.class, + "id", "intProp"); + Filter 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 ix_1, ix_2; + Filter filter; + FilteringScore score_1, score_2; + Comparator> 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 ix_1, ix_2; + Filter filter; + FilteringScore score_1, score_2; + Comparator> 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 ix_1, ix_2; + Filter 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> 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 ix = makeIndex(StorableTestBasic.class, "id"); + Filter filter; + FilteringScore score; + + filter = Filter.filterFor(StorableTestBasic.class, "id >= ? & id >= ? & id < ? & id <= ?"); + + score = FilteringScore.evaluate(ix, filter); + + assertEquals(2, score.getRangeStartFilters().size()); + + List> exStart = score.getExclusiveRangeStartFilters(); + assertEquals(0, exStart.size()); + + List> 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> exEnd = score.getExclusiveRangeEndFilters(); + assertEquals(1, exEnd.size()); + assertEquals(Filter.filterFor(StorableTestBasic.class, "id < ?"), exEnd.get(0)); + + List> inEnd = score.getInclusiveRangeEndFilters(); + assertEquals(1, inEnd.size()); + assertEquals(Filter.filterFor(StorableTestBasic.class, "id <= ?"), inEnd.get(0)); + } + + public void testKeyMatch() throws Exception { + StorableIndex ix = makeIndex(StorableTestBasic.class, "id", "intProp"); + ix = ix.unique(true); + Filter 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 StorableIndex makeIndex(Class 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
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
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 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> 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 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 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 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 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 iqa = + new IndexedQueryAnalyzer(Shipment.class, RepoAccess.INSTANCE); + Filter filter = Filter.filterFor + (Shipment.class, "order.address.addressState = ? & order.address.addressZip = ?"); + FilterValues values = filter.initialFilterValues(); + filter = values.getFilter(); + IndexedQueryAnalyzer.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 joinExec = JoinedQueryExecutor.build + (RepoAccess.INSTANCE, + result.getForeignProperty(), result.getFilter(), result.getOrdering()); + + FilterValues 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 iqa = + new IndexedQueryAnalyzer(Shipment.class, RepoAccess.INSTANCE); + Filter filter = Filter.filterFor + (Shipment.class, + "order.address.addressState = ? & order.address.addressID != ? " + + "& order.address.addressZip = ? & order.orderTotal > ? & shipmentNotes <= ? " + + "& order.addressID > ?"); + FilterValues values = filter.initialFilterValues(); + filter = values.getFilter(); + OrderingList 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 joinExec = JoinedQueryExecutor.build + (RepoAccess.INSTANCE, + result.getForeignProperty(), result.getFilter(), result.getOrdering()); + + FilterValues 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 joinExec2 = result.createExecutor(); + + StringBuffer buf2 = new StringBuffer(); + joinExec2.printPlan(buf2, 0, fv); + String plan2 = buf2.toString(); + + assertEquals(expected, plan2); + + Filter 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 StorageAccess storageAccessFor(Class type) { + return new StoreAccess(type); + } + } + + /** + * Partially implemented StorageAccess which only supplies information + * about indexes. + */ + static class StoreAccess + implements StorageAccess, QueryExecutorFactory + { + private final Class mType; + + StoreAccess(Class type) { + mType = type; + } + + public Class getStorableType() { + return mType; + } + + public QueryExecutorFactory getQueryExecutorFactory() { + return this; + } + + public QueryExecutor executor(Filter filter, OrderingList ordering) { + Iterable iterable = Collections.emptyList(); + + QueryExecutor exec = new IterableQueryExecutor + (filter.getStorableType(), iterable); + + if (filter != null) { + exec = new FilteredQueryExecutor(exec, filter); + } + + if (ordering != null && ordering.size() > 0) { + exec = new SortedQueryExecutor(null, exec, null, ordering); + } + + return exec; + } + + public Collection> getAllIndexes() { + StorableIndex[] 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 storageDelegate(StorableIndex index) { + return null; + } + + public SortBuffer createSortBuffer() { + return new ArraySortBuffer(); + } + + public long countAll() { + throw new UnsupportedOperationException(); + } + + public Cursor fetchAll() { + throw new UnsupportedOperationException(); + } + + public Cursor fetchOne(StorableIndex index, Object[] identityValues) { + throw new UnsupportedOperationException(); + } + + public Cursor fetchSubset(StorableIndex 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 StorableIndex makeIndex(Class type, String... props) { + return TestOrderingScore.makeIndex(type, props); + } + + static OrderingList makeOrdering(Class type, String... props) { + return TestOrderingScore.makeOrdering(type, props); + } + + public TestIndexedQueryExecutor(String name) { + super(name); + } + + public void testIdentityMatch() throws Exception { + StorableIndex index = + makeIndex(StorableTestBasic.class, "id", "-intProp", "doubleProp"); + + Filter filter = + Filter.filterFor(StorableTestBasic.class, "id = ?"); + FilterValues values = filter.initialFilterValues(); + filter = values.getFilter(); + + CompositeScore score = CompositeScore.evaluate(index, filter, null); + + Mock executor = new Mock(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(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(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 index = makeIndex(StorableTestBasic.class, "intProp"); + + Filter filter = + Filter.filterFor(StorableTestBasic.class, "intProp > ?"); + FilterValues values = filter.initialFilterValues(); + filter = values.getFilter(); + + CompositeScore score = CompositeScore.evaluate(index, filter, null); + + Mock executor = new Mock(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(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(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(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(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(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(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(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(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(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 index = makeIndex(StorableTestBasic.class, "intProp"); + + Filter filter = + Filter.filterFor(StorableTestBasic.class, "intProp < ?"); + FilterValues values = filter.initialFilterValues(); + filter = values.getFilter(); + + CompositeScore score = CompositeScore.evaluate(index, filter, null); + + Mock executor = new Mock(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(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(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(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(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(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(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(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(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(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 index = makeIndex(StorableTestBasic.class, "intProp"); + + Filter filter = + Filter.filterFor(StorableTestBasic.class, "intProp > ? & intProp < ?"); + FilterValues values = filter.initialFilterValues(); + filter = values.getFilter(); + + CompositeScore score = CompositeScore.evaluate(index, filter, null); + + Mock executor = new Mock(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(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(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 index; + Filter filter; + FilterValues values; + CompositeScore score; + Mock 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(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(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(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(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> 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 index; + Filter filter; + FilterValues values; + CompositeScore score; + Mock 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(index, score); + + assertEquals(values.getFilter(), executor.getFilter()); + List> 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 extends IndexedQueryExecutor + implements IndexedQueryExecutor.Support + { + Object[] mIdentityValues; + BoundaryType mRangeStartBoundary; + Object mRangeStartValue; + BoundaryType mRangeEndBoundary; + Object mRangeEndValue; + boolean mReverseRange; + boolean mReverseOrder; + + public Mock(StorableIndex index, CompositeScore score) { + super(null, index, score); + } + + public Cursor fetchSubset(StorableIndex 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 empty = Collections.emptyList(); + return new IteratorCursor(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 info = StorableIntrospector.examine(UserInfo.class); + Map> properties = info.getAllProperties(); + + RepositoryAccess repoAccess = new RepoAccess(); + + ChainedProperty targetToSourceProperty = + ChainedProperty.get(properties.get("address")); + + Filter targetFilter = Filter.filterFor(UserInfo.class, "address.state = ?"); + OrderingList targetOrdering = + OrderingList.get(UserInfo.class, "+address.country"); + + QueryExecutor 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 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 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 values = Filter + .filterFor(UserInfo.class, "address.state = ?").initialFilterValues().with("IL"); + + Cursor 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 StorageAccess storageAccessFor(Class type) { + return new StoreAccess(type); + } + } + + class StoreAccess implements StorageAccess, QueryExecutorFactory { + private final Class mType; + + StoreAccess(Class type) { + mType = type; + } + + public Class getStorableType() { + return mType; + } + + public QueryExecutorFactory getQueryExecutorFactory() { + return this; + } + + public QueryExecutor executor(Filter filter, OrderingList ordering) + throws RepositoryException + { + Storage storage = mRepository.storageFor(mType); + + QueryExecutor exec = new FullScanQueryExecutor + (new ScanQuerySupport(storage.query())); + + if (filter != null) { + exec = new FilteredQueryExecutor(exec, filter); + } + + if (ordering != null && ordering.size() > 0) { + exec = new SortedQueryExecutor(null, exec, null, ordering); + } + + return exec; + } + + public Collection> getAllIndexes() { + StorableIndex[] indexes = new StorableIndex[0]; + return Arrays.asList(indexes); + } + + public Storage storageDelegate(StorableIndex index) { + return null; + } + + public SortBuffer createSortBuffer() { + return new ArraySortBuffer(); + } + + public long countAll() { + throw new UnsupportedOperationException(); + } + + public Cursor fetchAll() { + throw new UnsupportedOperationException(); + } + + public Cursor fetchOne(StorableIndex index, Object[] identityValues) { + throw new UnsupportedOperationException(); + } + + public Cursor fetchSubset(StorableIndex index, + Object[] identityValues, + BoundaryType rangeStartBoundary, + Object rangeStartValue, + BoundaryType rangeEndBoundary, + Object rangeEndValue, + boolean reverseRange, + boolean reverseOrder) + { + throw new UnsupportedOperationException(); + } + } + + static class ScanQuerySupport implements FullScanQueryExecutor.Support { + private final Query mQuery; + + ScanQuerySupport(Query query) { + mQuery = query; + } + + public Class getStorableType() { + return mQuery.getStorableType(); + } + + public long countAll() throws FetchException { + return mQuery.count(); + } + + public Cursor 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> list_1 = + OrderingList.get(StorableTestBasic.class, "date"); + + assertEquals(1, list_1.size()); + assertEquals("+date", list_1.get(0).toString()); + + List> 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> 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> 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> 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> 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> list = + OrderingList.get(StorableTestBasic.class, "~date"); + try { + list.set(0, list.get(0).reverse()); + fail(); + } catch (UnsupportedOperationException e) { + } + } + + public void testConcatList() throws Exception { + OrderingList list_1 = + OrderingList.get(StorableTestBasic.class, "date", "-intProp", "~stringProp"); + + OrderingList list_2 = + OrderingList.get(StorableTestBasic.class, "longProp", "doubleProp"); + + OrderingList 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 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 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 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 list_1 = + OrderingList.get(StorableTestBasic.class, "date", "-intProp", "~stringProp"); + + OrderedProperty op_0 = list_1.get(0); + OrderedProperty op_1 = list_1.get(1); + OrderedProperty 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 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 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 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 list = + OrderingList.get(StorableTestBasic.class, "date", "intProp", "stringProp"); + + OrderedProperty[] 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 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 StorableIndex makeIndex(Class type, String... props) { + return new StorableIndex(makeOrdering(type, props).asArray(), UNSPECIFIED); + } + + static OrderingList makeOrdering(Class type, String... props) { + return OrderingList.get(type, props); + } + + public TestOrderingScore(String name) { + super(name); + } + + public void testEmpty() throws Exception { + StorableIndex 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 ix; + OrderingList ops; + OrderingScore 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 ix; + OrderingList ops; + OrderingScore 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 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 ix; + OrderingList ops; + OrderingScore score; + Filter 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 ix_1, ix_2; + OrderingList ops; + OrderingScore score_1, score_2; + Filter filter; + Comparator> 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 ix; + OrderingList ops; + OrderingScore score; + Filter 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 ix; + OrderingList ops; + OrderingScore score; + Filter 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 ix; + OrderingList ops; + OrderingScore score; + Filter 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 ix; + OrderingList ops; + OrderingScore score; + Filter 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 ix; + OrderingList ops; + OrderingScore score; + Filter 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 ix; + OrderingList ops; + OrderingScore score; + Filter 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 filter = Filter.getOpenFilter(StorableTestBasic.class); + assertEquals(0, PropertyFilterList.get(filter).size()); + } + + public void testClosed() throws Exception { + Filter filter = Filter.getClosedFilter(StorableTestBasic.class); + assertEquals(0, PropertyFilterList.get(filter).size()); + } + + public void testSingleton() throws Exception { + Filter filter = Filter.filterFor(StorableTestBasic.class, "id = ?"); + + List> list = PropertyFilterList.get(filter); + + assertEquals(1, list.size()); + assertEquals(filter, list.get(0)); + + List> list2 = PropertyFilterList.get(filter); + + assertTrue(list == list2); + } + + public void testMultiple() throws Exception { + Filter filter = + Filter.filterFor(StorableTestBasic.class, "id = ? & intProp > ?"); + + List> list = PropertyFilterList.get(filter); + + assertEquals(2, list.size()); + + Filter subFilter = + Filter.filterFor(StorableTestBasic.class, "id = ?"); + + assertEquals(subFilter, list.get(0)); + + subFilter = Filter.filterFor(StorableTestBasic.class, "intProp > ?"); + + assertEquals(subFilter, list.get(1)); + + List> list2 = PropertyFilterList.get(filter); + + assertTrue(list == list2); + } + + public void testIllegal() throws Exception { + Filter filter = + Filter.filterFor(StorableTestBasic.class, "id = ? | intProp > ?"); + + try { + List> 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
createExecutor(int... ids) { + return new IterableQueryExecutor
(Address.class, createCollection(ids)); + } + + protected Collection
createCollection(int... ids) { + Collection
elements = new ArrayList
(ids.length); + for (int i=0; i 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
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
unsorted = createExecutor(4, 2, 3, 1); + Filter
filter = Filter.getOpenFilter(Address.class); + FilterValues
values = filter.initialFilterValues(); + OrderingList
ordered = createOrdering("addressCountry"); + + QueryExecutor
executor = + new SortedQueryExecutor
(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
unsorted = createExecutor(1, 2, 3, 4); + Filter
filter = Filter.getOpenFilter(Address.class); + FilterValues
values = filter.initialFilterValues(); + OrderingList
handled = createOrdering("addressCountry"); + OrderingList
finisher = createOrdering("addressState"); + + QueryExecutor
executor = + new SortedQueryExecutor
(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 StorableIndex makeIndex(Class type, String... props) { + return TestOrderingScore.makeIndex(type, props); + } + + static OrderingList makeOrdering(Class 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.Result> subResults = result.getSubResults(); + + assertEquals(1, subResults.size()); + } + + public void testSingleSubResult() throws Exception { + UnionQueryAnalyzer uqa = + new UnionQueryAnalyzer(Shipment.class, TestIndexedQueryAnalyzer.RepoAccess.INSTANCE); + Filter filter = Filter.filterFor(Shipment.class, "shipmentID = ?"); + filter = filter.bind(); + UnionQueryAnalyzer.Result result = uqa.analyze(filter, null); + List.Result> subResults = result.getSubResults(); + + assertEquals(1, subResults.size()); + } + + public void testSingleSubResultUnspecifiedDirection() throws Exception { + UnionQueryAnalyzer uqa = + new UnionQueryAnalyzer(Shipment.class, TestIndexedQueryAnalyzer.RepoAccess.INSTANCE); + Filter filter = Filter.filterFor(Shipment.class, "shipmentID > ?"); + filter = filter.bind(); + OrderingList orderings = + makeOrdering(Shipment.class, "~shipmentID", "~orderID"); + UnionQueryAnalyzer.Result result = uqa.analyze(filter, orderings); + List.Result> subResults = result.getSubResults(); + + assertEquals(1, subResults.size()); + List> 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 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.Result> subResults = result.getSubResults(); + + assertEquals(2, subResults.size()); + IndexedQueryAnalyzer.Result res_0 = subResults.get(0); + IndexedQueryAnalyzer.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 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.Result> subResults = result.getSubResults(); + + assertEquals(2, subResults.size()); + IndexedQueryAnalyzer.Result res_0 = subResults.get(0); + IndexedQueryAnalyzer.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 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.Result> subResults = result.getSubResults(); + + assertEquals(2, subResults.size()); + IndexedQueryAnalyzer.Result res_0 = subResults.get(0); + IndexedQueryAnalyzer.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> 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 filter = Filter.filterFor(Shipment.class, + "shipmentID > ? | orderID = ?"); + filter = filter.bind(); + OrderingList orderings = + makeOrdering(Shipment.class, "~shipmentID", "~orderID"); + UnionQueryAnalyzer.Result result = uqa.analyze(filter, orderings); + assertEquals(OrderingList.get(Shipment.class, "+shipmentID", "+orderID"), + result.getTotalOrdering()); + List.Result> subResults = result.getSubResults(); + + assertEquals(2, subResults.size()); + IndexedQueryAnalyzer.Result res_0 = subResults.get(0); + IndexedQueryAnalyzer.Result res_1 = subResults.get(1); + + List> 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 filter = Filter.filterFor + (Shipment.class, + "shipmentID = ? & (shipmentID = ? | orderID = ?)"); + filter = filter.bind(); + UnionQueryAnalyzer.Result result = uqa.analyze(filter, null); + List.Result> subResults = result.getSubResults(); + + assertEquals(1, subResults.size()); + IndexedQueryAnalyzer.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 filter = Filter.filterFor + (Shipment.class, "shipmentNotes = ? | shipperID = ?"); + filter = filter.bind(); + UnionQueryAnalyzer.Result result = uqa.analyze(filter, null); + List.Result> subResults = result.getSubResults(); + + assertEquals(1, subResults.size()); + IndexedQueryAnalyzer.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 filter = Filter.filterFor + (Shipment.class, "shipmentNotes = ? | orderID = ?"); + filter = filter.bind(); + UnionQueryAnalyzer.Result result = uqa.analyze(filter, null); + List.Result> subResults = result.getSubResults(); + + assertEquals(1, subResults.size()); + IndexedQueryAnalyzer.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 filter = Filter.filterFor + (Shipment.class, "shipmentNotes = ? | orderID = ? & order.orderTotal > ?"); + filter = filter.bind(); + UnionQueryAnalyzer.Result result = uqa.analyze(filter, null); + List.Result> subResults = result.getSubResults(); + + assertEquals(2, subResults.size()); + IndexedQueryAnalyzer.Result res_0 = subResults.get(0); + IndexedQueryAnalyzer.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 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 exec = result.createExecutor(); + + assertEquals(filter, exec.getFilter()); + assertEquals(OrderingList.get(StorableTestBasic.class, "+id"), exec.getOrdering()); + + List.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 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 exec = result.createExecutor(); + + assertEquals(filter, exec.getFilter()); + assertEquals(OrderingList.get(StorableTestBasic.class, "+doubleProp", "+stringProp"), + exec.getOrdering()); + + List.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 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 exec = result.createExecutor(); + assertEquals(OrderingList.get(StorableTestBasic.class, "+stringProp", "+doubleProp"), + exec.getOrdering()); + + List.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 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 exec = result.createExecutor(); + + assertEquals(filter, exec.getFilter()); + assertEquals(OrderingList.get(StorableTestBasic.class, "+doubleProp", "+id"), + exec.getOrdering()); + + List.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 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 exec = result.createExecutor(); + + assertEquals(filter, exec.getFilter()); + assertEquals(OrderingList.get(StorableTestBasic.class, "+stringProp", "+doubleProp"), + exec.getOrdering()); + + List.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
primary = new SortedQueryExecutor
+ (null, createExecutor(1, 2, 3, 4, 5, 6, 7, 8), null, createOrdering("addressID")); + + Filter
filter_1 = Filter.filterFor(Address.class, "addressCountry > ?"); + FilterValues
values_1 = filter_1.initialFilterValues(); + QueryExecutor
executor_1 = new FilteredQueryExecutor
(primary, filter_1); + + Filter
filter_2 = Filter.filterFor(Address.class, "addressState <= ?"); + FilterValues
values_2 = filter_2.initialFilterValues(); + QueryExecutor
executor_2 = new FilteredQueryExecutor
(primary, filter_2); + + QueryExecutor
union = new UnionQueryExecutor
(executor_1, executor_2); + + Filter
filter = Filter + .filterFor(Address.class, "addressCountry > ? | addressState <= ?"); + FilterValues
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); + } +} -- cgit v1.2.3