diff --git oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndex.java oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndex.java index 885220a..dae6583 100644 --- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndex.java +++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndex.java @@ -119,30 +119,32 @@ public class OrderedPropertyIndex extends PropertyIndex implements AdvancedQuery String propertyName = PathUtils.getName(pr.propertyName); if (lookup.isIndexed(propertyName, "/", filter)) { PropertyValue value = null; - boolean createPlan = true; + boolean createPlan = false; if (pr.first == null && pr.last == null) { // open query: [property] is not null value = null; + createPlan = true; } else if (pr.first != null && pr.first.equals(pr.last) && pr.firstIncluding && pr.lastIncluding) { // [property]=[value] value = pr.first; -// ----------- DISABLING RANGE QUERIES FOR NOW. EASYING THE INTEGRATION WITH OAK-622 [BEGIN] -// } else if (pr.first != null && !pr.first.equals(pr.last)) { -// // '>' & '>=' use cases -// if (lookup.isAscending(root, propertyName, filter)) { -// value = pr.first; -// } else { -// createPlan = false; -// } -// } else if (pr.last != null && !pr.last.equals(pr.first)) { -// // '<' & '<=' -// if (!lookup.isAscending(root, propertyName, filter)) { -// value = pr.last; -// } else { -// createPlan = false; -// } -// ----------- DISABLING RANGE QUERIES FOR NOW. EASYING THE INTEGRATION WITH OAK-622 [ END ] + createPlan = true; + } else if (pr.first != null && !pr.first.equals(pr.last)) { + // '>' & '>=' use cases + if (lookup.isAscending(root, propertyName, filter)) { + value = pr.first; + createPlan = true; + } else { + createPlan = false; + } + } else if (pr.last != null && !pr.last.equals(pr.first)) { + // '<' & '<=' + if (!lookup.isAscending(root, propertyName, filter)) { + value = pr.last; + createPlan = true; + } else { + createPlan = false; + } } if (createPlan) { // we always return a sorted set @@ -174,7 +176,6 @@ public class OrderedPropertyIndex extends PropertyIndex implements AdvancedQuery LOG.debug("getPlanDescription() - plan: {}", plan); LOG.error("Not implemented yet"); throw new UnsupportedOperationException("Not implemented yet."); -// return null; } @Override diff --git oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/OrderedContentMirrorStoreStrategy.java oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/OrderedContentMirrorStoreStrategy.java index d731dbb..104c118 100644 --- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/OrderedContentMirrorStoreStrategy.java +++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/OrderedContentMirrorStoreStrategy.java @@ -44,7 +44,6 @@ import org.slf4j.LoggerFactory; import com.google.common.base.Predicate; import com.google.common.base.Strings; -import com.google.common.collect.Iterators; /** * Same as for {@link ContentMirrorStoreStrategy} but the order of the keys is kept by using the @@ -240,48 +239,7 @@ public class OrderedContentMirrorStoreStrategy extends ContentMirrorStoreStrateg // if the property is not there or is empty it means we're empty cne = Collections.emptyList(); } else { - cne = new Iterable() { - private NodeState localIndex = index; - private NodeState localStart = includeStart && !start.exists() ? EMPTY_START_NODE - : start; - private NodeState current = localStart; - private boolean localIncludeStart = includeStart; - - @Override - public Iterator iterator() { - return new Iterator() { - - @Override - public boolean hasNext() { - return (localIncludeStart && localStart.equals(current)) || (!localIncludeStart && !Strings.isNullOrEmpty(current.getString(NEXT))); - } - - @Override - public ChildNodeEntry next() { - ChildNodeEntry localCNE = null; - if (localIncludeStart && localStart.equals(current)) { - localCNE = new OrderedChildNodeEntry(START, current); - // let's set it to false. We just included it. - localIncludeStart = false; - } else { - if (hasNext()) { - final String name = current.getString(NEXT); - current = localIndex.getChildNode(name); - localCNE = new OrderedChildNodeEntry(name, current); - } else { - throw new NoSuchElementException(); - } - } - return localCNE; - } - - @Override - public void remove() { - throw new UnsupportedOperationException(); - } - }; - } - }; + cne = new FullIterable(index, includeStart); } return cne; } @@ -319,52 +277,26 @@ public class OrderedContentMirrorStoreStrategy extends ContentMirrorStoreStrateg if (pr.first != null && !pr.first.equals(pr.last)) { // '>' & '>=' use case - return new Iterable() { - private PropertyRestriction lpr = pr; - - @Override - public Iterator iterator() { - PathIterator pi = new PathIterator(filter, indexName); - Iterator children = getChildNodeEntries( - index).iterator(); - pi.setPathContainsValue(true); - pi.enqueue(Iterators.filter(children, - new Predicate() { - @Override - public boolean apply(ChildNodeEntry entry) { - String value = lpr.first - .getValue(Type.STRING); - String name = convert(entry.getName()); - return value.compareTo(name) < 0 || (lpr.firstIncluding && value - .equals(name)); - } - })); - return pi; - } - }; + ChildNodeEntry firstValueableItem = seek(index, + new PredicateGreaterThan(pr.first.getValue(Type.STRING), pr.firstIncluding)); + Iterable it = Collections.emptyList(); + if (firstValueableItem != null) { + Iterable childrenIterable = (pr.last == null) ? new SeekedIterable( + index, firstValueableItem) : new BetweenIterable(index, firstValueableItem, + pr.last.getValue(Type.STRING), pr.lastIncluding); + it = new QueryResultsWrapper(filter, indexName, childrenIterable); + } + return it; } else if (pr.last != null && !pr.last.equals(pr.first)) { // '<' & '<=' use case - return new Iterable() { - private PropertyRestriction lpr = pr; - - @Override - public Iterator iterator() { - PathIterator pi = new PathIterator(filter, indexName); - Iterator children = getChildNodeEntries(index) - .iterator(); - pi.setPathContainsValue(true); - pi.enqueue(Iterators.filter(children, new Predicate() { - @Override - public boolean apply(ChildNodeEntry entry) { - String value = lpr.last.getValue(Type.STRING); - String name = convert(entry.getName()); - return (value.compareTo(name) > 0) - || (lpr.lastIncluding && value.equals(name)); - } - })); - return pi; - } - }; + ChildNodeEntry firstValueableItem = seek(index, + new PredicateLessThan(pr.last.getValue(Type.STRING), pr.lastIncluding)); + Iterable it = Collections.emptyList(); + if (firstValueableItem != null) { + it = new QueryResultsWrapper(filter, indexName, new SeekedIterable(index, + firstValueableItem)); + } + return it; } else { // property is not null. AKA "open query" Iterable values = null; @@ -499,4 +431,324 @@ public class OrderedContentMirrorStoreStrategy extends ContentMirrorStoreStrateg } return count; } + + /** + * wrap an {@code Iterable} in something that can be understood by the Query + * Engine + */ + private static class QueryResultsWrapper implements Iterable { + private Iterable children; + private String indexName; + private Filter filter; + + public QueryResultsWrapper(Filter filter, String indexName, + Iterable children) { + this.children = children; + this.indexName = indexName; + this.filter = filter; + } + + @Override + public Iterator iterator() { + PathIterator pi = new PathIterator(filter, indexName); + pi.setPathContainsValue(true); + pi.enqueue(children.iterator()); + return pi; + } + } + + /** + * iterating throughout the index in the correct order. Externalised as class for easy + * overloading. + */ + private static class FullIterator implements Iterator { + private boolean includeStart; + private NodeState start; + private NodeState current; + private NodeState index; + private String currentName; + + public FullIterator(NodeState index, NodeState start, boolean includeStart, + NodeState current) { + this.includeStart = includeStart; + this.start = start; + this.current = current; + this.index = index; + } + + @Override + public boolean hasNext() { + return (includeStart && start.equals(current)) + || (!includeStart && !Strings.isNullOrEmpty(current.getString(NEXT))); + } + + @Override + public ChildNodeEntry next() { + ChildNodeEntry entry = null; + if (includeStart && start.equals(current)) { + entry = new OrderedChildNodeEntry(START, current); + // let's set it to false. We just included it. + includeStart = false; + } else { + if (hasNext()) { + currentName = current.getString(NEXT); + current = index.getChildNode(currentName); + entry = new OrderedChildNodeEntry(currentName, current); + } else { + throw new NoSuchElementException(); + } + } + return entry; + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + + /** + * @return the name of the current node. May be null in some cases. + */ + @Nullable + String getCurrentName() { + return currentName; + } + } + + /** + * Convenience class for iterating throughout the index in the correct order + */ + private static class FullIterable implements Iterable { + private boolean includeStart; + + NodeState index; + NodeState start; + NodeState current; + + /** + * @param index the current index content state. The {@code :index} node + * @param includeStart whether include {@code :start} or not. + */ + public FullIterable(NodeState index, boolean includeStart) { + this.index = index; + this.includeStart = includeStart; + NodeState s = index.getChildNode(START); + if (includeStart && !s.exists()) { + start = EMPTY_START_NODE; + } else { + start = s; + } + current = start; + } + + @Override + public Iterator iterator() { + return new FullIterator(index, start, includeStart, current); + } + } + + /** + * Iterator that allows to start iterating from a given position + */ + private static class SeekedIterator extends FullIterator { + /** + * whether the seekeed item has been returned already or not. + */ + private boolean firstReturned = false; + + /** + * the seeked item + */ + private ChildNodeEntry first; + + public SeekedIterator(NodeState index, NodeState start, ChildNodeEntry first) { + super(index, start, false, first.getNodeState()); + this.first = first; + } + + @Override + public boolean hasNext() { + return !firstReturned || super.hasNext(); + } + + @Override + public ChildNodeEntry next() { + if (firstReturned) { + return super.next(); + } else { + firstReturned = true; + return first; + } + } + } + + /** + * iterable that starts at a provided position ({@code ChildNodeEntry}) + */ + private static class SeekedIterable extends FullIterable { + ChildNodeEntry first; + + public SeekedIterable(NodeState index, ChildNodeEntry first) { + super(index, false); + this.first = first; + } + + @Override + public Iterator iterator() { + return new SeekedIterator(index, start, first); + } + } + + /** + * seek for an element in the index given the provided Predicate + * + * @param index the index content node {@code :index} + * @param condition the predicate to evaluate + * @return the entry or null if not found + */ + static ChildNodeEntry seek(@Nonnull NodeState index, + @Nonnull Predicate condition) { + + // TODO the FullIterable will have to be replaced with something else once we'll have the + // Skip part of the list implemented. + Iterable children = new FullIterable(index, false); + ChildNodeEntry entry = null; + for (ChildNodeEntry child : children) { + if (condition.apply(child)) { + entry = child; + break; + } + } + return entry; + } + + /** + * predicate for evaluating 'key' equality across index + */ + static class PredicateEquals implements Predicate { + private String searchfor; + + public PredicateEquals(@Nonnull String searchfor) { + this.searchfor = searchfor; + } + + @Override + public boolean apply(ChildNodeEntry arg0) { + return (arg0 != null && searchfor.equals(arg0.getName())); + } + } + + /** + * evaluates when the current element is greater than (>) and greater than equal + * {@code searchfor} + */ + static class PredicateGreaterThan implements Predicate { + private String searchfor; + private boolean include; + + public PredicateGreaterThan(@Nonnull String searchfor) { + this(searchfor, false); + } + + public PredicateGreaterThan(@Nonnull String searchfor, boolean include) { + this.searchfor = searchfor; + this.include = include; + } + + @Override + public boolean apply(ChildNodeEntry arg0) { + boolean b = false; + if(arg0!=null){ + String name = convert(arg0.getName()); + b = (searchfor.compareTo(name) < 0 || (include && searchfor + .equals(name))); + } + + return b; + } + } + + /** + * evaluates when the current element is less than (<) and less than equal {@code searchfor} + */ + static class PredicateLessThan implements Predicate { + private String searchfor; + private boolean include; + + public PredicateLessThan(@Nonnull String searchfor) { + this(searchfor, false); + } + + public PredicateLessThan(@Nonnull String searchfor, boolean include) { + this.searchfor = searchfor; + this.include = include; + } + + @Override + public boolean apply(ChildNodeEntry arg0) { + boolean b = false; + if (arg0 != null) { + String name = convert(arg0.getName()); + b = (searchfor.compareTo(name) > 0 || (include && searchfor.equals(name))); + } + + return b; + } + } + + /** + * iterable for going through a set of data in the case of BETWEEN queries. We don't have to + * return more data for having the Query Engine to skip them later. + */ + private static class BetweenIterable extends SeekedIterable { + private String lastKey; + private boolean lastInclude; + + public BetweenIterable(NodeState index, ChildNodeEntry first, String lastKey, + boolean lastInclude) { + super(index, first); + this.lastKey = lastKey; + this.lastInclude = lastInclude; + } + + @Override + public Iterator iterator() { + return new BetweenIterator(index, start, first, lastKey, lastInclude); + } + } + + /** + * iterator for iterating in the cases of BETWEEN queries. + */ + private static class BetweenIterator extends SeekedIterator { + private String lastKey; + private boolean lastInclude; + + /** + * @param index the current index content {@code :index} + * @param start the {@code :start} node + * @param first the first valuable options for starting iterating from. + * @param lastKey the last key to be returned + * @param lastInclude whether including the last key or not. + */ + public BetweenIterator(NodeState index, NodeState start, ChildNodeEntry first, + String lastKey, boolean lastInclude) { + super(index, start, first); + this.lastInclude = lastInclude; + this.lastKey = lastKey; + } + + @Override + public boolean hasNext() { + boolean next = super.hasNext(); + String name = getCurrentName(); + + if (name != null && next) { + name = convert(name); + next = (next && (lastKey.compareTo(name) > 0 || (lastInclude && lastKey + .equals(name)))); + } + return next; + } + } } diff --git oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndexDescendingQueryTest.java oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndexDescendingQueryTest.java index 6c69e7b..47c690b 100644 --- oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndexDescendingQueryTest.java +++ oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndexDescendingQueryTest.java @@ -39,7 +39,6 @@ import org.apache.jackrabbit.oak.plugins.index.IndexUtils; import org.apache.jackrabbit.oak.plugins.index.property.OrderedIndex.OrderDirection; import org.apache.jackrabbit.oak.spi.query.PropertyValues; import org.apache.jackrabbit.oak.util.NodeUtil; -import org.junit.Ignore; import org.junit.Test; import com.google.common.collect.ImmutableMap; @@ -136,7 +135,7 @@ public class OrderedPropertyIndexDescendingQueryTest extends BasicOrderedPropert * test the range query in case of '>' condition * @throws Exception */ - @Test @Ignore("Disabling for now. Integration with OAK-622 and prioritising.") + @Test public void queryGreaterThan() throws Exception { initWithProperProvider(); setTravesalEnabled(false); @@ -172,7 +171,7 @@ public class OrderedPropertyIndexDescendingQueryTest extends BasicOrderedPropert * test the range query in case of '>=' condition * @throws Exception */ - @Test @Ignore("Disabling for now. Integration with OAK-622 and prioritising.") + @Test public void queryGreaterEqualThan() throws Exception { initWithProperProvider(); setTravesalEnabled(false); @@ -285,4 +284,40 @@ public class OrderedPropertyIndexDescendingQueryTest extends BasicOrderedPropert setTravesalEnabled(true); } + /** + * testing explicitly OAK-1561 use-case + * + * @throws CommitFailedException + * @throws ParseException + */ + @Test + public void queryGreaterThenWithCast() throws CommitFailedException, ParseException { + + setTravesalEnabled(false); + + final OrderDirection direction = OrderDirection.ASC; + final String query = "SELECT * FROM [nt:base] WHERE " + ORDERED_PROPERTY + + "> cast('%s' as date)"; + + // index automatically created by the framework: + // {@code createTestIndexNode()} + + // initialising the data + Tree rTree = root.getTree("/"); + Tree test = rTree.addChild("test"); + Calendar start = midnightFirstJan2013(); + addChildNodes(generateOrderedDates(NUMBER_OF_NODES, direction, start), test, direction, + Type.DATE); + root.commit(); + + Calendar searchForCalendar = (Calendar) start.clone(); + searchForCalendar.add(Calendar.HOUR_OF_DAY, 36); + String searchFor = ISO_8601_2000.format(searchForCalendar.getTime()); + Iterator results = executeQuery(String.format(query, searchFor), SQL2, + null).getRows().iterator(); + assertFalse("the index should not be used in this case", results.hasNext()); + + setTravesalEnabled(true); + + } } diff --git oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndexQueryTest.java oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndexQueryTest.java index 75c27a5..bc95961 100644 --- oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndexQueryTest.java +++ oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndexQueryTest.java @@ -55,7 +55,6 @@ import org.apache.jackrabbit.oak.spi.query.QueryIndex.IndexPlan; import org.apache.jackrabbit.oak.spi.state.NodeBuilder; import org.apache.jackrabbit.oak.spi.state.NodeState; import org.apache.jackrabbit.oak.util.NodeUtil; -import org.junit.Ignore; import org.junit.Test; import com.google.common.collect.ImmutableList; @@ -145,7 +144,7 @@ public class OrderedPropertyIndexQueryTest extends BasicOrderedPropertyIndexQuer * @throws CommitFailedException * @throws ParseException */ - @Test @Ignore("Disabling for now. Integration with OAK-622 and prioritising.") + @Test public void queryGreaterThan() throws CommitFailedException, ParseException { setTravesalEnabled(false); @@ -184,7 +183,7 @@ public class OrderedPropertyIndexQueryTest extends BasicOrderedPropertyIndexQuer * @throws CommitFailedException * @throws ParseException */ - @Test @Ignore("Disabling for now. Integration with OAK-622 and prioritising.") + @Test public void queryGreaterEqualThan() throws CommitFailedException, ParseException { setTravesalEnabled(false); @@ -226,7 +225,7 @@ public class OrderedPropertyIndexQueryTest extends BasicOrderedPropertyIndexQuer * provider. not the lowcost one. * @throws Exception */ - @Test @Ignore("Disabling for now. Integration with OAK-622 and prioritising.") + @Test public void queryLessThan() throws Exception { initWithProperProvider(); setTravesalEnabled(false); @@ -264,7 +263,7 @@ public class OrderedPropertyIndexQueryTest extends BasicOrderedPropertyIndexQuer * provider. not the lowcost one. * @throws Exception */ - @Test @Ignore("Disabling for now. Integration with OAK-622 and prioritising.") + @Test public void queryLessEqualThan() throws Exception { initWithProperProvider(); initWithProperProvider(); @@ -556,4 +555,245 @@ public class OrderedPropertyIndexQueryTest extends BasicOrderedPropertyIndexQuer setTravesalEnabled(true); } + /** + * testing explicitly OAK-1561 use-case + * + * @throws CommitFailedException + * @throws ParseException + */ + @Test + public void queryGreaterThenWithCast() throws CommitFailedException, ParseException { + + setTravesalEnabled(false); + + final OrderDirection direction = OrderDirection.ASC; + final String query = "SELECT * FROM [nt:base] WHERE " + ORDERED_PROPERTY + + "> cast('%s' as date)"; + + // index automatically created by the framework: + // {@code createTestIndexNode()} + + // initialising the data + Tree rTree = root.getTree("/"); + Tree test = rTree.addChild("test"); + Calendar start = midnightFirstJan2013(); + List nodes = addChildNodes( + generateOrderedDates(NUMBER_OF_NODES, direction, start), test, direction, Type.DATE); + root.commit(); + + Calendar searchForCalendar = (Calendar) start.clone(); + searchForCalendar.add(Calendar.HOUR_OF_DAY, 36); + String searchFor = ISO_8601_2000.format(searchForCalendar.getTime()); + Iterator results = executeQuery(String.format(query, searchFor), SQL2, + null).getRows().iterator(); + Iterator filtered = Iterables.filter(nodes, + new ValuePathTuple.GreaterThanPredicate(searchFor)).iterator(); + assertRightOrder(Lists.newArrayList(filtered), results); + assertFalse("We should have looped throuhg all the results", results.hasNext()); + + setTravesalEnabled(true); + + } + + @Test + public void queryBetweenNoIncludes() throws Exception { + setTravesalEnabled(false); + + final OrderDirection direction = OrderDirection.ASC; + final String query = "SELECT * FROM [nt:base] WHERE " + ORDERED_PROPERTY + "> $start AND " + + ORDERED_PROPERTY + " < $end"; + + // index automatically created by the framework: + // {@code createTestIndexNode()} + + // initialising the data + Tree rTree = root.getTree("/"); + Tree test = rTree.addChild("test"); + Calendar start = midnightFirstJan2013(); + + List nodes = addChildNodes( + generateOrderedDates(NUMBER_OF_NODES, direction, start), test, direction, Type.DATE); + root.commit(); + + Calendar searchForCalendarStart = (Calendar) start.clone(); + searchForCalendarStart.add(Calendar.HOUR_OF_DAY, 36); + String searchForStart = ISO_8601_2000.format(searchForCalendarStart.getTime()); + + Calendar endCalendar = Calendar.getInstance(); + endCalendar.setTime(ISO_8601_2000.parse(nodes.get(nodes.size() - 1).getValue())); + endCalendar.add(Calendar.HOUR_OF_DAY, -36); + String searchForEnd = ISO_8601_2000.format(endCalendar.getTime()); + + Map filter = ImmutableMap.of("start", + PropertyValues.newDate(searchForStart), "end", PropertyValues.newDate(searchForEnd)); + Iterator results = executeQuery(query, SQL2, filter).getRows() + .iterator(); + + Iterator filtered = Iterables.filter(nodes, + new ValuePathTuple.BetweenPredicate(searchForStart, searchForEnd, false, false)) + .iterator(); + + assertRightOrder(Lists.newArrayList(filtered), results); + assertFalse("We should have looped throuhg all the results", results.hasNext()); + + setTravesalEnabled(true); + + } + + @Test + public void queryBetweenIncludeLower() throws Exception { + setTravesalEnabled(false); + + final OrderDirection direction = OrderDirection.ASC; + final String query = "SELECT * FROM [nt:base] WHERE " + ORDERED_PROPERTY + ">= $start AND " + + ORDERED_PROPERTY + " < $end"; + + // index automatically created by the framework: + // {@code createTestIndexNode()} + + // initialising the data + Tree rTree = root.getTree("/"); + Tree test = rTree.addChild("test"); + Calendar start = midnightFirstJan2013(); + + List nodes = addChildNodes( + generateOrderedDates(NUMBER_OF_NODES, direction, start), test, direction, Type.DATE); + root.commit(); + + Calendar searchForCalendarStart = (Calendar) start.clone(); + searchForCalendarStart.add(Calendar.HOUR_OF_DAY, 36); + String searchForStart = ISO_8601_2000.format(searchForCalendarStart.getTime()); + + Calendar endCalendar = Calendar.getInstance(); + endCalendar.setTime(ISO_8601_2000.parse(nodes.get(nodes.size() - 1).getValue())); + endCalendar.add(Calendar.HOUR_OF_DAY, -36); + String searchForEnd = ISO_8601_2000.format(endCalendar.getTime()); + + Map filter = ImmutableMap.of("start", + PropertyValues.newDate(searchForStart), "end", PropertyValues.newDate(searchForEnd)); + Iterator results = executeQuery(query, SQL2, filter).getRows() + .iterator(); + + Iterator filtered = Iterables.filter(nodes, + new ValuePathTuple.BetweenPredicate(searchForStart, searchForEnd, true, false)) + .iterator(); + + assertRightOrder(Lists.newArrayList(filtered), results); + assertFalse("We should have looped throuhg all the results", results.hasNext()); + + setTravesalEnabled(true); + + } + + @Test + public void queryBetweenIncludeHigher() throws Exception { + setTravesalEnabled(false); + + final OrderDirection direction = OrderDirection.ASC; + final String query = "SELECT * FROM [nt:base] WHERE " + ORDERED_PROPERTY + "> $start AND " + + ORDERED_PROPERTY + " <= $end"; + + // index automatically created by the framework: + // {@code createTestIndexNode()} + + // initialising the data + Tree rTree = root.getTree("/"); + Tree test = rTree.addChild("test"); + Calendar start = midnightFirstJan2013(); + + List nodes = addChildNodes( + generateOrderedDates(10, direction, start), test, direction, Type.DATE); + root.commit(); + + for(ValuePathTuple n : nodes){ + System.out.println("+++" + n); + } + + Calendar searchForCalendarStart = (Calendar) start.clone(); + searchForCalendarStart.add(Calendar.HOUR_OF_DAY, 36); + String searchForStart = ISO_8601_2000.format(searchForCalendarStart.getTime()); + + Calendar endCalendar = Calendar.getInstance(); + endCalendar.setTime(ISO_8601_2000.parse(nodes.get(nodes.size() - 1).getValue())); + endCalendar.add(Calendar.HOUR_OF_DAY, -36); + String searchForEnd = ISO_8601_2000.format(endCalendar.getTime()); + + Map filter = ImmutableMap.of("start", + PropertyValues.newDate(searchForStart), "end", PropertyValues.newDate(searchForEnd)); + Iterator results = executeQuery(query, SQL2, filter).getRows() + .iterator(); + + Iterator filtered = Iterables.filter(nodes, + new ValuePathTuple.BetweenPredicate(searchForStart, searchForEnd, false, true)) + .iterator(); + + while (filtered.hasNext()) { + System.out.println("---" + filtered.next()); + } + filtered = Iterables.filter(nodes, + new ValuePathTuple.BetweenPredicate(searchForStart, searchForEnd, false, true)) + .iterator(); + + assertRightOrder(Lists.newArrayList(filtered), results); + assertFalse("We should have looped throuhg all the results", results.hasNext()); + + setTravesalEnabled(true); + + } + + @Test + public void queryBetweenIncludeBoth() throws Exception { + setTravesalEnabled(false); + + final OrderDirection direction = OrderDirection.ASC; + final String query = "SELECT * FROM [nt:base] WHERE " + ORDERED_PROPERTY + ">= $start AND " + + ORDERED_PROPERTY + " <= $end"; + + // index automatically created by the framework: + // {@code createTestIndexNode()} + + // initialising the data + Tree rTree = root.getTree("/"); + Tree test = rTree.addChild("test"); + Calendar start = midnightFirstJan2013(); + + List nodes = addChildNodes( + generateOrderedDates(10, direction, start), test, direction, Type.DATE); + root.commit(); + + for(ValuePathTuple n : nodes){ + System.out.println("+++" + n); + } + + Calendar searchForCalendarStart = (Calendar) start.clone(); + searchForCalendarStart.add(Calendar.HOUR_OF_DAY, 36); + String searchForStart = ISO_8601_2000.format(searchForCalendarStart.getTime()); + + Calendar endCalendar = Calendar.getInstance(); + endCalendar.setTime(ISO_8601_2000.parse(nodes.get(nodes.size() - 1).getValue())); + endCalendar.add(Calendar.HOUR_OF_DAY, -36); + String searchForEnd = ISO_8601_2000.format(endCalendar.getTime()); + + Map filter = ImmutableMap.of("start", + PropertyValues.newDate(searchForStart), "end", PropertyValues.newDate(searchForEnd)); + Iterator results = executeQuery(query, SQL2, filter).getRows() + .iterator(); + + Iterator filtered = Iterables.filter(nodes, + new ValuePathTuple.BetweenPredicate(searchForStart, searchForEnd, false, true)) + .iterator(); + + while (filtered.hasNext()) { + System.out.println("---" + filtered.next()); + } + filtered = Iterables.filter(nodes, + new ValuePathTuple.BetweenPredicate(searchForStart, searchForEnd, true, true)) + .iterator(); + + assertRightOrder(Lists.newArrayList(filtered), results); + assertFalse("We should have looped throuhg all the results", results.hasNext()); + + setTravesalEnabled(true); + + } } diff --git oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/ValuePathTuple.java oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/ValuePathTuple.java index d2df183..b78e611 100644 --- oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/ValuePathTuple.java +++ oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/ValuePathTuple.java @@ -87,6 +87,28 @@ public class ValuePathTuple implements Comparable { } + public static class BetweenPredicate implements Predicate { + private String start; + private String end; + private boolean includeStart; + private boolean includeEnd; + + public BetweenPredicate(String start, String end, boolean includeStart, boolean includeEnd) { + this.start = start; + this.end = end; + this.includeStart = includeStart; + this.includeEnd = includeEnd; + } + + @Override + public boolean apply(ValuePathTuple arg0) { + String other = arg0.getValue(); + return + (start.compareTo(other) < 0 || (includeStart && start.equals(other))) + && (end.compareTo(other) > 0 || (includeEnd && end.equals(other))); + } + } + ValuePathTuple(String value, String path) { this.value = value; this.path = path; diff --git oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/ValuePathTupleTest.java oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/ValuePathTupleTest.java index e2e6049..f72de42 100644 --- oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/ValuePathTupleTest.java +++ oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/ValuePathTupleTest.java @@ -149,4 +149,72 @@ public class ValuePathTupleTest { assertEquals("e", filtered.next().getValue()); assertFalse(filtered.hasNext()); } + + @Test + public void betweenNoIncludes() { + List data = ImmutableList.of( + new ValuePathTuple("a", "foobar"), + new ValuePathTuple("b", "foobar"), + new ValuePathTuple("c", "foobar"), + new ValuePathTuple("d", "foobar"), + new ValuePathTuple("e", "foobar"), + new ValuePathTuple("f", "foobar")); + Iterator filtered = Iterables.filter(data, + new ValuePathTuple.BetweenPredicate("b", "d", false, false)).iterator(); + assertTrue(filtered.hasNext()); + assertEquals("c", filtered.next().getValue()); + assertFalse(filtered.hasNext()); + } + + @Test + public void betweenIncludeStart() { + List data = ImmutableList.of( + new ValuePathTuple("a", "foobar"), + new ValuePathTuple("b", "foobar"), + new ValuePathTuple("c", "foobar"), + new ValuePathTuple("d", "foobar"), + new ValuePathTuple("e", "foobar"), + new ValuePathTuple("f", "foobar")); + Iterator filtered = Iterables.filter(data, + new ValuePathTuple.BetweenPredicate("b", "d", true, false)).iterator(); + assertTrue(filtered.hasNext()); + assertEquals("b", filtered.next().getValue()); + assertEquals("c", filtered.next().getValue()); + assertFalse(filtered.hasNext()); + } + + @Test + public void betweenIncludeEnd() { + List data = ImmutableList.of( + new ValuePathTuple("a", "foobar"), + new ValuePathTuple("b", "foobar"), + new ValuePathTuple("c", "foobar"), + new ValuePathTuple("d", "foobar"), + new ValuePathTuple("e", "foobar"), + new ValuePathTuple("f", "foobar")); + Iterator filtered = Iterables.filter(data, + new ValuePathTuple.BetweenPredicate("b", "d", false, true)).iterator(); + assertTrue(filtered.hasNext()); + assertEquals("c", filtered.next().getValue()); + assertEquals("d", filtered.next().getValue()); + assertFalse(filtered.hasNext()); + } + + @Test + public void betweenIncludeBoth() { + List data = ImmutableList.of( + new ValuePathTuple("a", "foobar"), + new ValuePathTuple("b", "foobar"), + new ValuePathTuple("c", "foobar"), + new ValuePathTuple("d", "foobar"), + new ValuePathTuple("e", "foobar"), + new ValuePathTuple("f", "foobar")); + Iterator filtered = Iterables.filter(data, + new ValuePathTuple.BetweenPredicate("b", "d", true, true)).iterator(); + assertTrue(filtered.hasNext()); + assertEquals("b", filtered.next().getValue()); + assertEquals("c", filtered.next().getValue()); + assertEquals("d", filtered.next().getValue()); + assertFalse(filtered.hasNext()); + } } diff --git oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/OrderedContentMirrorStorageStrategyTest.java oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/OrderedContentMirrorStorageStrategyTest.java index d10e33d..39f6124 100644 --- oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/OrderedContentMirrorStorageStrategyTest.java +++ oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/OrderedContentMirrorStorageStrategyTest.java @@ -23,6 +23,7 @@ import static org.apache.jackrabbit.oak.plugins.index.property.strategy.OrderedC import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import java.text.DecimalFormat; @@ -1360,4 +1361,229 @@ public class OrderedContentMirrorStorageStrategyTest { assertEquals(store.count(ascendingMeta, pr, maxNodeCount), descendingStore.count(descendingMeta, pr, maxNodeCount)); } + + @Test + public void seekEqualsNotFound() { + OrderedContentMirrorStoreStrategy store = new OrderedContentMirrorStoreStrategy(); + NodeBuilder index = EmptyNodeState.EMPTY_NODE.builder(); + String n0 = KEYS[1]; + String n1 = KEYS[3]; + String n2 = KEYS[2]; + String n3 = KEYS[0]; + String nonExisting = "dsrfgdrtfhg"; + + // initialising the store + store.update(index, "/a/b", EMPTY_KEY_SET, newHashSet(n0)); + store.update(index, "/a/b", EMPTY_KEY_SET, newHashSet(n1)); + store.update(index, "/a/b", EMPTY_KEY_SET, newHashSet(n2)); + store.update(index, "/a/b", EMPTY_KEY_SET, newHashSet(n3)); + + assertNull("The item should have not been found", OrderedContentMirrorStoreStrategy.seek( + index.getNodeState(), + new OrderedContentMirrorStoreStrategy.PredicateEquals(nonExisting))); + } + + @Test + public void seekEquals() { + OrderedContentMirrorStoreStrategy store = new OrderedContentMirrorStoreStrategy(); + NodeBuilder index = EmptyNodeState.EMPTY_NODE.builder(); + String n0 = KEYS[1]; + String n1 = KEYS[3]; + String n2 = KEYS[2]; + String n3 = KEYS[0]; + + // initialising the store + store.update(index, "/a/b", EMPTY_KEY_SET, newHashSet(n0)); + store.update(index, "/a/b", EMPTY_KEY_SET, newHashSet(n1)); + store.update(index, "/a/b", EMPTY_KEY_SET, newHashSet(n2)); + store.update(index, "/a/b", EMPTY_KEY_SET, newHashSet(n3)); + + String searchFor = n1; + + ChildNodeEntry item = OrderedContentMirrorStoreStrategy.seek(index.getNodeState(), + new OrderedContentMirrorStoreStrategy.PredicateEquals(searchFor)); + + assertNotNull("we should have found an item", item); + assertEquals(searchFor, item.getName()); + } + + @Test + public void seekGreaterThanNotFound() { + OrderedContentMirrorStoreStrategy store = new OrderedContentMirrorStoreStrategy(); + NodeBuilder index = EmptyNodeState.EMPTY_NODE.builder(); + String n0 = KEYS[1]; + String n1 = KEYS[3]; + String n2 = KEYS[2]; + String n3 = KEYS[0]; + + // initialising the store + store.update(index, "/a/b", EMPTY_KEY_SET, newHashSet(n0)); + store.update(index, "/a/b", EMPTY_KEY_SET, newHashSet(n1)); + store.update(index, "/a/b", EMPTY_KEY_SET, newHashSet(n2)); + store.update(index, "/a/b", EMPTY_KEY_SET, newHashSet(n3)); + + String searchFor = n1; + + ChildNodeEntry item = OrderedContentMirrorStoreStrategy.seek(index.getNodeState(), + new OrderedContentMirrorStoreStrategy.PredicateGreaterThan(searchFor)); + + assertNull("no item should have been found", item); + } + + @Test + public void seekGreaterThan() { + OrderedContentMirrorStoreStrategy store = new OrderedContentMirrorStoreStrategy(); + NodeBuilder index = EmptyNodeState.EMPTY_NODE.builder(); + String n0 = KEYS[1]; + String n1 = KEYS[3]; + String n2 = KEYS[2]; + String n3 = KEYS[0]; + + // initialising the store + store.update(index, "/a/b", EMPTY_KEY_SET, newHashSet(n0)); + store.update(index, "/a/b", EMPTY_KEY_SET, newHashSet(n1)); + store.update(index, "/a/b", EMPTY_KEY_SET, newHashSet(n2)); + store.update(index, "/a/b", EMPTY_KEY_SET, newHashSet(n3)); + + String searchFor = n2; + + ChildNodeEntry item = OrderedContentMirrorStoreStrategy.seek(index.getNodeState(), + new OrderedContentMirrorStoreStrategy.PredicateGreaterThan(searchFor)); + + assertNotNull("we should have found an item", item); + assertEquals(n1, item.getName()); + } + + @Test + public void seekGreaterThanEqualsNotFound() { + OrderedContentMirrorStoreStrategy store = new OrderedContentMirrorStoreStrategy(); + NodeBuilder index = EmptyNodeState.EMPTY_NODE.builder(); + String n0 = KEYS[1]; + String n2 = KEYS[2]; + String n3 = KEYS[0]; + + // initialising the store + store.update(index, "/a/b", EMPTY_KEY_SET, newHashSet(n0)); + store.update(index, "/a/b", EMPTY_KEY_SET, newHashSet(n2)); + store.update(index, "/a/b", EMPTY_KEY_SET, newHashSet(n3)); + + String searchFor = KEYS[3]; + + ChildNodeEntry item = OrderedContentMirrorStoreStrategy.seek(index.getNodeState(), + new OrderedContentMirrorStoreStrategy.PredicateGreaterThan(searchFor, true)); + + assertNull("we should have not found an item", item); + } + + @Test + public void seekGreaterThanEquals() { + OrderedContentMirrorStoreStrategy store = new OrderedContentMirrorStoreStrategy(); + NodeBuilder index = EmptyNodeState.EMPTY_NODE.builder(); + String n0 = KEYS[1]; + String n2 = KEYS[2]; + String n3 = KEYS[0]; + + // initialising the store + store.update(index, "/a/b", EMPTY_KEY_SET, newHashSet(n0)); + store.update(index, "/a/b", EMPTY_KEY_SET, newHashSet(n2)); + store.update(index, "/a/b", EMPTY_KEY_SET, newHashSet(n3)); + + String searchFor = n2; + + ChildNodeEntry item = OrderedContentMirrorStoreStrategy.seek(index.getNodeState(), + new OrderedContentMirrorStoreStrategy.PredicateGreaterThan(searchFor, true)); + + assertNotNull("we should have found an item", item); + assertEquals(n2, item.getName()); + } + + @Test + public void seekLessThanNotFound() { + OrderedContentMirrorStoreStrategy store = new OrderedContentMirrorStoreStrategy( + OrderDirection.DESC); + NodeBuilder index = EmptyNodeState.EMPTY_NODE.builder(); + String n0 = KEYS[1]; + String n2 = KEYS[2]; + String n3 = KEYS[0]; + + // initialising the store + store.update(index, "/a/b", EMPTY_KEY_SET, newHashSet(n0)); + store.update(index, "/a/b", EMPTY_KEY_SET, newHashSet(n2)); + store.update(index, "/a/b", EMPTY_KEY_SET, newHashSet(n3)); + + String searchFor = n3; + + ChildNodeEntry item = OrderedContentMirrorStoreStrategy.seek(index.getNodeState(), + new OrderedContentMirrorStoreStrategy.PredicateLessThan(searchFor)); + + assertNull("we should have not found an item", item); + } + + @Test + public void seekLessThan() { + OrderedContentMirrorStoreStrategy store = new OrderedContentMirrorStoreStrategy( + OrderDirection.DESC); + NodeBuilder index = EmptyNodeState.EMPTY_NODE.builder(); + String n0 = KEYS[1]; + String n2 = KEYS[2]; + String n3 = KEYS[0]; + + // initialising the store + store.update(index, "/a/b", EMPTY_KEY_SET, newHashSet(n0)); + store.update(index, "/a/b", EMPTY_KEY_SET, newHashSet(n2)); + store.update(index, "/a/b", EMPTY_KEY_SET, newHashSet(n3)); + + String searchFor = n2; + + ChildNodeEntry item = OrderedContentMirrorStoreStrategy.seek(index.getNodeState(), + new OrderedContentMirrorStoreStrategy.PredicateLessThan(searchFor)); + + assertNotNull("we should have found an item", item); + assertEquals(n0, item.getName()); + } + + @Test + public void seekLessThanEqualNotFound() { + OrderedContentMirrorStoreStrategy store = new OrderedContentMirrorStoreStrategy( + OrderDirection.DESC); + NodeBuilder index = EmptyNodeState.EMPTY_NODE.builder(); + String n0 = KEYS[1]; + String n1 = KEYS[3]; + String n2 = KEYS[2]; + + // initialising the store + store.update(index, "/a/b", EMPTY_KEY_SET, newHashSet(n0)); + store.update(index, "/a/b", EMPTY_KEY_SET, newHashSet(n1)); + store.update(index, "/a/b", EMPTY_KEY_SET, newHashSet(n2)); + + String searchFor = KEYS[0]; + + ChildNodeEntry item = OrderedContentMirrorStoreStrategy.seek(index.getNodeState(), + new OrderedContentMirrorStoreStrategy.PredicateLessThan(searchFor, true)); + + assertNull("we should have not found an item", item); + } + + @Test + public void seekLessThanEqual() { + OrderedContentMirrorStoreStrategy store = new OrderedContentMirrorStoreStrategy( + OrderDirection.DESC); + NodeBuilder index = EmptyNodeState.EMPTY_NODE.builder(); + String n0 = KEYS[1]; + String n1 = KEYS[3]; + String n2 = KEYS[2]; + + // initialising the store + store.update(index, "/a/b", EMPTY_KEY_SET, newHashSet(n0)); + store.update(index, "/a/b", EMPTY_KEY_SET, newHashSet(n1)); + store.update(index, "/a/b", EMPTY_KEY_SET, newHashSet(n2)); + + String searchFor = n2; + + ChildNodeEntry item = OrderedContentMirrorStoreStrategy.seek(index.getNodeState(), + new OrderedContentMirrorStoreStrategy.PredicateLessThan(searchFor, true)); + + assertNotNull("we should have found an item", item); + assertEquals(n2, item.getName()); + } }