You are viewing a plain text version of this content. The canonical link for it is here.
Posted to oak-commits@jackrabbit.apache.org by da...@apache.org on 2014/08/05 12:39:47 UTC

svn commit: r1615904 - in /jackrabbit/oak/trunk/oak-core/src: main/java/org/apache/jackrabbit/oak/plugins/index/property/ main/java/org/apache/jackrabbit/oak/spi/query/ test/java/org/apache/jackrabbit/oak/plugins/index/property/

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