diff options
6 files changed, 327 insertions, 31 deletions
diff --git a/src/main/java/com/amazon/carbonado/qe/CompositeScore.java b/src/main/java/com/amazon/carbonado/qe/CompositeScore.java index c8e9490..6d0fad5 100644 --- a/src/main/java/com/amazon/carbonado/qe/CompositeScore.java +++ b/src/main/java/com/amazon/carbonado/qe/CompositeScore.java @@ -166,24 +166,38 @@ public class CompositeScore<S extends Storable> { return result;
}
- result = FilteringScore.rangeComparator()
- .compare(first.getFilteringScore(), second.getFilteringScore());
+ FilteringScore<?> firstScore = first.getFilteringScore();
+ FilteringScore<?> secondScore = second.getFilteringScore();
+
+ result = FilteringScore.rangeComparator().compare(firstScore, secondScore);
if (result != 0) {
return result;
}
- result = OrderingScore.fullComparator()
- .compare(first.getOrderingScore(), second.getOrderingScore());
+ if (considerOrdering(firstScore) && considerOrdering(secondScore)) {
+ // Only consider ordering if index is fast (clustered) or if
+ // index is used for any significant filtering. A full scan of
+ // an index just to get desired ordering can be very expensive
+ // due to random access I/O. A sort operation is often faster.
- if (result != 0) {
- return result;
+ result = OrderingScore.fullComparator()
+ .compare(first.getOrderingScore(), second.getOrderingScore());
+
+ if (result != 0) {
+ return result;
+ }
}
- result = FilteringScore.fullComparator()
- .compare(first.getFilteringScore(), second.getFilteringScore());
+ result = FilteringScore.fullComparator().compare(firstScore, secondScore);
return result;
}
+
+ private boolean considerOrdering(FilteringScore<?> score) {
+ return score.isIndexClustered()
+ || score.getIdentityCount() > 0
+ || score.hasRangeMatch();
+ }
}
}
diff --git a/src/main/java/com/amazon/carbonado/qe/IndexedQueryAnalyzer.java b/src/main/java/com/amazon/carbonado/qe/IndexedQueryAnalyzer.java index 76f0b52..dfa76ac 100644 --- a/src/main/java/com/amazon/carbonado/qe/IndexedQueryAnalyzer.java +++ b/src/main/java/com/amazon/carbonado/qe/IndexedQueryAnalyzer.java @@ -371,6 +371,15 @@ public class IndexedQueryAnalyzer<S extends Storable> { }
/**
+ * Returns true if local or foreign index is clustered. Scans of
+ * clustered indexes are generally faster.
+ */
+ public boolean isIndexClustered() {
+ return (mLocalIndex != null && mLocalIndex.isClustered())
+ || (mForeignIndex != null && mForeignIndex.isClustered());
+ }
+
+ /**
* Returns true if the given result uses the same index as this, and in
* the same way. The only allowed differences are in the remainder
* filter and orderings.
diff --git a/src/main/java/com/amazon/carbonado/qe/IndexedQueryExecutor.java b/src/main/java/com/amazon/carbonado/qe/IndexedQueryExecutor.java index d87740b..cc342c6 100644 --- a/src/main/java/com/amazon/carbonado/qe/IndexedQueryExecutor.java +++ b/src/main/java/com/amazon/carbonado/qe/IndexedQueryExecutor.java @@ -177,6 +177,10 @@ public class IndexedQueryExecutor<S extends Storable> extends AbstractQueryExecu filter = filter == null ? p : filter.and(p);
}
+ if (filter == null) {
+ return Filter.getOpenFilter(getStorableType());
+ }
+
return filter;
}
diff --git a/src/main/java/com/amazon/carbonado/qe/UnionQueryAnalyzer.java b/src/main/java/com/amazon/carbonado/qe/UnionQueryAnalyzer.java index 5933f6c..c64e4d4 100644 --- a/src/main/java/com/amazon/carbonado/qe/UnionQueryAnalyzer.java +++ b/src/main/java/com/amazon/carbonado/qe/UnionQueryAnalyzer.java @@ -19,6 +19,7 @@ package com.amazon.carbonado.qe;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.HashSet;
@@ -184,15 +185,28 @@ public class UnionQueryAnalyzer<S extends Storable> implements QueryExecutorFact // Keep looping until total ordering achieved.
while (true) {
- // For each ordering score, find the next free property. If
- // property is in the super key increment a tally associated with
- // property direction. Choose the property with the best tally and
- // augment the orderings with it and create new sub-results.
- // Remove the property from the super key and the key set. If any
- // key is now fully covered, a total ordering has been achieved.
+ // For each ordering score, iterate over the entire unused ordering
+ // properties and select the next free property. If property is in
+ // the super key increment a tally associated with property
+ // direction. Choose the property with the best tally and augment
+ // the orderings with it and create new sub-results. Remove the
+ // property from the super key and the key set. If any key is now
+ // fully covered, a total ordering has been achieved.
for (IndexedQueryAnalyzer<S>.Result result : subResults) {
OrderingScore<S> score = result.getCompositeScore().getOrderingScore();
+
+ OrderingList<S> unused = score.getUnusedOrdering();
+ if (unused.size() > 0) {
+ for (OrderedProperty<S> prop : unused) {
+ ChainedProperty<S> chainedProp = prop.getChainedProperty();
+ Tally tally = superKey.get(chainedProp);
+ if (tally != null) {
+ tally.increment(prop.getDirection());
+ }
+ }
+ }
+
OrderingList<S> free = score.getFreeOrdering();
if (free.size() > 0) {
OrderedProperty<S> prop = free.get(0);
@@ -237,7 +251,9 @@ public class UnionQueryAnalyzer<S extends Storable> implements QueryExecutorFact /**
* Returns a list of all primary and alternate keys, stripped of ordering.
*/
- private List<Set<ChainedProperty<S>>> getKeys() {
+ private List<Set<ChainedProperty<S>>> getKeys()
+ throws SupportException, RepositoryException
+ {
StorableInfo<S> info = StorableIntrospector.examine(mIndexAnalyzer.getStorableType());
List<Set<ChainedProperty<S>>> keys = new ArrayList<Set<ChainedProperty<S>>>();
@@ -247,6 +263,26 @@ public class UnionQueryAnalyzer<S extends Storable> implements QueryExecutorFact keys.add(stripOrdering(altKey.getProperties()));
}
+ // Also fold in all unique indexes, just in case they weren't reported
+ // as actual keys.
+ Collection<StorableIndex<S>> indexes =
+ mRepoAccess.storageAccessFor(getStorableType()).getAllIndexes();
+
+ for (StorableIndex<S> index : indexes) {
+ if (!index.isUnique()) {
+ continue;
+ }
+
+ int propCount = index.getPropertyCount();
+ Set<ChainedProperty<S>> props = new HashSet<ChainedProperty<S>>(propCount);
+
+ for (int i=0; i<propCount; i++) {
+ props.add(index.getOrderedProperty(i).getChainedProperty());
+ }
+
+ keys.add(props);
+ }
+
return keys;
}
@@ -332,6 +368,15 @@ public class UnionQueryAnalyzer<S extends Storable> implements QueryExecutorFact full = result;
break;
}
+ if (!result.getCompositeScore().getFilteringScore().hasAnyMatches()) {
+ if (full == null) {
+ // This index is used only for its ordering, and it will be
+ // tentatively selected as the "full scan". If a result is
+ // found doesn't use an index for anything, then it becomes
+ // the "full scan" index.
+ full = result;
+ }
+ }
}
if (full == null) {
diff --git a/src/test/java/com/amazon/carbonado/qe/TestIndexedQueryAnalyzer.java b/src/test/java/com/amazon/carbonado/qe/TestIndexedQueryAnalyzer.java index 740f26c..ef1711f 100644 --- a/src/test/java/com/amazon/carbonado/qe/TestIndexedQueryAnalyzer.java +++ b/src/test/java/com/amazon/carbonado/qe/TestIndexedQueryAnalyzer.java @@ -45,6 +45,7 @@ import com.amazon.carbonado.stored.Address; import com.amazon.carbonado.stored.Order;
import com.amazon.carbonado.stored.Shipment;
import com.amazon.carbonado.stored.Shipper;
+import com.amazon.carbonado.stored.StorableTestBasic;
import static com.amazon.carbonado.qe.TestIndexedQueryExecutor.Mock;
@@ -294,6 +295,14 @@ public class TestIndexedQueryAnalyzer extends TestCase { indexes = new StorableIndex[] {
makeIndex(mType, "shipperID")
};
+ } else if (StorableTestBasic.class.isAssignableFrom(mType)) {
+ indexes = new StorableIndex[] {
+ makeIndex(mType, "id").unique(true).clustered(true),
+ makeIndex(mType, "stringProp", "doubleProp").unique(true),
+ makeIndex(mType, "-stringProp", "-intProp", "~id").unique(true),
+ makeIndex(mType, "+intProp", "stringProp", "~id").unique(true),
+ makeIndex(mType, "-doubleProp", "+longProp", "~id").unique(true),
+ };
} else {
indexes = new StorableIndex[0];
}
diff --git a/src/test/java/com/amazon/carbonado/qe/TestUnionQueryAnalyzer.java b/src/test/java/com/amazon/carbonado/qe/TestUnionQueryAnalyzer.java index 0278eab..3d99c84 100644 --- a/src/test/java/com/amazon/carbonado/qe/TestUnionQueryAnalyzer.java +++ b/src/test/java/com/amazon/carbonado/qe/TestUnionQueryAnalyzer.java @@ -39,6 +39,7 @@ import com.amazon.carbonado.stored.Address; import com.amazon.carbonado.stored.Order;
import com.amazon.carbonado.stored.Shipment;
import com.amazon.carbonado.stored.Shipper;
+import com.amazon.carbonado.stored.StorableTestBasic;
import static com.amazon.carbonado.qe.TestIndexedQueryExecutor.Mock;
@@ -112,6 +113,7 @@ public class TestUnionQueryAnalyzer extends TestCase { "shipmentID = ? | orderID = ?");
filter = filter.bind();
UnionQueryAnalyzer.Result result = uqa.analyze(filter, null);
+ assertEquals(OrderingList.get(Shipment.class, "+shipmentID"), result.getTotalOrdering());
List<IndexedQueryAnalyzer<Shipment>.Result> subResults = result.getSubResults();
assertEquals(2, subResults.size());
@@ -143,6 +145,7 @@ public class TestUnionQueryAnalyzer extends TestCase { "shipmentID = ? | orderID > ?");
filter = filter.bind();
UnionQueryAnalyzer.Result result = uqa.analyze(filter, null);
+ assertEquals(OrderingList.get(Shipment.class, "+shipmentID"), result.getTotalOrdering());
List<IndexedQueryAnalyzer<Shipment>.Result> subResults = result.getSubResults();
assertEquals(2, subResults.size());
@@ -157,19 +160,14 @@ public class TestUnionQueryAnalyzer extends TestCase { assertEquals(null, res_0.getForeignProperty());
assertEquals(0, res_0.getRemainderOrdering().size());
- // Note: index that has proper ordering is preferred because "orderId > ?"
- // filter does not specify a complete range. It is not expected to actually
- // filter much, so we choose to avoid a large sort instead.
assertTrue(res_1.handlesAnything());
- assertFalse(res_1.getCompositeScore().getFilteringScore().hasRangeStart());
+ assertTrue(res_1.getCompositeScore().getFilteringScore().hasRangeStart());
assertFalse(res_1.getCompositeScore().getFilteringScore().hasRangeEnd());
- assertEquals(makeIndex(Shipment.class, "shipmentID"), res_1.getLocalIndex());
+ assertEquals(makeIndex(Shipment.class, "orderID"), res_1.getLocalIndex());
assertEquals(null, res_1.getForeignIndex());
assertEquals(null, res_1.getForeignProperty());
- assertEquals(0, res_0.getRemainderOrdering().size());
- // Remainder filter exists because the "orderID" index was not chosen.
- assertEquals(Filter.filterFor(Shipment.class, "orderID > ?").bind(),
- res_1.getRemainderFilter());
+ assertEquals(1, res_1.getRemainderOrdering().size());
+ assertEquals("+shipmentID", res_1.getRemainderOrdering().get(0).toString());
}
public void testSimpleUnion3() throws Exception {
@@ -179,6 +177,7 @@ public class TestUnionQueryAnalyzer extends TestCase { "shipmentID = ? | orderID > ? & orderID <= ?");
filter = filter.bind();
UnionQueryAnalyzer.Result result = uqa.analyze(filter, null);
+ assertEquals(OrderingList.get(Shipment.class, "+shipmentID"), result.getTotalOrdering());
List<IndexedQueryAnalyzer<Shipment>.Result> subResults = result.getSubResults();
assertEquals(2, subResults.size());
@@ -222,6 +221,8 @@ public class TestUnionQueryAnalyzer extends TestCase { OrderingList<Shipment> orderings =
makeOrdering(Shipment.class, "~shipmentID", "~orderID");
UnionQueryAnalyzer.Result result = uqa.analyze(filter, orderings);
+ assertEquals(OrderingList.get(Shipment.class, "+shipmentID", "+orderID"),
+ result.getTotalOrdering());
List<IndexedQueryAnalyzer<Shipment>.Result> subResults = result.getSubResults();
assertEquals(2, subResults.size());
@@ -341,20 +342,234 @@ public class TestUnionQueryAnalyzer extends TestCase { IndexedQueryAnalyzer<Shipment>.Result res_1 = subResults.get(1);
assertTrue(res_0.handlesAnything());
- assertEquals(makeIndex(Shipment.class, "shipmentID"), res_0.getLocalIndex());
+ assertEquals(makeIndex(Shipment.class, "orderID"), res_0.getLocalIndex());
assertEquals(null, res_0.getForeignIndex());
assertEquals(null, res_0.getForeignProperty());
- assertEquals(0, res_0.getRemainderOrdering().size());
- assertEquals(Filter.filterFor(Shipment.class, "shipmentNotes = ?").bind(),
+ assertEquals(1, res_0.getRemainderOrdering().size());
+ assertEquals("+shipmentID", res_0.getRemainderOrdering().get(0).toString());
+ assertEquals(Filter.filterFor(Shipment.class, "order.orderTotal > ?").bind(),
res_0.getRemainderFilter());
assertTrue(res_1.handlesAnything());
- assertEquals(makeIndex(Shipment.class, "orderID"), res_1.getLocalIndex());
+ assertEquals(makeIndex(Shipment.class, "shipmentID"), res_1.getLocalIndex());
assertEquals(null, res_1.getForeignIndex());
assertEquals(null, res_1.getForeignProperty());
- assertEquals(1, res_1.getRemainderOrdering().size());
- assertEquals("+shipmentID", res_1.getRemainderOrdering().get(0).toString());
- assertEquals(Filter.filterFor(Shipment.class, "order.orderTotal > ?").bind(),
+ assertEquals(0, res_1.getRemainderOrdering().size());
+ assertEquals(Filter.filterFor(Shipment.class, "shipmentNotes = ?").bind(),
res_1.getRemainderFilter());
+
+ }
+
+ public void testComplexUnionPlan() throws Exception {
+ UnionQueryAnalyzer uqa =
+ new UnionQueryAnalyzer(StorableTestBasic.class,
+ TestIndexedQueryAnalyzer.RepoAccess.INSTANCE);
+ Filter<StorableTestBasic> filter = Filter.filterFor
+ (StorableTestBasic.class, "doubleProp = ? | (stringProp = ? & intProp = ?) | id > ?");
+ filter = filter.bind();
+ UnionQueryAnalyzer.Result result = uqa.analyze(filter, null);
+ assertEquals(OrderingList.get(StorableTestBasic.class, "+id"), result.getTotalOrdering());
+
+ QueryExecutor<StorableTestBasic> exec = result.createExecutor();
+
+ assertEquals(filter, exec.getFilter());
+ assertEquals(OrderingList.get(StorableTestBasic.class, "+id"), exec.getOrdering());
+
+ List<IndexedQueryAnalyzer<Shipment>.Result> subResults = result.getSubResults();
+
+ assertEquals(3, subResults.size());
+
+ StringBuffer buf = new StringBuffer();
+ exec.printPlan(buf, 0, null);
+ String plan = buf.toString();
+
+ String expexted =
+ "union\n" +
+ " sort: [+id]\n" +
+ " index scan: com.amazon.carbonado.stored.StorableTestBasic\n" +
+ " ...index: {properties=[-doubleProp, +longProp, ~id], unique=true}\n" +
+ " ...identity filter: doubleProp = ?\n" +
+ " index scan: com.amazon.carbonado.stored.StorableTestBasic\n" +
+ " ...index: {properties=[-stringProp, -intProp, ~id], unique=true}\n" +
+ " ...identity filter: stringProp = ? & intProp = ?\n" +
+ " clustered index scan: com.amazon.carbonado.stored.StorableTestBasic\n" +
+ " ...index: {properties=[+id], unique=true}\n" +
+ " ...range filter: id > ?\n";
+
+ // Test test will fail if the format of the plan changes.
+ assertEquals(expexted, plan);
+ }
+
+ public void testComplexUnionPlan2() throws Exception {
+ UnionQueryAnalyzer uqa =
+ new UnionQueryAnalyzer(StorableTestBasic.class,
+ TestIndexedQueryAnalyzer.RepoAccess.INSTANCE);
+ Filter<StorableTestBasic> filter = Filter.filterFor
+ (StorableTestBasic.class, "doubleProp = ? | stringProp = ?");
+ filter = filter.bind();
+ UnionQueryAnalyzer.Result result = uqa.analyze(filter, null);
+ assertEquals(OrderingList.get(StorableTestBasic.class, "+doubleProp", "+stringProp"),
+ result.getTotalOrdering());
+
+ QueryExecutor<StorableTestBasic> exec = result.createExecutor();
+
+ assertEquals(filter, exec.getFilter());
+ assertEquals(OrderingList.get(StorableTestBasic.class, "+doubleProp", "+stringProp"),
+ exec.getOrdering());
+
+ List<IndexedQueryAnalyzer<Shipment>.Result> subResults = result.getSubResults();
+
+ assertEquals(2, subResults.size());
+
+ StringBuffer buf = new StringBuffer();
+ exec.printPlan(buf, 0, null);
+ String plan = buf.toString();
+
+ String expexted =
+ "union\n" +
+ " sort: [+stringProp]\n" +
+ " index scan: com.amazon.carbonado.stored.StorableTestBasic\n" +
+ " ...index: {properties=[-doubleProp, +longProp, ~id], unique=true}\n" +
+ " ...identity filter: doubleProp = ?\n" +
+ " index scan: com.amazon.carbonado.stored.StorableTestBasic\n" +
+ " ...index: {properties=[+stringProp, +doubleProp], unique=true}\n" +
+ " ...identity filter: stringProp = ?\n";
+
+ // Test test will fail if the format of the plan changes.
+ assertEquals(expexted, plan);
+ }
+
+ public void testComplexUnionPlan3() throws Exception {
+ UnionQueryAnalyzer uqa =
+ new UnionQueryAnalyzer(StorableTestBasic.class,
+ TestIndexedQueryAnalyzer.RepoAccess.INSTANCE);
+ Filter<StorableTestBasic> filter = Filter.filterFor
+ (StorableTestBasic.class, "stringProp = ? | stringProp = ?");
+ filter = filter.bind();
+ UnionQueryAnalyzer.Result result = uqa.analyze(filter, null);
+
+ boolean a = result.getTotalOrdering() ==
+ OrderingList.get(StorableTestBasic.class, "+doubleProp", "+stringProp");
+ boolean b = result.getTotalOrdering() ==
+ OrderingList.get(StorableTestBasic.class, "+stringProp", "+doubleProp");
+
+ assertTrue(a || b);
+
+ QueryExecutor<StorableTestBasic> exec = result.createExecutor();
+
+ assertEquals(filter, exec.getFilter());
+
+ a = exec.getOrdering() ==
+ OrderingList.get(StorableTestBasic.class, "+doubleProp", "+stringProp");
+ b = exec.getOrdering() ==
+ OrderingList.get(StorableTestBasic.class, "+stringProp", "+doubleProp");
+
+ assertTrue(a || b);
+
+ List<IndexedQueryAnalyzer<Shipment>.Result> subResults = result.getSubResults();
+
+ assertEquals(2, subResults.size());
+
+ StringBuffer buf = new StringBuffer();
+ exec.printPlan(buf, 0, null);
+ String plan = buf.toString();
+
+ String expexted =
+ "union\n" +
+ " index scan: com.amazon.carbonado.stored.StorableTestBasic\n" +
+ " ...index: {properties=[+stringProp, +doubleProp], unique=true}\n" +
+ " ...identity filter: stringProp = ?\n" +
+ " index scan: com.amazon.carbonado.stored.StorableTestBasic\n" +
+ " ...index: {properties=[+stringProp, +doubleProp], unique=true}\n" +
+ " ...identity filter: stringProp = ?[2]\n";
+
+ // Test test will fail if the format of the plan changes.
+ assertEquals(expexted, plan);
+ }
+
+ public void testComplexUnionPlan4() throws Exception {
+ UnionQueryAnalyzer uqa =
+ new UnionQueryAnalyzer(StorableTestBasic.class,
+ TestIndexedQueryAnalyzer.RepoAccess.INSTANCE);
+ Filter<StorableTestBasic> filter = Filter.filterFor
+ (StorableTestBasic.class, "doubleProp = ? | stringProp = ? | id > ?");
+ filter = filter.bind();
+ UnionQueryAnalyzer.Result result = uqa.analyze(filter, null);
+ assertEquals(OrderingList.get(StorableTestBasic.class, "+doubleProp", "+id"),
+ result.getTotalOrdering());
+
+ QueryExecutor<StorableTestBasic> exec = result.createExecutor();
+
+ assertEquals(filter, exec.getFilter());
+ assertEquals(OrderingList.get(StorableTestBasic.class, "+doubleProp", "+id"),
+ exec.getOrdering());
+
+ List<IndexedQueryAnalyzer<Shipment>.Result> subResults = result.getSubResults();
+
+ assertEquals(3, subResults.size());
+
+ StringBuffer buf = new StringBuffer();
+ exec.printPlan(buf, 0, null);
+ String plan = buf.toString();
+
+ String expexted =
+ "union\n" +
+ " sort: [+id]\n" +
+ " index scan: com.amazon.carbonado.stored.StorableTestBasic\n" +
+ " ...index: {properties=[-doubleProp, +longProp, ~id], unique=true}\n" +
+ " ...identity filter: doubleProp = ?\n" +
+ " sort: [+doubleProp], [+id]\n" +
+ " index scan: com.amazon.carbonado.stored.StorableTestBasic\n" +
+ " ...index: {properties=[+stringProp, +doubleProp], unique=true}\n" +
+ " ...identity filter: stringProp = ?\n" +
+ " sort: [+doubleProp, +id]\n" +
+ " clustered index scan: com.amazon.carbonado.stored.StorableTestBasic\n" +
+ " ...index: {properties=[+id], unique=true}\n" +
+ " ...range filter: id > ?\n";
+
+ // Test test will fail if the format of the plan changes.
+ assertEquals(expexted, plan);
+ }
+
+ public void testComplexUnionPlan5() throws Exception {
+ UnionQueryAnalyzer uqa =
+ new UnionQueryAnalyzer(StorableTestBasic.class,
+ TestIndexedQueryAnalyzer.RepoAccess.INSTANCE);
+ Filter<StorableTestBasic> filter = Filter.filterFor
+ (StorableTestBasic.class, "stringProp = ? | stringProp = ? | id > ?");
+ filter = filter.bind();
+ UnionQueryAnalyzer.Result result = uqa.analyze(filter, null);
+ assertEquals(OrderingList.get(StorableTestBasic.class, "+stringProp", "+doubleProp"),
+ result.getTotalOrdering());
+
+ QueryExecutor<StorableTestBasic> exec = result.createExecutor();
+
+ assertEquals(filter, exec.getFilter());
+ assertEquals(OrderingList.get(StorableTestBasic.class, "+stringProp", "+doubleProp"),
+ exec.getOrdering());
+
+ List<IndexedQueryAnalyzer<Shipment>.Result> subResults = result.getSubResults();
+
+ assertEquals(3, subResults.size());
+
+ StringBuffer buf = new StringBuffer();
+ exec.printPlan(buf, 0, null);
+ String plan = buf.toString();
+
+ String expexted =
+ "union\n" +
+ " index scan: com.amazon.carbonado.stored.StorableTestBasic\n" +
+ " ...index: {properties=[+stringProp, +doubleProp], unique=true}\n" +
+ " ...identity filter: stringProp = ?\n" +
+ " index scan: com.amazon.carbonado.stored.StorableTestBasic\n" +
+ " ...index: {properties=[+stringProp, +doubleProp], unique=true}\n" +
+ " ...identity filter: stringProp = ?[2]\n" +
+ " sort: [+stringProp, +doubleProp]\n" +
+ " clustered index scan: com.amazon.carbonado.stored.StorableTestBasic\n" +
+ " ...index: {properties=[+id], unique=true}\n" +
+ " ...range filter: id > ?\n";
+
+ // Test test will fail if the format of the plan changes.
+ assertEquals(expexted, plan);
}
}
|