Author: chetanm
Date: Wed Aug 30 04:03:55 2017
New Revision: 1806659

URL: http://svn.apache.org/viewvc?rev=1806659&view=rev
Log:
OAK-4906 - Lucene: Support relative property based query by transforming the 
path

Modified:
    
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexPlanner.java
    
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndex.java
    
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexPlannerTest.java
    
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndexTest.java

Modified: 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexPlanner.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexPlanner.java?rev=1806659&r1=1806658&r2=1806659&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexPlanner.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexPlanner.java
 Wed Aug 30 04:03:55 2017
@@ -19,7 +19,9 @@
 
 package org.apache.jackrabbit.oak.plugins.index.lucene;
 
+import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.LinkedList;
 import java.util.List;
@@ -29,7 +31,9 @@ import java.util.concurrent.atomic.Atomi
 
 import javax.annotation.CheckForNull;
 
+import com.google.common.collect.ArrayListMultimap;
 import com.google.common.collect.Iterables;
+import com.google.common.collect.Multimap;
 import org.apache.jackrabbit.JcrConstants;
 import org.apache.jackrabbit.oak.api.PropertyValue;
 import org.apache.jackrabbit.oak.api.Type;
@@ -180,6 +184,8 @@ class IndexPlanner {
         //Optimization - Go further only if any of the property is configured
         //for property index
         List<String> facetFields = new LinkedList<String>();
+        boolean ntBaseRule = NT_BASE.equals(indexingRule.getNodeTypeName());
+        Map<String, PropertyDefinition> relativePropDefns = new HashMap<>();
         if (indexingRule.propertyIndexEnabled) {
             for (PropertyRestriction pr : filter.getPropertyRestrictions()) {
                 String name = pr.propertyName;
@@ -196,6 +202,16 @@ class IndexPlanner {
                 }
 
                 PropertyDefinition pd = 
indexingRule.getConfig(pr.propertyName);
+
+                boolean relativeProps = false;
+                if (pd == null && ntBaseRule) {
+                    //Direct match not possible. Check for relative property 
definition
+                    //i.e. if no match found for jcr:content/@keyword then 
check if
+                    //property definition exists for 'keyword'
+                    pd = getSimpleProperty(indexingRule, pr.propertyName);
+                    relativeProps = pd != null;
+                }
+
                 if (pd != null && pd.propertyIndexEnabled()) {
                     if (pr.isNullRestriction() && !pd.nullCheckEnabled){
                         continue;
@@ -207,10 +223,15 @@ class IndexPlanner {
 
                     //A property definition with weight == 0 is only meant to 
be used
                     //with some other definitions
-                    if (pd.weight != 0) {
+                    if (pd.weight != 0 && !relativeProps) {
                         indexedProps.add(name);
                     }
-                    result.propDefns.put(name, pd);
+
+                    if (relativeProps) {
+                        relativePropDefns.put(name, pd);
+                    } else {
+                        result.propDefns.put(name, pd);
+                    }
                 }
             }
         }
@@ -224,6 +245,10 @@ class IndexPlanner {
             return null;
         }
 
+        if (indexedProps.isEmpty() && !relativePropDefns.isEmpty() && 
!canEvalAlFullText) {
+            indexedProps = planForRelativeProperties(relativePropDefns);
+        }
+
         //Fulltext expression can also be like 
jcr:contains(jcr:content/metadata/@format, 'image')
 
         List<OrderEntry> sortOrder = createSortOrder(indexingRule);
@@ -531,6 +556,68 @@ class IndexPlanner {
         return true;
     }
 
+    /**
+     * Computes the indexedProps which can be part of query by virtue of 
relativizing i.e.
+     * if query is on jcr:content/keyword then perform search on keyword and 
change parent
+     * path to jcr:content
+     * @param relativePropDefns property definitions for such relative 
properties. The key
+     *                          would be actual property name as in query i.e. 
jcr:content/keyword
+     *                          while property definition would be for 
'keyword'
+     * @return list of properties which are included in query issued to Lucene
+     */
+    private List<String> planForRelativeProperties(Map<String, 
PropertyDefinition> relativePropDefns) {
+        Multimap<String, Map.Entry<String, PropertyDefinition>> relpaths = 
ArrayListMultimap.create();
+        int maxSize = 0;
+        String maxCountedParent = null;
+
+        //Collect the relative properties grouped by parent path
+        //and track the parent having maximum properties
+        for (Map.Entry<String, PropertyDefinition> e : 
relativePropDefns.entrySet()) {
+            String relativePropertyPath = e.getKey();
+            String parent = getParentPath(relativePropertyPath);
+
+            relpaths.put(parent, e);
+            int count = relpaths.get(parent).size();
+            if (count > maxSize) {
+                maxSize = count;
+                maxCountedParent = parent;
+            }
+        }
+
+        //Set the parent path to one which is present in most prop. In case of 
tie any one
+        //such path would be picked
+        result.setParentPath(maxCountedParent);
+
+        //Now add only those properties to plan which have the maxCountedParent
+        List<String> indexedProps = new ArrayList<>(maxSize);
+        for (Map.Entry<String, PropertyDefinition> e : 
relpaths.get(maxCountedParent)) {
+            String relativePropertyPath = e.getKey();
+            result.propDefns.put(relativePropertyPath, e.getValue());
+            result.relPropMapping.put(relativePropertyPath, 
PathUtils.getName(relativePropertyPath));
+            if (e.getValue().weight != 0) {
+                indexedProps.add(relativePropertyPath);
+            }
+        }
+
+        return indexedProps;
+    }
+
+    @CheckForNull
+    private static PropertyDefinition getSimpleProperty(IndexingRule 
indexingRule, String relativePropertyName) {
+        String name = PathUtils.getName(relativePropertyName);
+        if (name.equals(relativePropertyName)){
+            //Not a relative property
+            return null;
+        }
+
+        //Properties using ../ or ./ notation not support. The relative 
property path
+        //must be fixed
+        if (relativePropertyName.startsWith("../") || 
relativePropertyName.startsWith("./")) {
+            return null;
+        }
+        return indexingRule.getConfig(name);
+    }
+
     private boolean canEvalPathRestrictions(IndexingRule rule) {
         //Opt out if one is looking for all children for '/' as its equivalent 
to
         //NO_RESTRICTION
@@ -716,8 +803,9 @@ class IndexPlanner {
         final String indexPath;
         final IndexDefinition indexDefinition;
         final IndexingRule indexingRule;
-        private List<PropertyDefinition> sortedProperties = newArrayList();
-        private Map<String, PropertyDefinition> propDefns = newHashMap();
+        private final List<PropertyDefinition> sortedProperties = 
newArrayList();
+        private final Map<String, PropertyDefinition> propDefns = newHashMap();
+        private final Map<String, String> relPropMapping = newHashMap();
 
         private boolean nonFullTextConstraints;
         private int parentDepth;
@@ -737,6 +825,14 @@ class IndexPlanner {
             return propDefns.get(pr.propertyName);
         }
 
+        /**
+         * Returns the property name to be used for query for given 
PropertyRestriction
+         * The name can be same as one for property restriction or it can be a 
mapped one
+         */
+        public String getPropertyName(PropertyRestriction pr) {
+            return relPropMapping.getOrDefault(pr.propertyName, 
pr.propertyName);
+        }
+
         public boolean hasProperty(String propName){
             return propDefns.containsKey(propName);
         }

Modified: 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndex.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndex.java?rev=1806659&r1=1806658&r2=1806659&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndex.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndex.java
 Wed Aug 30 04:03:55 2017
@@ -838,7 +838,7 @@ public class LucenePropertyIndex impleme
                 PropertyDefinition pd = planResult.getOrderedProperty(i);
                 PropertyRestriction orderRest = new PropertyRestriction();
                 orderRest.propertyName = oe.getPropertyName();
-                Query q = createQuery(orderRest, pd);
+                Query q = createQuery(oe.getPropertyName(), orderRest, pd);
                 if (q != null) {
                     qs.add(q);
                 }
@@ -1029,7 +1029,7 @@ public class LucenePropertyIndex impleme
                 continue;
             }
 
-            Query q = createQuery(pr, pd);
+            Query q = createQuery(planResult.getPropertyName(pr), pr, pd);
             if (q != null) {
                 qs.add(q);
             }
@@ -1089,7 +1089,7 @@ public class LucenePropertyIndex impleme
     }
 
     @CheckForNull
-    private static Query createQuery(PropertyRestriction pr,
+    private static Query createQuery(String propertyName, PropertyRestriction 
pr,
                                      PropertyDefinition defn) {
         int propType = determinePropertyType(defn, pr);
 
@@ -1103,7 +1103,6 @@ public class LucenePropertyIndex impleme
             return new TermQuery(new Term(FieldNames.NOT_NULL_PROPS, 
defn.name));
         }
 
-        String propertyName = pr.propertyName;
         switch (propType) {
             case PropertyType.DATE: {
                 Long first = pr.first != null ? 
FieldFactory.dateToLong(pr.first.getValue(Type.DATE)) : null;

Modified: 
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexPlannerTest.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexPlannerTest.java?rev=1806659&r1=1806658&r2=1806659&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexPlannerTest.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexPlannerTest.java
 Wed Aug 30 04:03:55 2017
@@ -858,6 +858,99 @@ public class IndexPlannerTest {
         assertNull(planner.getPlan());
     }
 
+    @Test
+    public void relativeProperty_Basics() throws Exception{
+        IndexDefinitionBuilder defnb = new IndexDefinitionBuilder();
+        defnb.indexRule("nt:base").property("foo").propertyIndex();
+        defnb.indexRule("nt:base").property("jcr:content/bar").propertyIndex();
+
+        IndexDefinition defn = new IndexDefinition(root, defnb.build(), 
"/foo");
+        IndexNode node = createIndexNode(defn);
+
+        FilterImpl filter = createFilter("nt:base");
+        filter.restrictProperty("jcr:content/foo", Operator.EQUAL, 
PropertyValues.newString("/bar"));
+        filter.restrictProperty("bar", Operator.EQUAL, 
PropertyValues.newString("/bar"));
+
+        IndexPlanner planner = new IndexPlanner(node, "/foo", filter, 
Collections.<OrderEntry>emptyList());
+        QueryIndex.IndexPlan plan = planner.getPlan();
+        assertNotNull(plan);
+
+        IndexPlanner.PlanResult pr = pr(plan);
+        assertTrue(pr.isPathTransformed());
+        assertEquals("/a/b", pr.transformPath("/a/b/jcr:content"));
+        assertNull(pr.transformPath("/a/b/c"));
+
+        assertTrue(pr.hasProperty("jcr:content/foo"));
+        assertFalse(pr.hasProperty("bar"));
+
+        Filter.PropertyRestriction r = new Filter.PropertyRestriction();
+        r.propertyName = "jcr:content/foo";
+        assertEquals("foo", pr.getPropertyName(r));
+    }
+
+    @Test
+    public void relativeProperty_Non_NtBase() throws Exception {
+        IndexDefinitionBuilder defnb = new IndexDefinitionBuilder();
+        defnb.indexRule("nt:unstructured").property("foo").propertyIndex();
+
+        IndexDefinition defn = new IndexDefinition(root, defnb.build(), 
"/foo");
+        IndexNode node = createIndexNode(defn);
+
+        FilterImpl filter = createFilter("nt:unstructured");
+        filter.restrictProperty("jcr:content/foo", Operator.EQUAL, 
PropertyValues.newString("/bar"));
+
+        IndexPlanner planner = new IndexPlanner(node, "/foo", filter, 
Collections.<OrderEntry>emptyList());
+        QueryIndex.IndexPlan plan = planner.getPlan();
+
+        //Should not return a plan for index rule other than nt:base
+        assertNull(plan);
+    }
+
+    @Test
+    public void relativeProperty_FullText() throws Exception{
+        IndexDefinitionBuilder defnb = new IndexDefinitionBuilder();
+        defnb.indexRule("nt:base").property("foo").propertyIndex();
+        defnb.aggregateRule("nt:base").include("*");
+
+        IndexDefinition defn = new IndexDefinition(root, defnb.build(), 
"/foo");
+        IndexNode node = createIndexNode(defn);
+
+        FilterImpl filter = createFilter("nt:base");
+        filter.restrictProperty("jcr:content/foo", Operator.EQUAL, 
PropertyValues.newString("/bar"));
+        FullTextExpression ft = FullTextParser.parse("jcr:content/*", 
"mountain OR valley");
+        filter.setFullTextConstraint(ft);
+
+        IndexPlanner planner = new IndexPlanner(node, "/foo", filter, 
Collections.<OrderEntry>emptyList());
+        QueryIndex.IndexPlan plan = planner.getPlan();
+        IndexPlanner.PlanResult pr = pr(plan);
+        assertFalse(pr.hasProperty("jcr:content/foo"));
+    }
+
+    @Test
+    public void relativeProperty_MultipleMatch() throws Exception{
+        IndexDefinitionBuilder defnb = new IndexDefinitionBuilder();
+        defnb.indexRule("nt:base").property("foo").propertyIndex();
+        defnb.indexRule("nt:base").property("bar").propertyIndex();
+        defnb.indexRule("nt:base").property("baz").propertyIndex();
+
+        IndexDefinition defn = new IndexDefinition(root, defnb.build(), 
"/foo");
+        IndexNode node = createIndexNode(defn);
+
+        FilterImpl filter = createFilter("nt:base");
+        filter.restrictProperty("jcr:content/foo", Operator.EQUAL, 
PropertyValues.newString("/bar"));
+        filter.restrictProperty("jcr:content/bar", Operator.EQUAL, 
PropertyValues.newString("/bar"));
+        filter.restrictProperty("metadata/baz", Operator.EQUAL, 
PropertyValues.newString("/bar"));
+
+        IndexPlanner planner = new IndexPlanner(node, "/foo", filter, 
Collections.<OrderEntry>emptyList());
+        QueryIndex.IndexPlan plan = planner.getPlan();
+        assertNotNull(plan);
+
+        IndexPlanner.PlanResult pr = pr(plan);
+        assertTrue(pr.hasProperty("jcr:content/foo"));
+        assertTrue(pr.hasProperty("jcr:content/bar"));
+        assertFalse(pr.hasProperty("metadata/baz"));
+    }
+
     private IndexPlanner createPlannerForFulltext(NodeState defn, 
FullTextExpression exp) throws IOException {
         IndexNode node = createIndexNode(new IndexDefinition(root, defn, 
"/foo"));
         FilterImpl filter = createFilter("nt:base");

Modified: 
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndexTest.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndexTest.java?rev=1806659&r1=1806658&r2=1806659&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndexTest.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndexTest.java
 Wed Aug 30 04:03:55 2017
@@ -2592,6 +2592,30 @@ public class LucenePropertyIndexTest ext
         assertNotNull(info.getIndexDefinitionDiff());
     }
 
+    @Test
+    public void relativeProperties() throws Exception{
+        IndexDefinitionBuilder idxb = new IndexDefinitionBuilder().noAsync();
+        idxb.indexRule("nt:base").property("foo").propertyIndex();
+
+        Tree idx = root.getTree("/").getChild("oak:index").addChild("test1");
+        idxb.build(idx);
+        root.commit();
+
+        Tree rootTree = root.getTree("/");
+        rootTree.addChild("a").addChild("jcr:content").setProperty("foo", 
"bar");
+        rootTree.addChild("b").addChild("jcr:content").setProperty("foo", 
"bar");
+        rootTree.addChild("c").setProperty("foo", "bar");
+        
rootTree.addChild("d").addChild("jcr:content").addChild("metadata").addChild("sub").setProperty("foo",
 "bar");
+
+        root.commit();
+
+        assertPlanAndQuery("select * from [nt:base] where [jcr:content/foo] = 
'bar'",
+                "lucene:test1(/oak:index/test1)", asList("/a", "/b"));
+
+        assertPlanAndQuery("select * from [nt:base] where 
[jcr:content/metadata/sub/foo] = 'bar'",
+                "lucene:test1(/oak:index/test1)", asList("/d"));
+    }
+
     private void assertPlanAndQuery(String query, String planExpectation, 
List<String> paths){
         assertThat(explain(query), containsString(planExpectation));
         assertQuery(query, paths);


Reply via email to