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);