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