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 ju...@apache.org on 2014/07/17 15:51:14 UTC

svn commit: r1611351 - in /jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property: OrderedPropertyIndex.java OrderedPropertyIndexLookup.java

Author: jukka
Date: Thu Jul 17 13:51:14 2014
New Revision: 1611351

URL: http://svn.apache.org/r1611351
Log:
OAK-1965: Support for constraints like: foo = 'X' OR bar = 'Y'

Decouple OrderedPropertyIndex from PropertyIndex to allow independent evolution

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

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=1611351&r1=1611350&r2=1611351&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 Thu Jul 17 13:51:14 2014
@@ -30,6 +30,7 @@ import org.apache.jackrabbit.oak.spi.que
 import org.apache.jackrabbit.oak.spi.query.Cursors;
 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.query.QueryIndex.AdvancedQueryIndex;
 import org.apache.jackrabbit.oak.spi.state.NodeState;
 import org.slf4j.Logger;
@@ -40,15 +41,14 @@ import com.google.common.collect.Immutab
 /**
  * A property index that supports ordering keys.
  */
-public class OrderedPropertyIndex extends PropertyIndex implements AdvancedQueryIndex {
+public class OrderedPropertyIndex implements QueryIndex, AdvancedQueryIndex {
+
     private static final Logger LOG = LoggerFactory.getLogger(OrderedPropertyIndex.class);
-    
-    @Override
+
     public String getIndexName() {
         return TYPE;
     }
 
-    @Override
     OrderedPropertyIndexLookup getLookup(NodeState root) {
         return new OrderedPropertyIndexLookup(root);
     }
@@ -58,7 +58,6 @@ public class OrderedPropertyIndex extend
      * 
      * !!! for now we want to skip the use-case of NON range-queries !!!
      */
-    @Override
     public double getCost(Filter filter, NodeState root) {
         throw new UnsupportedOperationException("Not supported as implementing AdvancedQueryIndex");
     }
@@ -89,77 +88,72 @@ public class OrderedPropertyIndex extend
         }
         List<IndexPlan> plans = new ArrayList<IndexPlan>();
 
-        PropertyIndexLookup pil = getLookup(root);
-        if (pil instanceof OrderedPropertyIndexLookup) {
-            OrderedPropertyIndexLookup lookup = (OrderedPropertyIndexLookup) pil;
-            Collection<PropertyRestriction> restrictions = filter.getPropertyRestrictions();
-
-            // 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,
+        OrderedPropertyIndexLookup lookup = getLookup(root);
+        Collection<PropertyRestriction> restrictions = filter.getPropertyRestrictions();
+
+        // 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);
-                    }
+                    IndexPlan plan = b.build();
+                    LOG.debug("plan: {}", plan);
+                    plans.add(plan);
                 }
             }
+        }
 
-            // 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(
+        // 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);
-                    }
+                                    : 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);
                 }
             }
-        } else {
-            LOG.error("Without an OrderedPropertyIndexLookup you should not end here");
         }
 
         return plans;
@@ -242,42 +236,49 @@ public class OrderedPropertyIndex extend
         List<OrderEntry> sortOrder = plan.getSortOrder();
         Iterable<String> paths = null;
         Cursor cursor = null;
-        PropertyIndexLookup pil = getLookup(root);
-        if (pil instanceof OrderedPropertyIndexLookup) {
-            OrderedPropertyIndexLookup lookup = (OrderedPropertyIndexLookup) pil;
-            Collection<PropertyRestriction> prs = filter.getPropertyRestrictions();
-            int depth = 1;
-            for (PropertyRestriction pr : prs) {
-                String propertyName = PathUtils.getName(pr.propertyName);
-                depth = PathUtils.getDepth(pr.propertyName);
-                if (lookup.isIndexed(propertyName, "/", filter)) {
-                    paths = lookup.query(filter, propertyName, pr);
-                } 
+        OrderedPropertyIndexLookup lookup = getLookup(root);
+        Collection<PropertyRestriction> prs = filter.getPropertyRestrictions();
+        int depth = 1;
+        for (PropertyRestriction pr : prs) {
+            String propertyName = PathUtils.getName(pr.propertyName);
+            depth = PathUtils.getDepth(pr.propertyName);
+            if (lookup.isIndexed(propertyName, "/", filter)) {
+                paths = lookup.query(filter, propertyName, 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());
-                    }
-                }
-            }
-            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);
+        }
+        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());
+                }
             }
-            cursor = Cursors.newPathCursor(paths, filter.getQueryEngineSettings());
-            if (depth > 1) {
-                cursor = Cursors.newAncestorCursor(cursor, depth - 1, filter.getQueryEngineSettings());
-            }
-        } else {
-            // if for some reasons it's not an Ordered Lookup we delegate up the chain
-            cursor = super.query(filter, root);
+        }
+        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());
+        if (depth > 1) {
+            cursor = Cursors.newAncestorCursor(cursor, depth - 1, filter.getQueryEngineSettings());
         }
         return cursor;
     }
+
+    //--------------------------------------------------------< QueryIndex >--
+
+    @Override
+    public String getPlan(Filter filter, NodeState root) {
+        return getPlanDescription(getIndexPlanBuilder(filter).build(), root);
+    }
+
+    @Override
+    public Cursor query(Filter filter, NodeState root) {
+        return query(getIndexPlanBuilder(filter).build(), root);
+    }
+
 }

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=1611351&r1=1611350&r2=1611351&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 Thu Jul 17 13:51:14 2014
@@ -17,20 +17,35 @@
 
 package org.apache.jackrabbit.oak.plugins.index.property;
 
+import static com.google.common.collect.Iterables.contains;
+import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.DECLARING_NODE_TYPES;
+import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.INDEX_CONTENT_NODE_NAME;
+import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.INDEX_DEFINITIONS_NAME;
+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 java.util.Set;
+
+import javax.annotation.Nullable;
+
+import org.apache.jackrabbit.oak.api.PropertyState;
 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.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.state.ChildNodeEntry;
 import org.apache.jackrabbit.oak.spi.state.NodeState;
 
 /**
  *
  */
-public class OrderedPropertyIndexLookup extends PropertyIndexLookup {
-    private NodeState root;
-    
+public class OrderedPropertyIndexLookup {
+
     /**
      * the standard Ascending ordered index
      */
@@ -45,13 +60,75 @@ public class OrderedPropertyIndexLookup 
      * we're slightly more expensive than the standard PropertyIndex.
      */
     private static final double COST_OVERHEAD = 3;
-    
+
+    /**
+     * The maximum cost when the index can be used.
+     */
+    private static final int MAX_COST = 100;
+
+    private NodeState root;
+
     public OrderedPropertyIndexLookup(NodeState root) {
-        super(root);
         this.root = root;
     }
 
-    @Override
+    /**
+     * Get the node with the index definition for the given property, if there
+     * is an applicable index with data.
+     *
+     * @param propertyName the property name
+     * @param filter the filter (which contains information of all supertypes,
+     *            unless the filter matches all types)
+     * @return the node where the index definition (metadata) is stored (the
+     *         parent of ":index"), or null if no index definition or index data
+     *         node was found
+     */
+    @Nullable
+    NodeState getIndexNode(NodeState node, String propertyName, Filter filter) {
+        // keep a fallback to a matching index def that has *no* node type constraints
+        // (initially, there is no fallback)
+        NodeState fallback = null;
+
+        NodeState state = node.getChildNode(INDEX_DEFINITIONS_NAME);
+        for (ChildNodeEntry entry : state.getChildNodeEntries()) {
+            NodeState index = entry.getNodeState();
+            PropertyState type = index.getProperty(TYPE_PROPERTY_NAME);
+            if (type == null || type.isArray() || !getType().equals(type.getValue(Type.STRING))) {
+                continue;
+            }
+            if (contains(index.getNames(PROPERTY_NAMES), propertyName)) {
+                NodeState indexContent = index.getChildNode(INDEX_CONTENT_NODE_NAME);
+                if (!indexContent.exists()) {
+                    continue;
+                }
+                Set<String> supertypes = getSuperTypes(filter);
+                if (index.hasProperty(DECLARING_NODE_TYPES)) {
+                    if (supertypes != null) {
+                        for (String typeName : index.getNames(DECLARING_NODE_TYPES)) {
+                            if (supertypes.contains(typeName)) {
+                                // TODO: prefer the most specific type restriction
+                                return index;
+                            }
+                        }
+                    }
+                } else if (supertypes == null) {
+                    return index;
+                } else if (fallback == null) {
+                    // update the fallback
+                    fallback = index;
+                }
+            }
+        }
+        return fallback;
+    }
+
+    private static Set<String> getSuperTypes(Filter filter) {
+        if (filter != null && !filter.matchesAllTypes()) {
+            return filter.getSupertypes();
+        }
+        return null;
+    }
+
     IndexStoreStrategy getStrategy(NodeState indexMeta) {
         if (OrderDirection.isAscending(indexMeta)) {
             return STORE;
@@ -66,12 +143,40 @@ public class OrderedPropertyIndexLookup 
         return OrderDirection.isAscending(indexMeta);
     }
 
-    @Override
+    /**
+     * 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
+     */
     String getType() {
         return OrderedIndex.TYPE;
     }
-    
-    @Override
+
     public double getCost(Filter filter, String propertyName, PropertyValue value) {
         double cost = Double.POSITIVE_INFINITY;
         NodeState indexMeta = getIndexNode(root, propertyName, filter);
@@ -83,6 +188,14 @@ public class OrderedPropertyIndexLookup 
         return cost;
     }
 
+    public Iterable<String> query(Filter filter, String propertyName, PropertyValue value) {
+        NodeState indexMeta = getIndexNode(root, propertyName, filter);
+        if (indexMeta == null) {
+            throw new IllegalArgumentException("No index for " + propertyName);
+        }
+        return getStrategy(indexMeta).query(filter, propertyName, indexMeta, encode(value));
+    }
+
     /**
      * query the strategy for the provided constrains
      * 
@@ -102,7 +215,7 @@ public class OrderedPropertyIndexLookup 
 
     /**
      * return an estimated count to be used in IndexPlans.
-     * 
+     *
      * @param propertyName
      * @param value
      * @param filter