From ee3de4cabc79aabf21197b24af6d233e5e4a50a4 Mon Sep 17 00:00:00 2001 From: "Brian S. O'Neill" Date: Wed, 30 Aug 2006 21:12:19 +0000 Subject: Added small set of tests that don't need an actual Repository instance. --- .../carbonado/qe/TestFilteredQueryExecutor.java | 57 ++ .../amazon/carbonado/qe/TestFilteringScore.java | 746 +++++++++++++++++++++ .../carbonado/qe/TestIndexedQueryExecutor.java | 725 ++++++++++++++++++++ .../com/amazon/carbonado/qe/TestOrderingScore.java | 517 ++++++++++++++ .../carbonado/qe/TestPropertyFilterList.java | 108 +++ .../com/amazon/carbonado/qe/TestQueryExecutor.java | 195 ++++++ .../carbonado/qe/TestSortedQueryExecutor.java | 84 +++ .../carbonado/qe/TestUnionQueryExecutor.java | 75 +++ 8 files changed, 2507 insertions(+) create mode 100644 src/test/java/com/amazon/carbonado/qe/TestFilteredQueryExecutor.java create mode 100644 src/test/java/com/amazon/carbonado/qe/TestFilteringScore.java create mode 100644 src/test/java/com/amazon/carbonado/qe/TestIndexedQueryExecutor.java create mode 100644 src/test/java/com/amazon/carbonado/qe/TestOrderingScore.java create mode 100644 src/test/java/com/amazon/carbonado/qe/TestPropertyFilterList.java create mode 100644 src/test/java/com/amazon/carbonado/qe/TestQueryExecutor.java create mode 100644 src/test/java/com/amazon/carbonado/qe/TestSortedQueryExecutor.java create mode 100644 src/test/java/com/amazon/carbonado/qe/TestUnionQueryExecutor.java (limited to 'src/test/java/com/amazon/carbonado/qe') diff --git a/src/test/java/com/amazon/carbonado/qe/TestFilteredQueryExecutor.java b/src/test/java/com/amazon/carbonado/qe/TestFilteredQueryExecutor.java new file mode 100644 index 0000000..76ea081 --- /dev/null +++ b/src/test/java/com/amazon/carbonado/qe/TestFilteredQueryExecutor.java @@ -0,0 +1,57 @@ +/* + * Copyright 2006 Amazon Technologies, Inc. or its affiliates. + * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks + * of Amazon Technologies, Inc. or its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.amazon.carbonado.qe; + +import junit.framework.TestSuite; + +import com.amazon.carbonado.filter.Filter; +import com.amazon.carbonado.filter.FilterValues; + +import com.amazon.carbonado.stored.Address; + +/** + * + * + * @author Brian S O'Neill + */ +public class TestFilteredQueryExecutor extends TestQueryExecutor { + public static void main(String[] args) { + junit.textui.TestRunner.run(suite()); + } + + public static TestSuite suite() { + return new TestSuite(TestFilteredQueryExecutor.class); + } + + public void testBasicFiltering() throws Exception { + QueryExecutor
unfiltered = createExecutor(1, 2, 3, 4); + Filter
filter = Filter.filterFor(Address.class, "addressCountry > ?"); + FilterValues
values = filter.initialFilterValues(); + + QueryExecutor
executor = new FilteredQueryExecutor
(unfiltered, filter); + + assertEquals(values.getFilter(), executor.getFilter()); + + assertEquals(2, executor.count(values.with("country_2"))); + + assertEquals(0, executor.getOrdering().size()); + + compareElements(executor.openCursor(values.with("country_2")), 3, 4); + } +} diff --git a/src/test/java/com/amazon/carbonado/qe/TestFilteringScore.java b/src/test/java/com/amazon/carbonado/qe/TestFilteringScore.java new file mode 100644 index 0000000..e6b4374 --- /dev/null +++ b/src/test/java/com/amazon/carbonado/qe/TestFilteringScore.java @@ -0,0 +1,746 @@ +/* + * Copyright 2006 Amazon Technologies, Inc. or its affiliates. + * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks + * of Amazon Technologies, Inc. or its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.amazon.carbonado.qe; + +import java.util.Comparator; +import java.util.List; + +import junit.framework.TestCase; +import junit.framework.TestSuite; + +import com.amazon.carbonado.Storable; + +import com.amazon.carbonado.info.Direction; +import static com.amazon.carbonado.info.Direction.*; +import com.amazon.carbonado.info.OrderedProperty; +import com.amazon.carbonado.info.StorableIndex; +import com.amazon.carbonado.info.StorableInfo; +import com.amazon.carbonado.info.StorableIntrospector; +import com.amazon.carbonado.info.StorableProperty; + +import com.amazon.carbonado.filter.Filter; +import com.amazon.carbonado.filter.PropertyFilter; + +import com.amazon.carbonado.stored.StorableTestBasic; + +/** + * + * + * @author Brian S O'Neill + */ +public class TestFilteringScore extends TestCase { + public static void main(String[] args) { + junit.textui.TestRunner.run(suite()); + } + + public static TestSuite suite() { + return new TestSuite(TestFilteringScore.class); + } + + static StorableIndex makeIndex(Class type, String... props) { + return TestOrderingScore.makeIndex(type, props); + } + + public TestFilteringScore(String name) { + super(name); + } + + public void testNoFilter() throws Exception { + StorableIndex ix = makeIndex(StorableTestBasic.class, "id"); + Filter filter = Filter.getOpenFilter(StorableTestBasic.class); + FilteringScore score = FilteringScore.evaluate(ix, filter); + + assertFalse(score.isIndexClustered()); + assertEquals(1, score.getIndexPropertyCount()); + + assertEquals(0, score.getIdentityCount()); + assertEquals(0, score.getIdentityFilters().size()); + assertFalse(score.hasRangeStart()); + assertEquals(0, score.getRangeStartFilters().size()); + assertFalse(score.hasRangeEnd()); + assertEquals(0, score.getRangeEndFilters().size()); + assertFalse(score.hasRangeMatch()); + assertFalse(score.hasAnyMatches()); + assertEquals(0, score.getRemainderCount()); + assertEquals(0, score.getRemainderFilters().size()); + } + + public void testSimpleIndexMisses() throws Exception { + StorableIndex ix = makeIndex(StorableTestBasic.class, "intProp"); + // Filter by a property not in index. + Filter filter = Filter.filterFor(StorableTestBasic.class, "id = ?"); + + FilteringScore score = FilteringScore.evaluate(ix, filter); + + assertFalse(score.isIndexClustered()); + assertEquals(1, score.getIndexPropertyCount()); + + assertEquals(0, score.getIdentityCount()); + assertEquals(0, score.getIdentityFilters().size()); + assertFalse(score.hasRangeStart()); + assertEquals(0, score.getRangeStartFilters().size()); + assertFalse(score.hasRangeEnd()); + assertEquals(0, score.getRangeEndFilters().size()); + assertFalse(score.hasRangeMatch()); + assertFalse(score.hasAnyMatches()); + assertEquals(1, score.getRemainderCount()); + assertEquals(1, score.getRemainderFilters().size()); + assertEquals(filter, score.getRemainderFilters().get(0)); + assertEquals(filter, score.getRemainderFilter()); + + // Try again with matching property, but with an operator that cannot be used by index. + filter = Filter.filterFor(StorableTestBasic.class, "intProp != ?"); + + score = FilteringScore.evaluate(ix, filter); + + assertFalse(score.isIndexClustered()); + assertEquals(1, score.getIndexPropertyCount()); + + assertEquals(0, score.getIdentityCount()); + assertEquals(0, score.getIdentityFilters().size()); + assertFalse(score.hasRangeStart()); + assertEquals(0, score.getRangeStartFilters().size()); + assertFalse(score.hasRangeEnd()); + assertEquals(0, score.getRangeEndFilters().size()); + assertFalse(score.hasRangeMatch()); + assertFalse(score.hasAnyMatches()); + assertEquals(1, score.getRemainderCount()); + assertEquals(1, score.getRemainderFilters().size()); + assertEquals(filter, score.getRemainderFilters().get(0)); + assertEquals(filter, score.getRemainderFilter()); + } + + public void testSimpleIndexMatches() throws Exception { + StorableIndex ix = makeIndex(StorableTestBasic.class, "id"); + // Filter by a property in index. + Filter filter = Filter.filterFor(StorableTestBasic.class, "id = ?"); + + FilteringScore score = FilteringScore.evaluate(ix, filter); + + assertFalse(score.isIndexClustered()); + assertEquals(1, score.getIndexPropertyCount()); + + assertEquals(1, score.getIdentityCount()); + assertEquals(1, score.getArrangementScore()); + assertEquals(filter, score.getIdentityFilters().get(0)); + assertFalse(score.hasRangeStart()); + assertEquals(0, score.getRangeStartFilters().size()); + assertFalse(score.hasRangeEnd()); + assertEquals(0, score.getRangeEndFilters().size()); + assertFalse(score.hasRangeMatch()); + assertTrue(score.hasAnyMatches()); + assertEquals(0, score.getRemainderCount()); + assertEquals(0, score.getRemainderFilters().size()); + assertEquals(null, score.getRemainderFilter()); + + // Try again with open ranges. + for (int i=0; i<4; i++) { + String expr; + switch (i) { + default: expr = "id > ?"; break; + case 1: expr = "id >= ?"; break; + case 2: expr = "id < ?"; break; + case 3: expr = "id <= ?"; break; + } + + filter = Filter.filterFor(StorableTestBasic.class, expr); + + score = FilteringScore.evaluate(ix, filter); + + assertEquals(0, score.getIdentityCount()); + assertEquals(0, score.getIdentityFilters().size()); + assertFalse(score.hasRangeMatch()); + assertTrue(score.hasAnyMatches()); + assertEquals(0, score.getRemainderCount()); + assertEquals(0, score.getRemainderFilters().size()); + assertEquals(null, score.getRemainderFilter()); + + if (i < 2) { + assertTrue(score.hasRangeStart()); + assertEquals(1, score.getRangeStartFilters().size()); + assertEquals(filter, score.getRangeStartFilters().get(0)); + assertFalse(score.hasRangeEnd()); + assertEquals(0, score.getRangeEndFilters().size()); + } else { + assertFalse(score.hasRangeStart()); + assertEquals(0, score.getRangeStartFilters().size()); + assertTrue(score.hasRangeEnd()); + assertEquals(1, score.getRangeEndFilters().size()); + assertEquals(filter, score.getRangeEndFilters().get(0)); + } + } + + // Try with duplicate open ranges. + filter = Filter.filterFor(StorableTestBasic.class, "id > ? & id > ?"); + + score = FilteringScore.evaluate(ix, filter); + + assertEquals(0, score.getIdentityCount()); + assertEquals(0, score.getIdentityFilters().size()); + assertFalse(score.hasRangeMatch()); + assertTrue(score.hasAnyMatches()); + assertEquals(0, score.getRemainderCount()); + assertEquals(0, score.getRemainderFilters().size()); + assertEquals(null, score.getRemainderFilter()); + + assertTrue(score.hasRangeStart()); + assertEquals(2, score.getRangeStartFilters().size()); + assertEquals(Filter.filterFor(StorableTestBasic.class, "id > ?"), + score.getRangeStartFilters().get(0)); + assertEquals(Filter.filterFor(StorableTestBasic.class, "id > ?"), + score.getRangeStartFilters().get(1)); + assertFalse(score.hasRangeEnd()); + assertEquals(0, score.getRangeEndFilters().size()); + + // Try with duplicate end ranges and slightly different operator. + filter = Filter.filterFor(StorableTestBasic.class, "id < ? & id <= ?"); + + score = FilteringScore.evaluate(ix, filter); + + assertEquals(0, score.getIdentityCount()); + assertEquals(0, score.getIdentityFilters().size()); + assertFalse(score.hasRangeMatch()); + assertTrue(score.hasAnyMatches()); + assertEquals(0, score.getRemainderCount()); + assertEquals(0, score.getRemainderFilters().size()); + assertEquals(null, score.getRemainderFilter()); + + assertFalse(score.hasRangeStart()); + assertEquals(0, score.getRangeStartFilters().size()); + assertTrue(score.hasRangeEnd()); + assertEquals(2, score.getRangeEndFilters().size()); + assertEquals(Filter.filterFor(StorableTestBasic.class, "id < ?"), + score.getRangeEndFilters().get(0)); + assertEquals(Filter.filterFor(StorableTestBasic.class, "id <= ?"), + score.getRangeEndFilters().get(1)); + + // Try with complete range. + filter = Filter.filterFor(StorableTestBasic.class, "id > ? & id < ?"); + + score = FilteringScore.evaluate(ix, filter); + + assertEquals(0, score.getIdentityCount()); + assertEquals(0, score.getIdentityFilters().size()); + assertTrue(score.hasRangeMatch()); + assertTrue(score.hasAnyMatches()); + assertEquals(0, score.getRemainderCount()); + assertEquals(0, score.getRemainderFilters().size()); + assertEquals(null, score.getRemainderFilter()); + + assertTrue(score.hasRangeStart()); + assertEquals(1, score.getRangeStartFilters().size()); + assertEquals(Filter.filterFor(StorableTestBasic.class, "id > ?"), + score.getRangeStartFilters().get(0)); + assertTrue(score.hasRangeEnd()); + assertEquals(1, score.getRangeEndFilters().size()); + assertEquals(Filter.filterFor(StorableTestBasic.class, "id < ?"), + score.getRangeEndFilters().get(0)); + + // Try with an identity filter consuming others. + filter = Filter.filterFor(StorableTestBasic.class, "id > ? & id = ? & id = ?"); + + score = FilteringScore.evaluate(ix, filter); + + assertEquals(1, score.getIdentityCount()); + assertEquals(1, score.getIdentityFilters().size()); + assertEquals(1, score.getArrangementScore()); + assertEquals(Filter.filterFor(StorableTestBasic.class, "id = ?"), + score.getIdentityFilters().get(0)); + + assertFalse(score.hasRangeStart()); + assertFalse(score.hasRangeEnd()); + assertFalse(score.hasRangeMatch()); + assertTrue(score.hasAnyMatches()); + assertEquals(2, score.getRemainderCount()); + assertEquals(2, score.getRemainderFilters().size()); + // Order of properties in filter accounts for sorting by PropertyFilterList. + assertEquals(Filter.filterFor(StorableTestBasic.class, "id = ? & id > ?"), + score.getRemainderFilter()); + + // Try with complex mix of non-identity matches. + filter = Filter.filterFor + (StorableTestBasic.class, + "id > ? & id != ? & id <= ? & id >= ? & id != ? & id > ?"); + + score = FilteringScore.evaluate(ix, filter); + + assertEquals(0, score.getIdentityCount()); + assertEquals(0, score.getIdentityFilters().size()); + + assertTrue(score.hasRangeMatch()); + assertTrue(score.hasAnyMatches()); + + assertTrue(score.hasRangeStart()); + assertEquals(3, score.getRangeStartFilters().size()); + assertEquals(Filter.filterFor(StorableTestBasic.class, "id > ?"), + score.getRangeStartFilters().get(0)); + assertEquals(Filter.filterFor(StorableTestBasic.class, "id >= ?"), + score.getRangeStartFilters().get(1)); + assertEquals(Filter.filterFor(StorableTestBasic.class, "id > ?"), + score.getRangeStartFilters().get(2)); + + assertTrue(score.hasRangeEnd()); + assertEquals(1, score.getRangeEndFilters().size()); + assertEquals(Filter.filterFor(StorableTestBasic.class, "id <= ?"), + score.getRangeEndFilters().get(0)); + + assertEquals(2, score.getRemainderCount()); + assertEquals(2, score.getRemainderFilters().size()); + assertEquals(Filter.filterFor(StorableTestBasic.class, "id != ? & id != ?"), + score.getRemainderFilter()); + } + + public void testCompositeIndexMatches() throws Exception { + StorableIndex ix = makeIndex(StorableTestBasic.class, + "id", "intProp", "stringProp"); + // Filter by a property in index. + Filter filter = Filter.filterFor(StorableTestBasic.class, "id = ?"); + + FilteringScore score = FilteringScore.evaluate(ix, filter); + + assertFalse(score.isIndexClustered()); + assertEquals(3, score.getIndexPropertyCount()); + + assertEquals(1, score.getIdentityCount()); + assertEquals(1, score.getArrangementScore()); + assertEquals(filter, score.getIdentityFilters().get(0)); + assertFalse(score.hasRangeStart()); + assertEquals(0, score.getRangeStartFilters().size()); + assertFalse(score.hasRangeEnd()); + assertEquals(0, score.getRangeEndFilters().size()); + assertFalse(score.hasRangeMatch()); + assertTrue(score.hasAnyMatches()); + assertEquals(0, score.getRemainderCount()); + assertEquals(0, score.getRemainderFilters().size()); + assertEquals(null, score.getRemainderFilter()); + + // Filter by a property with a gap. (filter must include "id" to use index) + filter = Filter.filterFor(StorableTestBasic.class, "intProp = ?"); + + score = FilteringScore.evaluate(ix, filter); + + assertEquals(0, score.getIdentityCount()); + assertFalse(score.hasAnyMatches()); + assertEquals(1, score.getRemainderCount()); + assertEquals(1, score.getRemainderFilters().size()); + assertEquals(filter, score.getRemainderFilter()); + + // Filter by a property with a gap and a range operator. (index still cannot be used) + filter = Filter.filterFor(StorableTestBasic.class, "intProp = ? & stringProp < ?"); + + score = FilteringScore.evaluate(ix, filter); + + assertEquals(0, score.getIdentityCount()); + assertFalse(score.hasAnyMatches()); + assertEquals(2, score.getRemainderCount()); + assertEquals(2, score.getRemainderFilters().size()); + assertEquals(filter, score.getRemainderFilter()); + + // Filter with range match before identity match. Identity cannot be used. + filter = Filter.filterFor(StorableTestBasic.class, "intProp = ? & id < ?"); + + score = FilteringScore.evaluate(ix, filter); + + assertEquals(0, score.getIdentityCount()); + assertTrue(score.hasAnyMatches()); + + assertTrue(score.hasRangeEnd()); + assertEquals(1, score.getRangeEndFilters().size()); + assertEquals(Filter.filterFor(StorableTestBasic.class, "id < ?"), + score.getRangeEndFilters().get(0)); + + assertEquals(1, score.getRemainderCount()); + assertEquals(1, score.getRemainderFilters().size()); + assertEquals(Filter.filterFor(StorableTestBasic.class, "intProp = ?"), + score.getRemainderFilter()); + + // Filter with fully specified identity match, but a few remaining. + filter = Filter.filterFor + (StorableTestBasic.class, + "intProp = ? & id = ? & stringProp = ? & stringProp > ? & doubleProp = ?"); + + score = FilteringScore.evaluate(ix, filter); + + assertTrue(score.hasAnyMatches()); + assertEquals(3, score.getIdentityCount()); + assertEquals(2, score.getArrangementScore()); + + assertEquals(Filter.filterFor(StorableTestBasic.class, "id = ?"), + score.getIdentityFilters().get(0)); + assertEquals(Filter.filterFor(StorableTestBasic.class, "intProp = ?"), + score.getIdentityFilters().get(1)); + assertEquals(Filter.filterFor(StorableTestBasic.class, "stringProp = ?"), + score.getIdentityFilters().get(2)); + + assertEquals(2, score.getRemainderCount()); + assertEquals(2, score.getRemainderFilters().size()); + // Order of remainder properties accounts for sorting by PropertyFilterList. + assertEquals(Filter.filterFor(StorableTestBasic.class, "doubleProp = ?"), + score.getRemainderFilters().get(0)); + assertEquals(Filter.filterFor(StorableTestBasic.class, "stringProp > ?"), + score.getRemainderFilters().get(1)); + + // Filter with identity and range matches. + filter = Filter.filterFor + (StorableTestBasic.class, + "intProp > ? & id = ? & stringProp = ? & intProp <= ? & " + + "stringProp < ? & doubleProp = ?"); + + score = FilteringScore.evaluate(ix, filter); + + assertTrue(score.hasAnyMatches()); + assertEquals(1, score.getIdentityCount()); + assertEquals(1, score.getArrangementScore()); + + assertEquals(3, score.getRemainderCount()); + assertEquals(3, score.getRemainderFilters().size()); + // Order of remainder properties accounts for sorting by PropertyFilterList. + assertEquals(Filter.filterFor(StorableTestBasic.class, "stringProp = ?"), + score.getRemainderFilters().get(0)); + assertEquals(Filter.filterFor(StorableTestBasic.class, "doubleProp = ?"), + score.getRemainderFilters().get(1)); + assertEquals(Filter.filterFor(StorableTestBasic.class, "stringProp < ?"), + score.getRemainderFilters().get(2)); + + assertTrue(score.hasRangeMatch()); + assertTrue(score.hasRangeStart()); + assertEquals(1, score.getRangeStartFilters().size()); + assertEquals(Filter.filterFor(StorableTestBasic.class, "intProp > ?"), + score.getRangeStartFilters().get(0)); + + assertTrue(score.hasRangeEnd()); + assertEquals(1, score.getRangeEndFilters().size()); + assertEquals(Filter.filterFor(StorableTestBasic.class, "intProp <= ?"), + score.getRangeEndFilters().get(0)); + + // Filter with fully specified identity match, with backwards arrangement. + filter = Filter.filterFor + (StorableTestBasic.class, "stringProp = ? & intProp = ? & id = ?"); + + score = FilteringScore.evaluate(ix, filter); + + assertTrue(score.hasAnyMatches()); + assertEquals(3, score.getIdentityCount()); + // Arrangement score is always at least one if identity count is not zero. + assertEquals(1, score.getArrangementScore()); + } + + public void testReverseRange() throws Exception { + StorableIndex ix = makeIndex(StorableTestBasic.class, + "id", "intProp"); + Filter filter = + Filter.filterFor(StorableTestBasic.class, "id = ? & intProp > ?"); + + FilteringScore score = FilteringScore.evaluate(ix, filter); + + assertFalse(score.shouldReverseRange()); + + ix = makeIndex(StorableTestBasic.class, "id", "-intProp"); + score = FilteringScore.evaluate(ix, filter); + + assertTrue(score.shouldReverseRange()); + } + + public void testRangeComparator() throws Exception { + StorableIndex ix_1, ix_2; + Filter filter; + FilteringScore score_1, score_2; + Comparator> comp = FilteringScore.rangeComparator(); + + ix_1 = makeIndex(StorableTestBasic.class, "id", "intProp", "doubleProp", "-stringProp"); + ix_2 = makeIndex(StorableTestBasic.class, "id", "stringProp", "intProp"); + + filter = Filter.filterFor(StorableTestBasic.class, "id = ?"); + score_1 = FilteringScore.evaluate(ix_1, filter); + score_2 = FilteringScore.evaluate(ix_2, filter); + + assertEquals(0, comp.compare(score_1, score_2)); + assertEquals(0, comp.compare(score_2, score_1)); + + score_1 = FilteringScore.evaluate(ix_1.clustered(true), filter); + score_2 = FilteringScore.evaluate(ix_2, filter); + + assertEquals(-1, comp.compare(score_1, score_2)); + + filter = Filter.filterFor(StorableTestBasic.class, "stringProp = ?"); + score_1 = FilteringScore.evaluate(ix_1, filter); + score_2 = FilteringScore.evaluate(ix_2, filter); + + assertEquals(0, comp.compare(score_1, score_2)); + assertEquals(0, comp.compare(score_2, score_1)); + + filter = Filter.filterFor(StorableTestBasic.class, "stringProp = ?"); + score_1 = FilteringScore.evaluate(ix_1.clustered(true), filter); + score_2 = FilteringScore.evaluate(ix_2, filter); + + assertEquals(0, comp.compare(score_1, score_2)); + assertEquals(0, comp.compare(score_2, score_1)); + + filter = Filter.filterFor(StorableTestBasic.class, "id = ? & intProp = ?"); + score_1 = FilteringScore.evaluate(ix_1, filter); + score_2 = FilteringScore.evaluate(ix_2, filter); + + assertEquals(-1, comp.compare(score_1, score_2)); + assertEquals(1, comp.compare(score_2, score_1)); + + filter = Filter.filterFor(StorableTestBasic.class, "id = ? & stringProp != ?"); + score_1 = FilteringScore.evaluate(ix_1, filter); + score_2 = FilteringScore.evaluate(ix_2, filter); + + assertEquals(0, comp.compare(score_1, score_2)); + assertEquals(0, comp.compare(score_2, score_1)); + + filter = Filter.filterFor(StorableTestBasic.class, "id = ? & stringProp = ?"); + score_1 = FilteringScore.evaluate(ix_1, filter); + score_2 = FilteringScore.evaluate(ix_2, filter); + + assertEquals(1, comp.compare(score_1, score_2)); + assertEquals(-1, comp.compare(score_2, score_1)); + + // Both indexes are as good, since range is open. + filter = Filter.filterFor(StorableTestBasic.class, "id = ? & stringProp > ?"); + score_1 = FilteringScore.evaluate(ix_1, filter); + score_2 = FilteringScore.evaluate(ix_2, filter); + + assertEquals(0, comp.compare(score_1, score_2)); + assertEquals(0, comp.compare(score_2, score_1)); + + filter = Filter.filterFor(StorableTestBasic.class, + "id = ? & intProp = ? & stringProp > ?"); + score_1 = FilteringScore.evaluate(ix_1, filter); + score_2 = FilteringScore.evaluate(ix_2, filter); + + assertEquals(-1, comp.compare(score_1, score_2)); + assertEquals(1, comp.compare(score_2, score_1)); + + // Test range match with tie resolved by clustered index. + filter = Filter.filterFor(StorableTestBasic.class, "id >= ? & id < ?"); + score_1 = FilteringScore.evaluate(ix_1, filter); + score_2 = FilteringScore.evaluate(ix_2, filter); + + assertEquals(0, comp.compare(score_1, score_2)); + assertEquals(0, comp.compare(score_2, score_1)); + + filter = Filter.filterFor(StorableTestBasic.class, "id >= ? & id < ?"); + score_1 = FilteringScore.evaluate(ix_1, filter); + score_2 = FilteringScore.evaluate(ix_2.clustered(true), filter); + + assertEquals(1, comp.compare(score_1, score_2)); + assertEquals(-1, comp.compare(score_2, score_1)); + + // Test open range match with tie still not resolved by clustered index. + filter = Filter.filterFor(StorableTestBasic.class, "id >= ?"); + score_1 = FilteringScore.evaluate(ix_1, filter); + score_2 = FilteringScore.evaluate(ix_2, filter); + + assertEquals(0, comp.compare(score_1, score_2)); + assertEquals(0, comp.compare(score_2, score_1)); + + filter = Filter.filterFor(StorableTestBasic.class, "id >= ?"); + score_1 = FilteringScore.evaluate(ix_1, filter); + score_2 = FilteringScore.evaluate(ix_2.clustered(true), filter); + + assertEquals(0, comp.compare(score_1, score_2)); + assertEquals(0, comp.compare(score_2, score_1)); + } + + public void testFullComparator() throws Exception { + StorableIndex ix_1, ix_2; + Filter filter; + FilteringScore score_1, score_2; + Comparator> comp = FilteringScore.fullComparator(); + + ix_1 = makeIndex(StorableTestBasic.class, "id", "intProp", "doubleProp", "-stringProp"); + ix_2 = makeIndex(StorableTestBasic.class, "id", "stringProp", "intProp"); + + filter = Filter.filterFor(StorableTestBasic.class, "id = ?"); + score_1 = FilteringScore.evaluate(ix_1, filter); + score_2 = FilteringScore.evaluate(ix_2, filter); + + // Second is better because it has fewer properties. + assertEquals(1, comp.compare(score_1, score_2)); + assertEquals(-1, comp.compare(score_2, score_1)); + + score_1 = FilteringScore.evaluate(ix_1.clustered(true), filter); + score_2 = FilteringScore.evaluate(ix_2, filter); + + assertEquals(-1, comp.compare(score_1, score_2)); + + filter = Filter.filterFor(StorableTestBasic.class, "stringProp = ?"); + score_1 = FilteringScore.evaluate(ix_1, filter); + score_2 = FilteringScore.evaluate(ix_2, filter); + + // Although no property matches, second is better just because it has fewer properties. + assertEquals(1, comp.compare(score_1, score_2)); + assertEquals(-1, comp.compare(score_2, score_1)); + + filter = Filter.filterFor(StorableTestBasic.class, "stringProp = ?"); + score_1 = FilteringScore.evaluate(ix_1.clustered(true), filter); + score_2 = FilteringScore.evaluate(ix_2, filter); + + // Although no property matches, first is better just because it is clustered. + assertEquals(-1, comp.compare(score_1, score_2)); + assertEquals(1, comp.compare(score_2, score_1)); + + filter = Filter.filterFor(StorableTestBasic.class, "id = ? & intProp = ?"); + score_1 = FilteringScore.evaluate(ix_1, filter); + score_2 = FilteringScore.evaluate(ix_2, filter); + + assertEquals(-1, comp.compare(score_1, score_2)); + assertEquals(1, comp.compare(score_2, score_1)); + + filter = Filter.filterFor(StorableTestBasic.class, "id = ? & stringProp != ?"); + score_1 = FilteringScore.evaluate(ix_1, filter); + score_2 = FilteringScore.evaluate(ix_2, filter); + + // Second is better because it has fewer properties. + assertEquals(1, comp.compare(score_1, score_2)); + assertEquals(-1, comp.compare(score_2, score_1)); + + filter = Filter.filterFor(StorableTestBasic.class, "id = ? & stringProp = ?"); + score_1 = FilteringScore.evaluate(ix_1, filter); + score_2 = FilteringScore.evaluate(ix_2, filter); + + assertEquals(1, comp.compare(score_1, score_2)); + assertEquals(-1, comp.compare(score_2, score_1)); + + // Second index is better since the open range matches. + filter = Filter.filterFor(StorableTestBasic.class, "id = ? & stringProp > ?"); + score_1 = FilteringScore.evaluate(ix_1, filter); + score_2 = FilteringScore.evaluate(ix_2, filter); + + assertEquals(1, comp.compare(score_1, score_2)); + assertEquals(-1, comp.compare(score_2, score_1)); + + filter = Filter.filterFor(StorableTestBasic.class, + "id = ? & intProp = ? & stringProp > ?"); + score_1 = FilteringScore.evaluate(ix_1, filter); + score_2 = FilteringScore.evaluate(ix_2, filter); + + assertEquals(-1, comp.compare(score_1, score_2)); + assertEquals(1, comp.compare(score_2, score_1)); + + // Test range match with tie resolved by clustered index. + filter = Filter.filterFor(StorableTestBasic.class, "id >= ? & id < ?"); + score_1 = FilteringScore.evaluate(ix_1, filter); + score_2 = FilteringScore.evaluate(ix_2, filter); + + // Second is better because it has fewer properties. + assertEquals(1, comp.compare(score_1, score_2)); + assertEquals(-1, comp.compare(score_2, score_1)); + + filter = Filter.filterFor(StorableTestBasic.class, "id >= ? & id < ?"); + score_1 = FilteringScore.evaluate(ix_1.clustered(true), filter); + score_2 = FilteringScore.evaluate(ix_2, filter); + + // First is better because it is clusted. + assertEquals(-1, comp.compare(score_1, score_2)); + assertEquals(1, comp.compare(score_2, score_1)); + + // Test range match with tie resolved by clustered index. + filter = Filter.filterFor(StorableTestBasic.class, "id >= ? & id < ?"); + score_1 = FilteringScore.evaluate(ix_1.clustered(true), filter); + score_2 = FilteringScore.evaluate(ix_2.clustered(true), filter); + + // Second is better because it has fewer properties. + assertEquals(1, comp.compare(score_1, score_2)); + assertEquals(-1, comp.compare(score_2, score_1)); + } + + public void testArrangementFullComparator() throws Exception { + StorableIndex ix_1, ix_2; + Filter filter; + FilteringScore score_1, score_2; + + ix_1 = makeIndex(StorableTestBasic.class, "id", "intProp", "-stringProp"); + ix_2 = makeIndex(StorableTestBasic.class, "id", "stringProp", "intProp"); + + filter = Filter.filterFor(StorableTestBasic.class, + "id = ? & intProp = ? & stringProp = ?"); + + score_1 = FilteringScore.evaluate(ix_1, filter); + score_2 = FilteringScore.evaluate(ix_2, filter); + + Comparator> comp = FilteringScore.rangeComparator(); + + // With just range comparison, either index works. + assertEquals(0, comp.compare(score_1, score_2)); + assertEquals(0, comp.compare(score_2, score_1)); + + comp = FilteringScore.fullComparator(); + + // First index is better because property arrangement matches. + assertEquals(-1, comp.compare(score_1, score_2)); + assertEquals(1, comp.compare(score_2, score_1)); + } + + public void testRangeFilterSubset() throws Exception { + StorableIndex ix = makeIndex(StorableTestBasic.class, "id"); + Filter filter; + FilteringScore score; + + filter = Filter.filterFor(StorableTestBasic.class, "id >= ? & id >= ? & id < ? & id <= ?"); + + score = FilteringScore.evaluate(ix, filter); + + assertEquals(2, score.getRangeStartFilters().size()); + + List> exStart = score.getExclusiveRangeStartFilters(); + assertEquals(0, exStart.size()); + + List> inStart = score.getInclusiveRangeStartFilters(); + assertEquals(2, inStart.size()); + assertEquals(Filter.filterFor(StorableTestBasic.class, "id >= ?"), inStart.get(0)); + assertEquals(Filter.filterFor(StorableTestBasic.class, "id >= ?"), inStart.get(1)); + + assertEquals(2, score.getRangeEndFilters().size()); + + List> exEnd = score.getExclusiveRangeEndFilters(); + assertEquals(1, exEnd.size()); + assertEquals(Filter.filterFor(StorableTestBasic.class, "id < ?"), exEnd.get(0)); + + List> inEnd = score.getInclusiveRangeEndFilters(); + assertEquals(1, inEnd.size()); + assertEquals(Filter.filterFor(StorableTestBasic.class, "id <= ?"), inEnd.get(0)); + } + + public void testKeyMatch() throws Exception { + StorableIndex ix = makeIndex(StorableTestBasic.class, "id", "intProp"); + ix = ix.unique(true); + Filter filter; + FilteringScore score; + + filter = Filter.filterFor(StorableTestBasic.class, "id = ?"); + score = FilteringScore.evaluate(ix, filter); + assertFalse(score.isKeyMatch()); + + filter = Filter.filterFor(StorableTestBasic.class, "id = ? & intProp > ?"); + score = FilteringScore.evaluate(ix, filter); + assertFalse(score.isKeyMatch()); + + filter = Filter.filterFor(StorableTestBasic.class, "id = ? & intProp = ?"); + score = FilteringScore.evaluate(ix, filter); + assertTrue(score.isKeyMatch()); + + filter = Filter.filterFor(StorableTestBasic.class, + "id = ? & intProp = ? & doubleProp = ?"); + score = FilteringScore.evaluate(ix, filter); + assertTrue(score.isKeyMatch()); + } +} diff --git a/src/test/java/com/amazon/carbonado/qe/TestIndexedQueryExecutor.java b/src/test/java/com/amazon/carbonado/qe/TestIndexedQueryExecutor.java new file mode 100644 index 0000000..a039e63 --- /dev/null +++ b/src/test/java/com/amazon/carbonado/qe/TestIndexedQueryExecutor.java @@ -0,0 +1,725 @@ +/* + * 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 junit.framework.TestCase; +import junit.framework.TestSuite; + +import com.amazon.carbonado.Cursor; +import com.amazon.carbonado.Storable; + +import com.amazon.carbonado.cursor.IteratorCursor; + +import com.amazon.carbonado.filter.Filter; +import com.amazon.carbonado.filter.FilterValues; + +import com.amazon.carbonado.info.OrderedProperty; +import com.amazon.carbonado.info.StorableIndex; + +import com.amazon.carbonado.stored.StorableTestBasic; + +/** + * + * + * @author Brian S O'Neill + */ +public class TestIndexedQueryExecutor extends TestCase { + public static void main(String[] args) { + junit.textui.TestRunner.run(suite()); + } + + public static TestSuite suite() { + return new TestSuite(TestIndexedQueryExecutor.class); + } + + static StorableIndex makeIndex(Class type, String... props) { + return TestOrderingScore.makeIndex(type, props); + } + + static OrderedProperty[] makeOrderings(Class type, String... props) + { + return TestOrderingScore.makeOrderings(type, props); + } + + public TestIndexedQueryExecutor(String name) { + super(name); + } + + public void testIdentityMatch() throws Exception { + StorableIndex index = + makeIndex(StorableTestBasic.class, "id", "-intProp", "doubleProp"); + + Filter filter = + Filter.filterFor(StorableTestBasic.class, "id = ?"); + FilterValues values = filter.initialFilterValues(); + filter = values.getFilter(); + + CompositeScore score = CompositeScore.evaluate(index, filter); + + Mock executor = new Mock(index, score); + + executor.openCursor(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); + + executor = new Mock(index, score); + + executor.openCursor(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); + + executor = new Mock(index, score); + + executor.openCursor(values.with(200)); + + assertEquals(null, executor.mIdentityValues); + assertEquals(BoundaryType.OPEN, executor.mRangeStartBoundary); + assertEquals(null, executor.mRangeStartValue); + assertEquals(BoundaryType.OPEN, executor.mRangeEndBoundary); + assertEquals(null, executor.mRangeEndValue); + assertFalse(executor.mReverseRange); + assertFalse(executor.mReverseOrder); + } + + public void testOpenRangeStartMatch() throws Exception { + StorableIndex index = makeIndex(StorableTestBasic.class, "intProp"); + + Filter filter = + Filter.filterFor(StorableTestBasic.class, "intProp > ?"); + FilterValues values = filter.initialFilterValues(); + filter = values.getFilter(); + + CompositeScore score = CompositeScore.evaluate(index, filter); + + Mock executor = new Mock(index, score); + + executor.openCursor(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); + + executor = new Mock(index, score); + + executor.openCursor(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); + + executor = new Mock(index, score); + + executor.openCursor(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); + + executor = new Mock(index, score); + + executor.openCursor(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); + + executor = new Mock(index, score); + + executor.openCursor(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); + + executor = new Mock(index, score); + + executor.openCursor(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, + makeOrderings(StorableTestBasic.class, "-intProp")); + + executor = new Mock(index, score); + + executor.openCursor(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); + + executor = new Mock(index, score); + + executor.openCursor(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, + makeOrderings(StorableTestBasic.class, "-intProp")); + + executor = new Mock(index, score); + + executor.openCursor(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, + makeOrderings(StorableTestBasic.class, "intProp")); + + executor = new Mock(index, score); + + executor.openCursor(values.with(100)); + + assertEquals(null, executor.mIdentityValues); + assertEquals(BoundaryType.EXCLUSIVE, executor.mRangeStartBoundary); + assertEquals(100, executor.mRangeStartValue); + assertEquals(BoundaryType.OPEN, executor.mRangeEndBoundary); + assertEquals(null, executor.mRangeEndValue); + assertTrue(executor.mReverseRange); + assertTrue(executor.mReverseOrder); + } + + public void testOpenRangeEndMatch() throws Exception { + StorableIndex index = makeIndex(StorableTestBasic.class, "intProp"); + + Filter filter = + Filter.filterFor(StorableTestBasic.class, "intProp < ?"); + FilterValues values = filter.initialFilterValues(); + filter = values.getFilter(); + + CompositeScore score = CompositeScore.evaluate(index, filter); + + Mock executor = new Mock(index, score); + + executor.openCursor(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); + + executor = new Mock(index, score); + + executor.openCursor(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); + + executor = new Mock(index, score); + + executor.openCursor(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); + + executor = new Mock(index, score); + + executor.openCursor(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); + + executor = new Mock(index, score); + + executor.openCursor(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); + + executor = new Mock(index, score); + + executor.openCursor(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, + makeOrderings(StorableTestBasic.class, "-intProp")); + + executor = new Mock(index, score); + + executor.openCursor(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); + + executor = new Mock(index, score); + + executor.openCursor(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, + makeOrderings(StorableTestBasic.class, "-intProp")); + + executor = new Mock(index, score); + + executor.openCursor(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, + makeOrderings(StorableTestBasic.class, "intProp")); + + executor = new Mock(index, score); + + executor.openCursor(values.with(100)); + + assertEquals(null, executor.mIdentityValues); + assertEquals(BoundaryType.OPEN, executor.mRangeStartBoundary); + assertEquals(null, executor.mRangeStartValue); + assertEquals(BoundaryType.EXCLUSIVE, executor.mRangeEndBoundary); + assertEquals(100, executor.mRangeEndValue); + assertTrue(executor.mReverseRange); + assertTrue(executor.mReverseOrder); + } + + public void testClosedRangeMatch() throws Exception { + // These tests are not as exhaustive, as I don't expect the combination + // of start and end ranges to interfere with each other. + + StorableIndex index = makeIndex(StorableTestBasic.class, "intProp"); + + Filter filter = + Filter.filterFor(StorableTestBasic.class, "intProp > ? & intProp < ?"); + FilterValues values = filter.initialFilterValues(); + filter = values.getFilter(); + + CompositeScore score = CompositeScore.evaluate(index, filter); + + Mock executor = new Mock(index, score); + + executor.openCursor(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); + + executor = new Mock(index, score); + + executor.openCursor(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); + + executor = new Mock(index, score); + + executor.openCursor(values.with(10).with(100).with(30)); + + assertEquals(null, executor.mIdentityValues); + assertEquals(BoundaryType.EXCLUSIVE, executor.mRangeStartBoundary); + assertEquals(30, executor.mRangeStartValue); + assertEquals(BoundaryType.EXCLUSIVE, executor.mRangeEndBoundary); + assertEquals(100, executor.mRangeEndValue); + assertFalse(executor.mReverseRange); + assertFalse(executor.mReverseOrder); + } + + public void testIdentityAndRangeMatch() throws Exception { + // These tests are not as exhaustive, as I don't expect the combination + // of identity and ranges to interfere with each other. + + StorableIndex index; + Filter filter; + FilterValues values; + CompositeScore score; + Mock executor; + + index = makeIndex(StorableTestBasic.class, "intProp", "-doubleProp", "stringProp"); + + filter = Filter.filterFor(StorableTestBasic.class, + "intProp = ? & doubleProp > ? & doubleProp < ?"); + values = filter.initialFilterValues(); + filter = values.getFilter(); + + score = CompositeScore.evaluate(index, filter); + + executor = new Mock(index, score); + + executor.openCursor(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, + makeOrderings(StorableTestBasic.class, "doubleProp")); + + executor = new Mock(index, score); + + executor.openCursor(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, + makeOrderings(StorableTestBasic.class, "stringProp")); + + executor = new Mock(index, score); + + executor.openCursor(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, + makeOrderings(StorableTestBasic.class, "-stringProp")); + + executor = new Mock(index, score); + + executor.openCursor(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()); + OrderedProperty[] expectedOrdering = + makeOrderings(StorableTestBasic.class, "intProp", "-doubleProp", "stringProp"); + assertEquals(Arrays.asList(expectedOrdering), executor.getOrdering()); + } + + /** + * Mock object doesn't really open a cursor -- it just captures the passed + * parameters. + */ + static class Mock extends IndexedQueryExecutor { + Object[] mIdentityValues; + BoundaryType mRangeStartBoundary; + Object mRangeStartValue; + BoundaryType mRangeEndBoundary; + Object mRangeEndValue; + boolean mReverseRange; + boolean mReverseOrder; + + public Mock(StorableIndex index, CompositeScore score) { + super(index, score); + } + + protected Cursor openCursor(StorableIndex index, + Object[] identityValues, + BoundaryType rangeStartBoundary, + Object rangeStartValue, + BoundaryType rangeEndBoundary, + Object rangeEndValue, + boolean reverseRange, + boolean reverseOrder) + { + mIdentityValues = identityValues; + mRangeStartBoundary = rangeStartBoundary; + mRangeStartValue = rangeStartValue; + mRangeEndBoundary = rangeEndBoundary; + mRangeEndValue = rangeEndValue; + mReverseRange = reverseRange; + mReverseOrder = reverseOrder; + + Collection empty = Collections.emptyList(); + return new IteratorCursor(empty); + } + } +} diff --git a/src/test/java/com/amazon/carbonado/qe/TestOrderingScore.java b/src/test/java/com/amazon/carbonado/qe/TestOrderingScore.java new file mode 100644 index 0000000..3929f59 --- /dev/null +++ b/src/test/java/com/amazon/carbonado/qe/TestOrderingScore.java @@ -0,0 +1,517 @@ +/* + * 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.stored.StorableTestBasic; + +/** + * + * + * @author Brian S O'Neill + */ +public class TestOrderingScore extends TestCase { + public static void main(String[] args) { + junit.textui.TestRunner.run(suite()); + } + + public static TestSuite suite() { + return new TestSuite(TestOrderingScore.class); + } + + static StorableIndex makeIndex(Class type, String... props) { + return new StorableIndex(makeOrderings(type, props), UNSPECIFIED); + } + + static OrderedProperty[] makeOrderings(Class type, String... props) + { + StorableInfo info = StorableIntrospector.examine(type); + OrderedProperty[] ops = new OrderedProperty[props.length]; + for (int i=0; i ix = makeIndex(StorableTestBasic.class, "id"); + + OrderingScore score = OrderingScore.evaluate(ix, null); + + assertEquals(0, score.getHandledCount()); + assertEquals(0, score.getRemainderCount()); + assertEquals(false, score.shouldReverseOrder()); + } + + public void testOneProp() throws Exception { + StorableIndex ix; + OrderedProperty[] ops; + OrderingScore score; + + ///////////// + ix = makeIndex(StorableTestBasic.class, "id"); + + ops = makeOrderings(StorableTestBasic.class, "id"); + score = OrderingScore.evaluate(ix, null, ops); + + assertEquals(1, score.getHandledCount()); + assertEquals("+id", score.getHandledOrderings().get(0).toString()); + assertEquals(0, score.getRemainderCount()); + assertEquals(false, score.shouldReverseOrder()); + + ops = makeOrderings(StorableTestBasic.class, "+id"); + score = OrderingScore.evaluate(ix, null, ops); + + assertEquals(1, score.getHandledCount()); + assertEquals("+id", score.getHandledOrderings().get(0).toString()); + assertEquals(0, score.getRemainderCount()); + assertEquals(false, score.shouldReverseOrder()); + + ops = makeOrderings(StorableTestBasic.class, "-id"); + score = OrderingScore.evaluate(ix, null, ops); + + assertEquals(1, score.getHandledCount()); + assertEquals("-id", score.getHandledOrderings().get(0).toString()); + assertEquals(0, score.getRemainderCount()); + assertEquals(true, score.shouldReverseOrder()); + + ///////////// + ix = makeIndex(StorableTestBasic.class, "+id"); + + ops = makeOrderings(StorableTestBasic.class, "id"); + score = OrderingScore.evaluate(ix, null, ops); + + assertEquals(1, score.getHandledCount()); + assertEquals("+id", score.getHandledOrderings().get(0).toString()); + assertEquals(0, score.getRemainderCount()); + assertEquals(false, score.shouldReverseOrder()); + + ops = makeOrderings(StorableTestBasic.class, "+id"); + score = OrderingScore.evaluate(ix, null, ops); + + assertEquals(1, score.getHandledCount()); + assertEquals("+id", score.getHandledOrderings().get(0).toString()); + assertEquals(0, score.getRemainderCount()); + assertEquals(false, score.shouldReverseOrder()); + + ops = makeOrderings(StorableTestBasic.class, "-id"); + score = OrderingScore.evaluate(ix, null, ops); + + assertEquals(1, score.getHandledCount()); + assertEquals("-id", score.getHandledOrderings().get(0).toString()); + assertEquals(0, score.getRemainderCount()); + assertEquals(true, score.shouldReverseOrder()); + + ///////////// + ix = makeIndex(StorableTestBasic.class, "-id"); + + ops = makeOrderings(StorableTestBasic.class, "id"); + score = OrderingScore.evaluate(ix, null, ops); + + assertEquals(1, score.getHandledCount()); + assertEquals("+id", score.getHandledOrderings().get(0).toString()); + assertEquals(0, score.getRemainderCount()); + assertEquals(true, score.shouldReverseOrder()); + + ops = makeOrderings(StorableTestBasic.class, "+id"); + score = OrderingScore.evaluate(ix, null, ops); + + assertEquals(1, score.getHandledCount()); + assertEquals("+id", score.getHandledOrderings().get(0).toString()); + assertEquals(0, score.getRemainderCount()); + assertEquals(true, score.shouldReverseOrder()); + + ops = makeOrderings(StorableTestBasic.class, "-id"); + score = OrderingScore.evaluate(ix, null, ops); + + assertEquals(1, score.getHandledCount()); + assertEquals("-id", score.getHandledOrderings().get(0).toString()); + assertEquals(0, score.getRemainderCount()); + assertEquals(false, score.shouldReverseOrder()); + + ///////////// + ix = makeIndex(StorableTestBasic.class, "intProp"); + + ops = makeOrderings(StorableTestBasic.class, "id"); + score = OrderingScore.evaluate(ix, null, ops); + + assertEquals(0, score.getHandledCount()); + assertEquals(1, score.getRemainderCount()); + assertEquals("+id", score.getRemainderOrderings().get(0).toString()); + assertEquals(false, score.shouldReverseOrder()); + } + + public void testMultipleProps() throws Exception { + final StorableIndex ix; + OrderedProperty[] ops; + OrderingScore score; + + ix = makeIndex(StorableTestBasic.class, "id", "intProp"); + + ops = makeOrderings(StorableTestBasic.class, "id"); + score = OrderingScore.evaluate(ix, null, ops); + assertEquals(1, score.getHandledCount()); + assertEquals("+id", score.getHandledOrderings().get(0).toString()); + assertEquals(0, score.getRemainderCount()); + assertEquals(false, score.shouldReverseOrder()); + + ops = makeOrderings(StorableTestBasic.class, "-id"); + score = OrderingScore.evaluate(ix, null, ops); + assertEquals(1, score.getHandledCount()); + assertEquals("-id", score.getHandledOrderings().get(0).toString()); + assertEquals(0, score.getRemainderCount()); + assertEquals(true, score.shouldReverseOrder()); + + ops = makeOrderings(StorableTestBasic.class, "id", "intProp"); + score = OrderingScore.evaluate(ix, null, ops); + assertEquals(2, score.getHandledCount()); + assertEquals("+id", score.getHandledOrderings().get(0).toString()); + assertEquals("+intProp", score.getHandledOrderings().get(1).toString()); + assertEquals(0, score.getRemainderCount()); + assertEquals(false, score.shouldReverseOrder()); + + ops = makeOrderings(StorableTestBasic.class, "-id", "-intProp"); + score = OrderingScore.evaluate(ix, null, ops); + assertEquals(2, score.getHandledCount()); + assertEquals("-id", score.getHandledOrderings().get(0).toString()); + assertEquals("-intProp", score.getHandledOrderings().get(1).toString()); + assertEquals(0, score.getRemainderCount()); + assertEquals(true, score.shouldReverseOrder()); + + ops = makeOrderings(StorableTestBasic.class, "-id", "+intProp"); + score = OrderingScore.evaluate(ix, null, ops); + assertEquals(1, score.getHandledCount()); + assertEquals("-id", score.getHandledOrderings().get(0).toString()); + assertEquals(1, score.getRemainderCount()); + assertEquals("+intProp", score.getRemainderOrderings().get(0).toString()); + assertEquals(true, score.shouldReverseOrder()); + + ops = makeOrderings(StorableTestBasic.class, "+id", "-intProp"); + score = OrderingScore.evaluate(ix, null, ops); + assertEquals(1, score.getHandledCount()); + assertEquals("+id", score.getHandledOrderings().get(0).toString()); + assertEquals(1, score.getRemainderCount()); + assertEquals("-intProp", score.getRemainderOrderings().get(0).toString()); + assertEquals(false, score.shouldReverseOrder()); + + ops = makeOrderings(StorableTestBasic.class, "intProp"); + score = OrderingScore.evaluate(ix, null, ops); + assertEquals(0, score.getHandledCount()); + assertEquals(1, score.getRemainderCount()); + assertEquals("+intProp", score.getRemainderOrderings().get(0).toString()); + assertEquals(false, score.shouldReverseOrder()); + + // Gap is allowed if identity filtered. + + Filter filter; + + filter = Filter.filterFor(StorableTestBasic.class, "id = ?"); + + ops = makeOrderings(StorableTestBasic.class, "intProp"); + score = OrderingScore.evaluate(ix, filter, ops); + assertEquals(1, score.getHandledCount()); + assertEquals("+intProp", score.getHandledOrderings().get(0).toString()); + assertEquals(0, score.getRemainderCount()); + assertEquals(false, score.shouldReverseOrder()); + + ops = makeOrderings(StorableTestBasic.class, "-intProp"); + score = OrderingScore.evaluate(ix, filter, ops); + assertEquals(1, score.getHandledCount()); + assertEquals("-intProp", score.getHandledOrderings().get(0).toString()); + assertEquals(0, score.getRemainderCount()); + assertEquals(true, score.shouldReverseOrder()); + + ops = makeOrderings(StorableTestBasic.class, "intProp", "id"); + score = OrderingScore.evaluate(ix, filter, ops); + assertEquals(1, score.getHandledCount()); + assertEquals("+intProp", score.getHandledOrderings().get(0).toString()); + // Since "id" is filtered, don't count as remainder. + assertEquals(0, score.getRemainderCount()); + assertEquals(false, score.shouldReverseOrder()); + + ops = makeOrderings(StorableTestBasic.class, "-intProp", "id"); + score = OrderingScore.evaluate(ix, filter, ops); + assertEquals(1, score.getHandledCount()); + assertEquals("-intProp", score.getHandledOrderings().get(0).toString()); + // Since "id" is filtered, don't count as remainder. + assertEquals(0, score.getRemainderCount()); + assertEquals(true, score.shouldReverseOrder()); + + ops = makeOrderings(StorableTestBasic.class, "intProp", "doubleProp"); + score = OrderingScore.evaluate(ix, filter, ops); + assertEquals(1, score.getHandledCount()); + assertEquals("+intProp", score.getHandledOrderings().get(0).toString()); + assertEquals(1, score.getRemainderCount()); + assertEquals("+doubleProp", score.getRemainderOrderings().get(0).toString()); + assertEquals(false, score.shouldReverseOrder()); + + ops = makeOrderings(StorableTestBasic.class, "intProp", "-doubleProp"); + score = OrderingScore.evaluate(ix, filter, ops); + assertEquals(1, score.getHandledCount()); + assertEquals("+intProp", score.getHandledOrderings().get(0).toString()); + assertEquals(1, score.getRemainderCount()); + assertEquals("-doubleProp", score.getRemainderOrderings().get(0).toString()); + assertEquals(false, score.shouldReverseOrder()); + + filter = Filter.filterFor(StorableTestBasic.class, "id > ? & doubleProp = ?"); + + ops = makeOrderings(StorableTestBasic.class, "intProp"); + score = OrderingScore.evaluate(ix, filter, ops); + assertEquals(0, score.getHandledCount()); + assertEquals(1, score.getRemainderCount()); + assertEquals("+intProp", score.getRemainderOrderings().get(0).toString()); + assertEquals(false, score.shouldReverseOrder()); + + ops = makeOrderings(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 = makeOrderings(StorableTestBasic.class, "doubleProp", "-intProp"); + score = OrderingScore.evaluate(ix, filter, ops); + assertEquals(1, score.getHandledCount()); + assertEquals("-intProp", score.getHandledOrderings().get(0).toString()); + assertEquals(0, score.getRemainderCount()); + assertEquals(true, score.shouldReverseOrder()); + } + + public void testMidGap() throws Exception { + final StorableIndex ix; + OrderedProperty[] ops; + OrderingScore score; + Filter filter; + + ix = makeIndex(StorableTestBasic.class, "id", "intProp", "doubleProp", "-stringProp"); + + ops = makeOrderings(StorableTestBasic.class, "id", "intProp", "doubleProp", "-stringProp"); + score = OrderingScore.evaluate(ix, null, ops); + assertEquals(4, score.getHandledCount()); + assertEquals("+id", score.getHandledOrderings().get(0).toString()); + assertEquals("+intProp", score.getHandledOrderings().get(1).toString()); + assertEquals("+doubleProp", score.getHandledOrderings().get(2).toString()); + assertEquals("-stringProp", score.getHandledOrderings().get(3).toString()); + assertEquals(0, score.getRemainderCount()); + assertEquals(false, score.shouldReverseOrder()); + + // Now ignore mid index properties, creating a gap. + + ops = makeOrderings(StorableTestBasic.class, "id", "-stringProp"); + score = OrderingScore.evaluate(ix, null, ops); + assertEquals(1, score.getHandledCount()); + assertEquals("+id", score.getHandledOrderings().get(0).toString()); + assertEquals(1, score.getRemainderCount()); + assertEquals("-stringProp", score.getRemainderOrderings().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 = makeOrderings(StorableTestBasic.class, "id", "-stringProp"); + score = OrderingScore.evaluate(ix, filter, ops); + assertEquals(1, score.getHandledCount()); + assertEquals("+id", score.getHandledOrderings().get(0).toString()); + assertEquals(1, score.getRemainderCount()); + assertEquals("-stringProp", score.getRemainderOrderings().get(0).toString()); + assertEquals(false, score.shouldReverseOrder()); + + filter = Filter.filterFor(StorableTestBasic.class, "doubleProp >= ? & intProp = ?"); + + ops = makeOrderings(StorableTestBasic.class, "id", "-stringProp"); + score = OrderingScore.evaluate(ix, filter, ops); + assertEquals(1, score.getHandledCount()); + assertEquals("+id", score.getHandledOrderings().get(0).toString()); + assertEquals(1, score.getRemainderCount()); + assertEquals("-stringProp", score.getRemainderOrderings().get(0).toString()); + assertEquals(false, score.shouldReverseOrder()); + + // Now a complete bridge. + + filter = Filter.filterFor(StorableTestBasic.class, "doubleProp = ? & intProp = ?"); + + ops = makeOrderings(StorableTestBasic.class, "id", "-stringProp"); + score = OrderingScore.evaluate(ix, filter, ops); + assertEquals(2, score.getHandledCount()); + assertEquals("+id", score.getHandledOrderings().get(0).toString()); + assertEquals("-stringProp", score.getHandledOrderings().get(1).toString()); + assertEquals(0, score.getRemainderCount()); + assertEquals(false, score.shouldReverseOrder()); + + // Again in reverse. + + ops = makeOrderings(StorableTestBasic.class, "-id", "stringProp"); + score = OrderingScore.evaluate(ix, filter, ops); + assertEquals(2, score.getHandledCount()); + assertEquals("-id", score.getHandledOrderings().get(0).toString()); + assertEquals("+stringProp", score.getHandledOrderings().get(1).toString()); + assertEquals(0, score.getRemainderCount()); + assertEquals(true, score.shouldReverseOrder()); + + // Failed double reverse. + + ops = makeOrderings(StorableTestBasic.class, "-id", "-stringProp"); + score = OrderingScore.evaluate(ix, filter, ops); + assertEquals(1, score.getHandledCount()); + assertEquals("-id", score.getHandledOrderings().get(0).toString()); + assertEquals(1, score.getRemainderCount()); + assertEquals("-stringProp", score.getRemainderOrderings().get(0).toString()); + assertEquals(true, score.shouldReverseOrder()); + } + + public void testComparator() throws Exception { + StorableIndex ix_1, ix_2; + OrderedProperty[] ops; + OrderingScore score_1, score_2; + Filter filter; + Comparator> comp = OrderingScore.fullComparator(); + + ix_1 = makeIndex(StorableTestBasic.class, "id", "intProp", "doubleProp", "-stringProp"); + ix_2 = makeIndex(StorableTestBasic.class, "intProp", "doubleProp", "id"); + + ops = makeOrderings(StorableTestBasic.class, "-id", "-intProp"); + score_1 = OrderingScore.evaluate(ix_1, null, ops); + score_2 = OrderingScore.evaluate(ix_2, null, ops); + + assertEquals(-1, comp.compare(score_1, score_2)); + assertEquals(1, comp.compare(score_2, score_1)); + + filter = Filter.filterFor(StorableTestBasic.class, "id = ?"); + score_1 = OrderingScore.evaluate(ix_1, filter, ops); + score_2 = OrderingScore.evaluate(ix_2, filter, ops); + + // Index 2 has less properties, so it is better. + assertEquals(1, comp.compare(score_1, score_2)); + assertEquals(-1, comp.compare(score_2, score_1)); + + // Keep ix_2 slightly better by matching desired order. + ix_2 = makeIndex(StorableTestBasic.class, "-intProp", "doubleProp", "id", "stringProp"); + + score_1 = OrderingScore.evaluate(ix_1, filter, ops); + score_2 = OrderingScore.evaluate(ix_2, filter, ops); + + assertEquals(1, comp.compare(score_1, score_2)); + assertEquals(-1, comp.compare(score_2, score_1)); + + // Make ix_1 slightly better by making it clustered. + ix_1 = ix_1.clustered(true); + + score_1 = OrderingScore.evaluate(ix_1, filter, ops); + score_2 = OrderingScore.evaluate(ix_2, filter, ops); + + assertEquals(-1, comp.compare(score_1, score_2)); + assertEquals(1, comp.compare(score_2, score_1)); + + // Make ix_2 better when clustered. + ix_2 = ix_2.clustered(true); + + score_1 = OrderingScore.evaluate(ix_1, filter, ops); + score_2 = OrderingScore.evaluate(ix_2, filter, ops); + + assertEquals(1, comp.compare(score_1, score_2)); + assertEquals(-1, comp.compare(score_2, score_1)); + + // Make ix_1 same by reversing order. + ix_1 = ix_1.reverse(); + + score_1 = OrderingScore.evaluate(ix_1, filter, ops); + score_2 = OrderingScore.evaluate(ix_2, filter, ops); + + assertEquals(0, comp.compare(score_1, score_2)); + assertEquals(0, comp.compare(score_2, score_1)); + } + + public void testIndexNotNeeded() throws Exception { + // Test an index which matches desired orderings, but ordering + // properties are filtered out. Thus the index is not needed. + + final StorableIndex ix; + OrderedProperty[] ops; + OrderingScore score; + Filter filter; + + ix = makeIndex(StorableTestBasic.class, "id", "intProp", "doubleProp"); + + ops = makeOrderings(StorableTestBasic.class, "id", "intProp", "doubleProp"); + score = OrderingScore.evaluate(ix, null, ops); + assertEquals(3, score.getHandledCount()); + assertEquals("+id", score.getHandledOrderings().get(0).toString()); + assertEquals("+intProp", score.getHandledOrderings().get(1).toString()); + assertEquals("+doubleProp", score.getHandledOrderings().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.getHandledOrderings().get(0).toString()); + assertEquals(0, score.getRemainderCount()); + assertEquals(false, score.shouldReverseOrder()); + + filter = Filter.filterFor(StorableTestBasic.class, "id = ? & intProp = ? & doubleProp =?"); + + score = OrderingScore.evaluate(ix, filter, ops); + assertEquals(0, score.getHandledCount()); + assertEquals(0, score.getRemainderCount()); + assertEquals(false, score.shouldReverseOrder()); + } + + public void testUniqueIndexNotNeeded() throws Exception { + // Test a unique index which has been fully specified. Ordering is not + // needed at all. + final StorableIndex ix; + OrderedProperty[] ops; + OrderingScore score; + Filter filter; + + ix = makeIndex(StorableTestBasic.class, "id", "intProp", "doubleProp").unique(true); + ops = makeOrderings(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()); + } +} diff --git a/src/test/java/com/amazon/carbonado/qe/TestPropertyFilterList.java b/src/test/java/com/amazon/carbonado/qe/TestPropertyFilterList.java new file mode 100644 index 0000000..b0af52f --- /dev/null +++ b/src/test/java/com/amazon/carbonado/qe/TestPropertyFilterList.java @@ -0,0 +1,108 @@ +/* + * Copyright 2006 Amazon Technologies, Inc. or its affiliates. + * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks + * of Amazon Technologies, Inc. or its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.amazon.carbonado.qe; + +import java.util.List; + +import junit.framework.TestCase; +import junit.framework.TestSuite; + +import com.amazon.carbonado.filter.Filter; +import com.amazon.carbonado.filter.PropertyFilter; + +import com.amazon.carbonado.stored.StorableTestBasic; + +/** + * + * + * @author Brian S O'Neill + */ +public class TestPropertyFilterList extends TestCase { + public static void main(String[] args) { + junit.textui.TestRunner.run(suite()); + } + + public static TestSuite suite() { + return new TestSuite(TestPropertyFilterList.class); + } + + public TestPropertyFilterList(String name) { + super(name); + } + + public void testNull() throws Exception { + assertEquals(0, PropertyFilterList.get(null).size()); + } + + public void testOpen() throws Exception { + Filter filter = Filter.getOpenFilter(StorableTestBasic.class); + assertEquals(0, PropertyFilterList.get(filter).size()); + } + + public void testClosed() throws Exception { + Filter filter = Filter.getClosedFilter(StorableTestBasic.class); + assertEquals(0, PropertyFilterList.get(filter).size()); + } + + public void testSingleton() throws Exception { + Filter filter = Filter.filterFor(StorableTestBasic.class, "id = ?"); + + List> list = PropertyFilterList.get(filter); + + assertEquals(1, list.size()); + assertEquals(filter, list.get(0)); + + List> list2 = PropertyFilterList.get(filter); + + assertTrue(list == list2); + } + + public void testMultiple() throws Exception { + Filter filter = + Filter.filterFor(StorableTestBasic.class, "id = ? & intProp > ?"); + + List> list = PropertyFilterList.get(filter); + + assertEquals(2, list.size()); + + Filter subFilter = + Filter.filterFor(StorableTestBasic.class, "id = ?"); + + assertEquals(subFilter, list.get(0)); + + subFilter = Filter.filterFor(StorableTestBasic.class, "intProp > ?"); + + assertEquals(subFilter, list.get(1)); + + List> list2 = PropertyFilterList.get(filter); + + assertTrue(list == list2); + } + + public void testIllegal() throws Exception { + Filter filter = + Filter.filterFor(StorableTestBasic.class, "id = ? | intProp > ?"); + + try { + List> list = PropertyFilterList.get(filter); + fail(); + } catch (IllegalArgumentException e) { + } + } +} diff --git a/src/test/java/com/amazon/carbonado/qe/TestQueryExecutor.java b/src/test/java/com/amazon/carbonado/qe/TestQueryExecutor.java new file mode 100644 index 0000000..4727a56 --- /dev/null +++ b/src/test/java/com/amazon/carbonado/qe/TestQueryExecutor.java @@ -0,0 +1,195 @@ +/* + * Copyright 2006 Amazon Technologies, Inc. or its affiliates. + * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks + * of Amazon Technologies, Inc. or its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.amazon.carbonado.qe; + +import java.io.IOException; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Comparator; +import java.util.List; +import java.util.Map; + +import junit.framework.TestCase; + +import com.amazon.carbonado.Cursor; +import com.amazon.carbonado.FetchException; + +import com.amazon.carbonado.info.Direction; +import com.amazon.carbonado.info.OrderedProperty; +import com.amazon.carbonado.info.StorableInfo; +import com.amazon.carbonado.info.StorableIntrospector; +import com.amazon.carbonado.info.StorableProperty; + +import com.amazon.carbonado.stored.Dummy; +import com.amazon.carbonado.stored.Address; + +/** + * + * + * @author Brian S O'Neill + */ +public abstract class TestQueryExecutor extends TestCase { + + protected QueryExecutor
createExecutor(int... ids) { + return new IterableQueryExecutor
(Address.class, createCollection(ids)); + } + + protected Collection
createCollection(int... ids) { + Collection
elements = new ArrayList
(ids.length); + for (int i=0; i elements, int... expectedIDs) + throws FetchException + { + for (int id : expectedIDs) { + if (elements.hasNext()) { + Address e = elements.next(); + if (e.getAddressID() != id) { + fail("Element mismatch: expected=" + id + ", actual=" + e.getAddressID()); + elements.close(); + return; + } + } else { + fail("Too few elements in cursor"); + return; + } + } + + if (elements.hasNext()) { + Address e = elements.next(); + fail("Too many elements in cursor: " + e.getAddressID()); + elements.close(); + } + } + + protected List> createOrdering(String... properties) { + StorableInfo
info = StorableIntrospector.examine(Address.class); + Map> props = info.getAllProperties(); + + List> ordered = new ArrayList>(); + + for (String prop : properties) { + ordered.add(OrderedProperty.get(props.get(prop), Direction.ASCENDING)); + } + + return ordered; + } + + 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..0972e94 --- /dev/null +++ b/src/test/java/com/amazon/carbonado/qe/TestSortedQueryExecutor.java @@ -0,0 +1,84 @@ +/* + * Copyright 2006 Amazon Technologies, Inc. or its affiliates. + * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks + * of Amazon Technologies, Inc. or its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.amazon.carbonado.qe; + +import java.util.List; + +import junit.framework.TestSuite; + +import com.amazon.carbonado.filter.Filter; +import com.amazon.carbonado.filter.FilterValues; + +import com.amazon.carbonado.info.OrderedProperty; + +import com.amazon.carbonado.stored.Address; + +/** + * + * + * @author Brian S O'Neill + */ +public class TestSortedQueryExecutor extends TestQueryExecutor { + public static void main(String[] args) { + junit.textui.TestRunner.run(suite()); + } + + public static TestSuite suite() { + return new TestSuite(TestSortedQueryExecutor.class); + } + + public void testBasicSorting() throws Exception { + QueryExecutor
unsorted = createExecutor(4, 2, 3, 1); + Filter
filter = Filter.getOpenFilter(Address.class); + FilterValues
values = filter.initialFilterValues(); + List> ordered = createOrdering("addressCountry"); + + QueryExecutor
executor = + new ArraySortedQueryExecutor
(unsorted, null, ordered); + + assertEquals(filter, executor.getFilter()); + + assertEquals(4, executor.count(values)); + + assertEquals(ordered, executor.getOrdering()); + + compareElements(executor.openCursor(values), 1, 2, 3, 4); + } + + public void testBasicFinisherSorting() throws Exception { + QueryExecutor
unsorted = createExecutor(1, 2, 3, 4); + Filter
filter = Filter.getOpenFilter(Address.class); + FilterValues
values = filter.initialFilterValues(); + List> handled = createOrdering("addressCountry"); + List> finisher = createOrdering("addressState"); + + QueryExecutor
executor = + new ArraySortedQueryExecutor
(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.openCursor(values), 1, 2, 3, 4); + } +} 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..639176c --- /dev/null +++ b/src/test/java/com/amazon/carbonado/qe/TestUnionQueryExecutor.java @@ -0,0 +1,75 @@ +/* + * Copyright 2006 Amazon Technologies, Inc. or its affiliates. + * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks + * of Amazon Technologies, Inc. or its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.amazon.carbonado.qe; + +import java.util.List; + +import junit.framework.TestSuite; + +import com.amazon.carbonado.filter.Filter; +import com.amazon.carbonado.filter.FilterValues; + +import com.amazon.carbonado.info.OrderedProperty; + +import com.amazon.carbonado.stored.Address; + +/** + * + * + * @author Brian S O'Neill + */ +public class TestUnionQueryExecutor extends TestQueryExecutor { + public static void main(String[] args) { + junit.textui.TestRunner.run(suite()); + } + + public static TestSuite suite() { + return new TestSuite(TestUnionQueryExecutor.class); + } + + public void testBasicUnion() throws Exception { + QueryExecutor
primary = new ArraySortedQueryExecutor
+ (createExecutor(1, 2, 3, 4, 5, 6, 7, 8), null, createOrdering("addressID")); + + Filter
filter_1 = Filter.filterFor(Address.class, "addressCountry > ?"); + FilterValues
values_1 = filter_1.initialFilterValues(); + QueryExecutor
executor_1 = new FilteredQueryExecutor
(primary, filter_1); + + Filter
filter_2 = Filter.filterFor(Address.class, "addressState <= ?"); + FilterValues
values_2 = filter_2.initialFilterValues(); + QueryExecutor
executor_2 = new FilteredQueryExecutor
(primary, filter_2); + + QueryExecutor
union = new UnionQueryExecutor
(executor_1, executor_2); + + Filter
filter = Filter + .filterFor(Address.class, "addressCountry > ? | addressState <= ?"); + FilterValues
values = filter.initialFilterValues(); + filter = values.getFilter(); + + assertEquals(filter, union.getFilter()); + + values = values.with("country_6").with("state_3"); + + assertEquals(5, union.count(values)); + + assertEquals(primary.getOrdering(), union.getOrdering()); + + compareElements(union.openCursor(values), 1, 2, 3, 7, 8); + } +} -- cgit v1.2.3