Author: davide Date: Tue Aug 5 10:39:47 2014 New Revision: 1615904 URL: http://svn.apache.org/r1615904 Log: OAK-1980 - Use index on non-root node
Applying OAK-1980.patch. Thanks Marcel. Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndex.java jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndexLookup.java jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/Cursors.java jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/QueryIndex.java jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedIndexCostTest.java jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndexQueryTest.java Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndex.java URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndex.java?rev=1615904&r1=1615903&r2=1615904&view=diff ============================================================================== --- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndex.java (original) +++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndex.java Tue Aug 5 10:39:47 2014 @@ -17,6 +17,7 @@ package org.apache.jackrabbit.oak.plugins.index.property; +import static org.apache.jackrabbit.oak.plugins.index.property.OrderedIndex.OrderDirection; import static org.apache.jackrabbit.oak.plugins.index.property.OrderedIndex.TYPE; import java.util.ArrayList; @@ -24,8 +25,8 @@ import java.util.Collection; import java.util.List; import org.apache.jackrabbit.oak.api.PropertyValue; -import org.apache.jackrabbit.oak.api.Type; import org.apache.jackrabbit.oak.commons.PathUtils; +import org.apache.jackrabbit.oak.plugins.index.property.strategy.OrderedContentMirrorStoreStrategy; import org.apache.jackrabbit.oak.spi.query.Cursor; import org.apache.jackrabbit.oak.spi.query.Cursors; import org.apache.jackrabbit.oak.spi.query.Filter; @@ -36,8 +37,6 @@ import org.apache.jackrabbit.oak.spi.sta import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.google.common.collect.ImmutableList; - /** * A property index that supports ordering keys. */ @@ -65,7 +64,7 @@ public class OrderedPropertyIndex implem /** * @return an builder with some initial common settings */ - private static IndexPlan.Builder getIndexPlanBuilder(final Filter filter) { + static IndexPlan.Builder getIndexPlanBuilder(final Filter filter) { IndexPlan.Builder b = new IndexPlan.Builder(); b.setCostPerExecution(1); // we're local. Low-cost // we're local but slightly more expensive than a standard PropertyIndex @@ -98,70 +97,20 @@ public class OrderedPropertyIndex implem OrderedPropertyIndexLookup lookup = getLookup(root); Collection<PropertyRestriction> restrictions = filter.getPropertyRestrictions(); + String filterPath = filter.getPath(); // first we process the sole orders as we could be in a situation where we don't have // a where condition indexed but we do for order. In that case we will return always the // whole index if (sortOrder != null) { for (OrderEntry oe : sortOrder) { - String propertyName = PathUtils.getName(oe.getPropertyName()); - if (lookup.isIndexed(propertyName, "/", filter)) { - IndexPlan.Builder b = getIndexPlanBuilder(filter); - b.setSortOrder(ImmutableList.of(new OrderEntry( - oe.getPropertyName(), - Type.UNDEFINED, - lookup.isAscending(root, propertyName, filter) ? OrderEntry.Order.ASCENDING - : OrderEntry.Order.DESCENDING))); - b.setEstimatedEntryCount(lookup.getEstimatedEntryCount(propertyName, null, - filter, null)); - IndexPlan plan = b.build(); - LOG.debug("plan: {}", plan); - plans.add(plan); - } + lookup.collectPlans(filter, filterPath, oe, plans); } } // then we add plans for each restriction that could apply to us for (Filter.PropertyRestriction pr : restrictions) { - String propertyName = PathUtils.getName(pr.propertyName); - if (lookup.isIndexed(propertyName, "/", filter)) { - PropertyValue value = null; - 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; - createPlan = true; - } else if (pr.first != null && !pr.first.equals(pr.last)) { - // '>' & '>=' use cases - value = pr.first; - createPlan = true; - } else if (pr.last != null && !pr.last.equals(pr.first)) { - // '<' & '<=' - value = pr.last; - createPlan = true; - } - if (createPlan) { - // we always return a sorted set - IndexPlan.Builder b = getIndexPlanBuilder(filter); - b.setSortOrder(ImmutableList.of(new OrderEntry( - propertyName, - Type.UNDEFINED, - lookup.isAscending(root, propertyName, filter) ? OrderEntry.Order.ASCENDING - : OrderEntry.Order.DESCENDING))); - long count = lookup.getEstimatedEntryCount(propertyName, value, filter, pr); - b.setEstimatedEntryCount(count); - LOG.debug("estimatedCount: {}", count); - - IndexPlan plan = b.build(); - LOG.debug("plan: {}", plan); - plans.add(plan); - } - } + lookup.collectPlans(filter, filterPath, pr, plans); } return plans; @@ -171,15 +120,12 @@ public class OrderedPropertyIndex implem public String getPlanDescription(IndexPlan plan, NodeState root) { LOG.debug("getPlanDescription({}, {})", plan, root); StringBuilder buff = new StringBuilder("ordered"); - OrderedPropertyIndexLookup lookup = getLookup(root); - Filter filter = plan.getFilter(); + NodeState definition = plan.getDefinition(); int depth = 1; boolean found = false; - for (PropertyRestriction pr : filter.getPropertyRestrictions()) { + if (plan.getPropertyRestriction() != null) { + PropertyRestriction pr = plan.getPropertyRestriction(); String propertyName = PathUtils.getName(pr.propertyName); - if (!lookup.isIndexed(propertyName, "/", filter)) { - continue; - } String operation = null; PropertyValue value = null; // TODO support pr.list @@ -193,13 +139,13 @@ public class OrderedPropertyIndex implem value = pr.first; } else if (pr.first != null && !pr.first.equals(pr.last)) { // '>' & '>=' use cases - if (lookup.isAscending(root, propertyName, filter)) { + if (OrderDirection.isAscending(definition)) { value = pr.first; operation = pr.firstIncluding ? ">=" : ">"; } } else if (pr.last != null && !pr.last.equals(pr.first)) { // '<' & '<=' - if (!lookup.isAscending(root, propertyName, filter)) { + if (!OrderDirection.isAscending(definition)) { value = pr.last; operation = pr.lastIncluding ? "<=" : "<"; } @@ -207,21 +153,14 @@ public class OrderedPropertyIndex implem if (operation != null) { buff.append(' ').append(propertyName).append(' '). append(operation).append(' ').append(value); - } else { - continue; + found = true; } - // stop with the first property that is indexed - found = true; - break; } List<OrderEntry> sortOrder = plan.getSortOrder(); if (!found && sortOrder != null && !sortOrder.isEmpty()) { // we could be here if we have a query where the ORDER BY makes us play it. for (OrderEntry oe : sortOrder) { String propertyName = PathUtils.getName(oe.getPropertyName()); - if (!lookup.isIndexed(propertyName, "/", null)) { - continue; - } depth = PathUtils.getDepth(oe.getPropertyName()); buff.append(" order by ").append(propertyName); // stop with the first property that is indexed @@ -243,34 +182,34 @@ public class OrderedPropertyIndex implem Filter filter = plan.getFilter(); List<OrderEntry> sortOrder = plan.getSortOrder(); Iterable<String> paths = null; - Cursor cursor = null; - OrderedPropertyIndexLookup lookup = getLookup(root); - Collection<PropertyRestriction> prs = filter.getPropertyRestrictions(); + OrderedContentMirrorStoreStrategy strategy + = OrderedPropertyIndexLookup.getStrategy(plan.getDefinition()); int depth = 1; - for (PropertyRestriction pr : prs) { + PropertyRestriction pr = plan.getPropertyRestriction(); + if (pr != null) { String propertyName = PathUtils.getName(pr.propertyName); - depth = PathUtils.getDepth(pr.propertyName); - if (lookup.isIndexed(propertyName, "/", filter)) { - paths = lookup.query(filter, propertyName, pr); - } + depth = PathUtils.getDepth(propertyName); + paths = strategy.query(plan.getFilter(), propertyName, + plan.getDefinition(), pr); } if (paths == null && sortOrder != null && !sortOrder.isEmpty()) { // we could be here if we have a query where the ORDER BY makes us play it. for (OrderEntry oe : sortOrder) { String propertyName = PathUtils.getName(oe.getPropertyName()); depth = PathUtils.getDepth(oe.getPropertyName()); - if (lookup.isIndexed(propertyName, "/", null)) { - paths = lookup.query(filter, propertyName, new PropertyRestriction()); - } + paths = strategy.query(plan.getFilter(), propertyName, + plan.getDefinition(), new PropertyRestriction()); } } + if (paths == null) { // if still here then something went wrong. throw new IllegalStateException( "OrderedPropertyIndex index is used even when no index is available for filter " + filter); } - cursor = Cursors.newPathCursor(paths, filter.getQueryEngineSettings()); + Cursor cursor = Cursors.newPathCursor(paths, filter.getQueryEngineSettings()); + cursor = Cursors.newPrefixCursor(cursor, plan.getPathPrefix()); if (depth > 1) { cursor = Cursors.newAncestorCursor(cursor, depth - 1, filter.getQueryEngineSettings()); } Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndexLookup.java URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndexLookup.java?rev=1615904&r1=1615903&r2=1615904&view=diff ============================================================================== --- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndexLookup.java (original) +++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndexLookup.java Tue Aug 5 10:39:47 2014 @@ -24,7 +24,9 @@ import static org.apache.jackrabbit.oak. import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.PROPERTY_NAMES; import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.TYPE_PROPERTY_NAME; import static org.apache.jackrabbit.oak.plugins.index.property.PropertyIndex.encode; +import static org.apache.jackrabbit.oak.spi.query.QueryIndex.OrderEntry.Order; +import java.util.List; import java.util.Set; import javax.annotation.Nullable; @@ -34,27 +36,33 @@ import org.apache.jackrabbit.oak.api.Pro import org.apache.jackrabbit.oak.api.Type; import org.apache.jackrabbit.oak.commons.PathUtils; import org.apache.jackrabbit.oak.plugins.index.property.OrderedIndex.OrderDirection; -import org.apache.jackrabbit.oak.plugins.index.property.strategy.IndexStoreStrategy; import org.apache.jackrabbit.oak.plugins.index.property.strategy.OrderedContentMirrorStoreStrategy; import org.apache.jackrabbit.oak.spi.query.Filter; import org.apache.jackrabbit.oak.spi.query.Filter.PropertyRestriction; +import org.apache.jackrabbit.oak.spi.query.QueryIndex; import org.apache.jackrabbit.oak.spi.state.ChildNodeEntry; import org.apache.jackrabbit.oak.spi.state.NodeState; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.collect.ImmutableList; /** * */ public class OrderedPropertyIndexLookup { + private static final Logger LOG = LoggerFactory.getLogger(OrderedPropertyIndexLookup.class); + /** * the standard Ascending ordered index */ - private static final IndexStoreStrategy STORE = new OrderedContentMirrorStoreStrategy(); + private static final OrderedContentMirrorStoreStrategy STORE = new OrderedContentMirrorStoreStrategy(); /** * the descending ordered index */ - private static final IndexStoreStrategy REVERSED_STORE = new OrderedContentMirrorStoreStrategy(OrderDirection.DESC); + private static final OrderedContentMirrorStoreStrategy REVERSED_STORE = new OrderedContentMirrorStoreStrategy(OrderDirection.DESC); /** * we're slightly more expensive than the standard PropertyIndex. @@ -68,8 +76,19 @@ public class OrderedPropertyIndexLookup private NodeState root; + private String name; + + private OrderedPropertyIndexLookup parent; + public OrderedPropertyIndexLookup(NodeState root) { + this(root, "", null); + } + + public OrderedPropertyIndexLookup(NodeState root, String name, + OrderedPropertyIndexLookup parent) { this.root = root; + this.name = name; + this.parent = parent; } /** @@ -129,7 +148,7 @@ public class OrderedPropertyIndexLookup return null; } - IndexStoreStrategy getStrategy(NodeState indexMeta) { + static OrderedContentMirrorStoreStrategy getStrategy(NodeState indexMeta) { if (OrderDirection.isAscending(indexMeta)) { return STORE; } else { @@ -144,31 +163,6 @@ public class OrderedPropertyIndexLookup } /** - * Checks whether the named property is indexed somewhere along the given - * path. Lookup starts at the current path (at the root of this object) and - * traverses down the path. - * - * @param propertyName property name - * @param path lookup path - * @param filter for the node type restriction (null if no node type restriction) - * @return true if the property is indexed - */ - public boolean isIndexed(String propertyName, String path, Filter filter) { - if (PathUtils.denotesRoot(path)) { - return getIndexNode(root, propertyName, filter) != null; - } - - NodeState node = root; - for (String s : PathUtils.elements(path)) { - if (getIndexNode(node, propertyName, filter) != null) { - return true; - } - node = node.getChildNode(s); - } - return false; - } - - /** * retrieve the type of the index * * @return the type @@ -209,23 +203,131 @@ public class OrderedPropertyIndexLookup if (indexMeta == null) { throw new IllegalArgumentException("No index for " + propertyName); } - return ((OrderedContentMirrorStoreStrategy) getStrategy(indexMeta)).query(filter, - propertyName, indexMeta, pr); + return getStrategy(indexMeta).query(filter, propertyName, indexMeta, pr); } /** - * return an estimated count to be used in IndexPlans. + * Collect plans for ordered indexes along the given path and order entry. * - * @param propertyName - * @param value - * @param filter - * @param pr - * @return the estimated count + * @param filter a filter description. + * @param path a relative path from this lookup to the filter path. + * @param oe an order entry. + * @param plans collected plans are added to this list. */ - public long getEstimatedEntryCount(String propertyName, PropertyValue value, Filter filter, - PropertyRestriction pr) { - NodeState indexMeta = getIndexNode(root, propertyName, filter); - OrderedContentMirrorStoreStrategy strategy = (OrderedContentMirrorStoreStrategy) getStrategy(indexMeta); - return strategy.count(indexMeta, pr, MAX_COST); + void collectPlans(Filter filter, + String path, + QueryIndex.OrderEntry oe, + List<QueryIndex.IndexPlan> plans) { + String propertyName = PathUtils.getName(oe.getPropertyName()); + NodeState definition = getIndexNode(root, propertyName, filter); + if (definition != null) { + Order order = OrderDirection.isAscending(definition) + ? Order.ASCENDING : Order.DESCENDING; + long entryCount = getStrategy(definition).count(definition, (PropertyRestriction) null, MAX_COST); + QueryIndex.IndexPlan.Builder b = OrderedPropertyIndex.getIndexPlanBuilder(filter); + b.setSortOrder(ImmutableList.of(new QueryIndex.OrderEntry(oe.getPropertyName(), Type.UNDEFINED, order))); + b.setEstimatedEntryCount(entryCount); + b.setDefinition(definition); + b.setPathPrefix(getPath()); + QueryIndex.IndexPlan plan = b.build(); + LOG.debug("plan: {}", plan); + plans.add(plan); + } + // walk down path + String remainder = ""; + OrderedPropertyIndexLookup lookup = null; + for (String element : PathUtils.elements(path)) { + if (lookup == null) { + lookup = new OrderedPropertyIndexLookup( + root.getChildNode(element), element, this); + } else { + remainder = PathUtils.concat(remainder, element); + } + } + if (lookup != null) { + lookup.collectPlans(filter, remainder, oe, plans); + } + } + + /** + * Collect plans for ordered indexes along the given path and property + * restriction. + * + * @param filter a filter description. + * @param path a relative path from this lookup to the filter path. + * @param pr a property restriction. + * @param plans collected plans are added to this list. + */ + void collectPlans(Filter filter, + String path, + PropertyRestriction pr, + List<QueryIndex.IndexPlan> plans) { + String propertyName = PathUtils.getName(pr.propertyName); + NodeState definition = getIndexNode(root, propertyName, filter); + if (definition != null) { + PropertyValue value = null; + 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; + createPlan = true; + } else if (pr.first != null && !pr.first.equals(pr.last)) { + // '>' & '>=' use cases + value = pr.first; + createPlan = true; + } else if (pr.last != null && !pr.last.equals(pr.first)) { + // '<' & '<=' + value = pr.last; + createPlan = true; + } + if (createPlan) { + // we always return a sorted set + Order order = OrderDirection.isAscending(definition) + ? Order.ASCENDING : Order.DESCENDING; + QueryIndex.IndexPlan.Builder b = OrderedPropertyIndex.getIndexPlanBuilder(filter); + b.setDefinition(definition); + b.setSortOrder(ImmutableList.of(new QueryIndex.OrderEntry( + propertyName, Type.UNDEFINED, order))); + long count = getStrategy(definition).count(definition, pr, MAX_COST); + b.setEstimatedEntryCount(count); + b.setPropertyRestriction(pr); + b.setPathPrefix(getPath()); + + QueryIndex.IndexPlan plan = b.build(); + LOG.debug("plan: {}", plan); + plans.add(plan); + } + } + // walk down path + String remainder = ""; + OrderedPropertyIndexLookup lookup = null; + for (String element : PathUtils.elements(path)) { + if (lookup == null) { + lookup = new OrderedPropertyIndexLookup( + root.getChildNode(element), element, this); + } else { + remainder = PathUtils.concat(remainder, element); + } + } + if (lookup != null) { + lookup.collectPlans(filter, remainder, pr, plans); + } + } + + private String getPath() { + return buildPath(new StringBuilder()).toString(); + } + + private StringBuilder buildPath(StringBuilder sb) { + if (parent != null) { + parent.buildPath(sb); + sb.append("/").append(name); + } + return sb; } } \ No newline at end of file Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/Cursors.java URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/Cursors.java?rev=1615904&r1=1615903&r2=1615904&view=diff ============================================================================== --- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/Cursors.java (original) +++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/Cursors.java Tue Aug 5 10:39:47 2014 @@ -24,6 +24,7 @@ import java.util.List; import javax.annotation.Nullable; +import org.apache.jackrabbit.oak.api.PropertyValue; import org.apache.jackrabbit.oak.commons.PathUtils; import org.apache.jackrabbit.oak.plugins.memory.MemoryChildNodeEntry; import org.apache.jackrabbit.oak.query.FilterIterators; @@ -71,6 +72,23 @@ public class Cursors { public static Cursor newPathCursor(Iterable<String> paths, QueryEngineSettings settings) { return new PathCursor(paths.iterator(), true, settings); } + + /** + * Creates a cursor which wraps another cursor and adds a path prefix to + * each of row of the wrapped cursor. This method will return the passed + * cursor as is if {@code path} is the empty string or the root path ("/"). + * + * @param c the cursor to wrap. + * @param path the path prefix. + * @return the cursor. + */ + public static Cursor newPrefixCursor(Cursor c, String path) { + if (path.isEmpty() || PathUtils.denotesRoot(path)) { + // no need to wrap + return c; + } + return new PrefixCursor(c, path); + } /** * Creates a {@link Cursor} over paths, and make the result distinct. @@ -202,6 +220,48 @@ public class Cursors { } /** + * A cursor which wraps another cursor and adds a path prefix to each of + * row of the wrapped cursor. + */ + private static final class PrefixCursor extends AbstractCursor { + + private final Cursor c; + private final String path; + + PrefixCursor(Cursor c, String prefix) { + this.c = c; + this.path = prefix; + } + + @Override + public IndexRow next() { + final IndexRow r = c.next(); + return new IndexRow() { + + @Override + public String getPath() { + String sub = r.getPath(); + if (PathUtils.isAbsolute(sub)) { + return path + sub; + } else { + return PathUtils.concat(path, r.getPath()); + } + } + + @Override + public PropertyValue getValue(String columnName) { + return r.getValue(columnName); + } + }; + } + + @Override + public boolean hasNext() { + return c.hasNext(); + } + } + + /** * A cursor that reads all nodes in a given subtree. */ private static class TraversingCursor extends AbstractCursor { Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/QueryIndex.java URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/QueryIndex.java?rev=1615904&r1=1615903&r2=1615904&view=diff ============================================================================== --- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/QueryIndex.java (original) +++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/QueryIndex.java Tue Aug 5 10:39:47 2014 @@ -27,6 +27,8 @@ import org.apache.jackrabbit.oak.api.Typ import org.apache.jackrabbit.oak.plugins.index.aggregate.NodeAggregator; import org.apache.jackrabbit.oak.spi.state.NodeState; +import static org.apache.jackrabbit.oak.spi.query.Filter.PropertyRestriction; + /** * Represents an index. The index should use the data in the filter if possible * to speed up reading. @@ -153,7 +155,7 @@ public interface QueryIndex { * Get the query plan description (for logging purposes). * * @param plan the index plan - * @param rootState root state of the current repository snapshot + * @param root root state of the current repository snapshot * @return the query plan description */ String getPlanDescription(IndexPlan plan, NodeState root); @@ -243,6 +245,29 @@ public interface QueryIndex { * @return the sort order */ List<OrderEntry> getSortOrder(); + + /** + * The node state with the index definition. + * + * @return the node state with the index definition. + */ + NodeState getDefinition(); + + /** + * The path prefix for this index plan. + * @return + */ + String getPathPrefix(); + + /** + * The property restriction for this index plan or <code>null</code> if + * this index plan isn't base on a property restriction. E.g. a plan + * based on an order by clause in the query. + * + * @return the restriction this plan is based on or <code>null</code>. + */ + @CheckForNull + PropertyRestriction getPropertyRestriction(); /** * A builder for index plans. @@ -257,6 +282,9 @@ public interface QueryIndex { protected boolean isFulltextIndex; protected boolean includesNodeData; protected List<OrderEntry> sortOrder; + protected NodeState definition; + protected PropertyRestriction propRestriction; + protected String pathPrefix = "/"; public Builder setCostPerExecution(double costPerExecution) { this.costPerExecution = costPerExecution; @@ -297,7 +325,22 @@ public interface QueryIndex { this.sortOrder = sortOrder; return this; } - + + public Builder setDefinition(NodeState definition) { + this.definition = definition; + return this; + } + + public Builder setPropertyRestriction(PropertyRestriction restriction) { + this.propRestriction = restriction; + return this; + } + + public Builder setPathPrefix(String pathPrefix) { + this.pathPrefix = pathPrefix; + return this; + } + public IndexPlan build() { return new IndexPlan() { @@ -319,7 +362,14 @@ public interface QueryIndex { private final List<OrderEntry> sortOrder = Builder.this.sortOrder == null ? null : new ArrayList<OrderEntry>( - Builder.this.sortOrder); + Builder.this.sortOrder); + private final NodeState definition = + Builder.this.definition; + private final PropertyRestriction propRestriction = + Builder.this.propRestriction; + private final String pathPrefix = + Builder.this.pathPrefix; + @Override public String toString() { return String.format( @@ -330,7 +380,10 @@ public interface QueryIndex { + " isDelayed : %s," + " isFulltextIndex : %s," + " includesNodeData : %s," - + " sortOrder : %s }", + + " sortOrder : %s," + + " definition : %s," + + " propertyRestriction : %s," + + " pathPrefix : %s }", costPerExecution, costPerEntry, estimatedEntryCount, @@ -338,7 +391,10 @@ public interface QueryIndex { isDelayed, isFulltextIndex, includesNodeData, - sortOrder + sortOrder, + definition, + propRestriction, + pathPrefix ); } @@ -386,10 +442,24 @@ public interface QueryIndex { public List<OrderEntry> getSortOrder() { return sortOrder; } - + + @Override + public NodeState getDefinition() { + return definition; + } + + @Override + public PropertyRestriction getPropertyRestriction() { + return propRestriction; + } + + @Override + public String getPathPrefix() { + return pathPrefix; + } }; } - + } } Modified: jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedIndexCostTest.java URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedIndexCostTest.java?rev=1615904&r1=1615903&r2=1615904&view=diff ============================================================================== --- jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedIndexCostTest.java (original) +++ jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedIndexCostTest.java Tue Aug 5 10:39:47 2014 @@ -44,29 +44,6 @@ import com.google.common.collect.Immutab * tests the Cost-related part of the provider/strategy */ public class OrderedIndexCostTest extends BasicOrderedPropertyIndexQueryTest { - /** - * convenience class that return an always indexed strategy - */ - private static class AlwaysIndexedOrderedPropertyIndex extends OrderedPropertyIndex { - @Override - AlwaysIndexedLookup getLookup(NodeState root) { - return new AlwaysIndexedLookup(root); - } - - /** - * convenience class that always return true at the isIndexed test - */ - private static class AlwaysIndexedLookup extends OrderedPropertyIndexLookup { - public AlwaysIndexedLookup(NodeState root) { - super(root); - } - - @Override - public boolean isIndexed(String propertyName, String path, Filter filter) { - return true; - } - } - } @Override protected void createTestIndexNode() throws Exception { @@ -147,7 +124,7 @@ public class OrderedIndexCostTest extend */ @Test @Ignore("As of OAK-622 this should no longer be used. Removing later.") public void costGreaterThanAscendingDirection() throws Exception { - OrderedPropertyIndex index = new AlwaysIndexedOrderedPropertyIndex(); + OrderedPropertyIndex index = new OrderedPropertyIndex(); NodeBuilder builder = InitialContent.INITIAL_CONTENT.builder(); defineAscendingIndex(builder); NodeState root = builder.getNodeState(); @@ -171,7 +148,7 @@ public class OrderedIndexCostTest extend */ @Test @Ignore("As of OAK-622 this should no longer be used. Removing later.") public void costGreaterThanEqualAscendingDirection() throws IllegalArgumentException, RepositoryException { - OrderedPropertyIndex index = new AlwaysIndexedOrderedPropertyIndex(); + OrderedPropertyIndex index = new OrderedPropertyIndex(); NodeBuilder builder = InitialContent.INITIAL_CONTENT.builder(); defineAscendingIndex(builder); NodeState root = builder.getNodeState(); @@ -196,7 +173,7 @@ public class OrderedIndexCostTest extend */ @Test @Ignore("As of OAK-622 this should no longer be used. Removing later.") public void costLessThanAscendingDirection() throws IllegalArgumentException, RepositoryException { - OrderedPropertyIndex index = new AlwaysIndexedOrderedPropertyIndex(); + OrderedPropertyIndex index = new OrderedPropertyIndex(); NodeBuilder builder = InitialContent.INITIAL_CONTENT.builder(); defineAscendingIndex(builder); NodeState root = builder.getNodeState(); @@ -215,7 +192,7 @@ public class OrderedIndexCostTest extend @Test @Ignore("As of OAK-622 this should no longer be used. Removing later.") public void costLessThanEqualsAscendingDirection() throws IllegalArgumentException, RepositoryException { - OrderedPropertyIndex index = new AlwaysIndexedOrderedPropertyIndex(); + OrderedPropertyIndex index = new OrderedPropertyIndex(); NodeBuilder builder = InitialContent.INITIAL_CONTENT.builder(); defineAscendingIndex(builder); NodeState root = builder.getNodeState(); @@ -235,7 +212,7 @@ public class OrderedIndexCostTest extend @Test @Ignore("As of OAK-622 this should no longer be used. Removing later.") public void costGreaterThanDescendingDirection() throws IllegalArgumentException, RepositoryException { - OrderedPropertyIndex index = new AlwaysIndexedOrderedPropertyIndex(); + OrderedPropertyIndex index = new OrderedPropertyIndex(); NodeBuilder builder = InitialContent.INITIAL_CONTENT.builder(); defineDescendingIndex(builder); NodeState root = builder.getNodeState(); @@ -255,7 +232,7 @@ public class OrderedIndexCostTest extend @Test @Ignore("As of OAK-622 this should no longer be used. Removing later.") public void costGreaterEqualThanDescendingDirection() throws IllegalArgumentException, RepositoryException { - OrderedPropertyIndex index = new AlwaysIndexedOrderedPropertyIndex(); + OrderedPropertyIndex index = new OrderedPropertyIndex(); NodeBuilder builder = InitialContent.INITIAL_CONTENT.builder(); defineDescendingIndex(builder); NodeState root = builder.getNodeState(); @@ -276,7 +253,7 @@ public class OrderedIndexCostTest extend @Test @Ignore("As of OAK-622 this should no longer be used. Removing later.") public void costLessThanDescendingDirection() throws IllegalArgumentException, RepositoryException { - OrderedPropertyIndex index = new AlwaysIndexedOrderedPropertyIndex(); + OrderedPropertyIndex index = new OrderedPropertyIndex(); NodeBuilder builder = InitialContent.INITIAL_CONTENT.builder(); defineDescendingIndex(builder); NodeState root = builder.getNodeState(); @@ -296,7 +273,7 @@ public class OrderedIndexCostTest extend @Test @Ignore("As of OAK-622 this should no longer be used. Removing later.") public void costLessThanEqualDescendingDirection() throws IllegalArgumentException, RepositoryException { - OrderedPropertyIndex index = new AlwaysIndexedOrderedPropertyIndex(); + OrderedPropertyIndex index = new OrderedPropertyIndex(); NodeBuilder builder = InitialContent.INITIAL_CONTENT.builder(); defineDescendingIndex(builder); NodeState root = builder.getNodeState(); @@ -317,7 +294,7 @@ public class OrderedIndexCostTest extend @Test @Ignore("As of OAK-622 this should no longer be used. Removing later.") public void costBetweenDescendingDirection() throws IllegalArgumentException, RepositoryException { - OrderedPropertyIndex index = new AlwaysIndexedOrderedPropertyIndex(); + OrderedPropertyIndex index = new OrderedPropertyIndex(); NodeBuilder builder = InitialContent.INITIAL_CONTENT.builder(); defineDescendingIndex(builder); NodeState root = builder.getNodeState(); @@ -340,7 +317,7 @@ public class OrderedIndexCostTest extend @Test @Ignore("As of OAK-622 this should no longer be used. Removing later.") public void costBetweenAscendingDirection() throws IllegalArgumentException, RepositoryException { - OrderedPropertyIndex index = new AlwaysIndexedOrderedPropertyIndex(); + OrderedPropertyIndex index = new OrderedPropertyIndex(); NodeBuilder builder = InitialContent.INITIAL_CONTENT.builder(); defineAscendingIndex(builder); NodeState root = builder.getNodeState(); Modified: jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndexQueryTest.java URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndexQueryTest.java?rev=1615904&r1=1615903&r2=1615904&view=diff ============================================================================== --- jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndexQueryTest.java (original) +++ jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndexQueryTest.java Tue Aug 5 10:39:47 2014 @@ -22,6 +22,7 @@ import static junit.framework.Assert.ass import static org.apache.jackrabbit.JcrConstants.JCR_PRIMARYTYPE; import static org.apache.jackrabbit.JcrConstants.JCR_SYSTEM; import static org.apache.jackrabbit.JcrConstants.NT_UNSTRUCTURED; +import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.INDEX_DEFINITIONS_NAME; import static org.apache.jackrabbit.oak.plugins.nodetype.NodeTypeConstants.JCR_NODE_TYPES; import static org.junit.Assert.assertNotNull; @@ -47,7 +48,6 @@ import org.apache.jackrabbit.oak.api.Res import org.apache.jackrabbit.oak.api.Tree; import org.apache.jackrabbit.oak.api.Type; import org.apache.jackrabbit.oak.commons.PathUtils; -import org.apache.jackrabbit.oak.plugins.index.IndexConstants; import org.apache.jackrabbit.oak.plugins.index.IndexUpdateProvider; import org.apache.jackrabbit.oak.plugins.index.IndexUtils; import org.apache.jackrabbit.oak.plugins.index.property.OrderedIndex.OrderDirection; @@ -78,9 +78,13 @@ public class OrderedPropertyIndexQueryTe @Override protected void createTestIndexNode() throws Exception { - Tree index = root.getTree("/"); - IndexUtils.createIndexDefinition(new NodeUtil(index.getChild(IndexConstants.INDEX_DEFINITIONS_NAME)), - TEST_INDEX_NAME, false, new String[] { ORDERED_PROPERTY }, null, OrderedIndex.TYPE); + createTestIndexNode("/"); + } + + protected void createTestIndexNode(String path) throws Exception { + Tree index = root.getTree(path); + IndexUtils.createIndexDefinition(new NodeUtil(index.getChild(INDEX_DEFINITIONS_NAME)), + TEST_INDEX_NAME, false, new String[] { ORDERED_PROPERTY }, null, OrderedIndex.TYPE); root.commit(); } @@ -446,7 +450,7 @@ public class OrderedPropertyIndexQueryTe NodeBuilder root = EmptyNodeState.EMPTY_NODE.builder(); - IndexUtils.createIndexDefinition(root.child(IndexConstants.INDEX_DEFINITIONS_NAME), + IndexUtils.createIndexDefinition(root.child(INDEX_DEFINITIONS_NAME), TEST_INDEX_NAME, false, ImmutableList.of(ORDERED_PROPERTY), null, OrderedIndex.TYPE, ImmutableMap.<String, String> of()); @@ -523,7 +527,7 @@ public class OrderedPropertyIndexQueryTe RepositoryException, CommitFailedException { NodeBuilder root = EmptyNodeState.EMPTY_NODE.builder(); - IndexUtils.createIndexDefinition(root.child(IndexConstants.INDEX_DEFINITIONS_NAME), + IndexUtils.createIndexDefinition(root.child(INDEX_DEFINITIONS_NAME), TEST_INDEX_NAME, false, ImmutableList.of(ORDERED_PROPERTY), null, OrderedIndex.TYPE, ImmutableMap.<String, String> of()); @@ -564,7 +568,7 @@ public class OrderedPropertyIndexQueryTe public void planOrderAndWhereMixed() throws IllegalArgumentException, RepositoryException, CommitFailedException { NodeBuilder root = EmptyNodeState.EMPTY_NODE.builder(); - IndexUtils.createIndexDefinition(root.child(IndexConstants.INDEX_DEFINITIONS_NAME), + IndexUtils.createIndexDefinition(root.child(INDEX_DEFINITIONS_NAME), TEST_INDEX_NAME, false, ImmutableList.of(ORDERED_PROPERTY), null, OrderedIndex.TYPE, ImmutableMap.<String, String> of()); @@ -867,9 +871,57 @@ public class OrderedPropertyIndexQueryTe .iterator(); assertRightOrder(Lists.newArrayList(filtered), results); - assertFalse("We should have looped throuhg all the results", results.hasNext()); + assertFalse("We should have looped through all the results", results.hasNext()); setTraversalEnabled(true); } + + @Test + public void indexDefinitionBelowRoot() throws Exception { + setTraversalEnabled(false); + + // remove the default test index definition + root.getTree("/" + INDEX_DEFINITIONS_NAME + "/" + TEST_INDEX_NAME).remove(); + root.getTree("/").addChild("test").addChild(INDEX_DEFINITIONS_NAME); + root.commit(); + createTestIndexNode("/test"); + + Tree test = root.getTree("/test"); + List<ValuePathTuple> nodes = addChildNodes(generateOrderedValues(NUMBER_OF_NODES), test, + OrderDirection.ASC, Type.STRING); + root.commit(); + + // querying + Iterator<? extends ResultRow> results; + results = executeQuery(String.format("SELECT * FROM [%s] as s WHERE s.foo IS NOT NULL and ISDESCENDANTNODE(s, '/test')", NT_UNSTRUCTURED), SQL2, null) + .getRows().iterator(); + assertRightOrder(nodes, results); + + setTraversalEnabled(true); + } + + @Test + public void indexDefinitionBelowRootOrderBy() throws Exception { + setTraversalEnabled(false); + + // remove the default test index definition + root.getTree("/" + INDEX_DEFINITIONS_NAME + "/" + TEST_INDEX_NAME).remove(); + root.getTree("/").addChild("test").addChild(INDEX_DEFINITIONS_NAME); + root.commit(); + createTestIndexNode("/test"); + + Tree test = root.getTree("/test"); + List<ValuePathTuple> nodes = addChildNodes(generateOrderedValues(NUMBER_OF_NODES), test, + OrderDirection.ASC, Type.STRING); + root.commit(); + + // querying + Iterator<? extends ResultRow> results; + results = executeQuery(String.format("SELECT * FROM [%s] as s WHERE ISDESCENDANTNODE(s, '/test') ORDER BY s.foo", NT_UNSTRUCTURED), SQL2, null) + .getRows().iterator(); + assertRightOrder(nodes, results); + + setTraversalEnabled(true); + } } \ No newline at end of file