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 ca...@apache.org on 2017/11/15 03:33:46 UTC

svn commit: r1815285 - in /jackrabbit/oak/trunk/oak-lucene/src: main/java/org/apache/jackrabbit/oak/plugins/index/lucene/ main/java/org/apache/jackrabbit/oak/plugins/index/lucene/util/ test/java/org/apache/jackrabbit/oak/plugins/index/lucene/ test/java...

Author: catholicon
Date: Wed Nov 15 03:33:46 2017
New Revision: 1815285

URL: http://svn.apache.org/viewvc?rev=1815285&view=rev
Log:
OAK-6905: Query engine: support coalesce function as in-built method

Implement coalesce in lucene index

Modified:
    jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/PropertyDefinition.java
    jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/util/FunctionIndexProcessor.java
    jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/util/IndexDefinitionBuilder.java
    jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/FunctionIndexTest.java
    jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/util/FunctionIndexProcessorTest.java

Modified: jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/PropertyDefinition.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/PropertyDefinition.java?rev=1815285&r1=1815284&r2=1815285&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/PropertyDefinition.java (original)
+++ jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/PropertyDefinition.java Wed Nov 15 03:33:46 2017
@@ -227,6 +227,7 @@ public class PropertyDefinition {
                 ", useInSuggest=" + useInSuggest+
                 ", nullCheckEnabled=" + nullCheckEnabled +
                 ", notNullCheckEnabled=" + notNullCheckEnabled +
+                ", function=" + function +
                 '}';
     }
 

Modified: jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/util/FunctionIndexProcessor.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/util/FunctionIndexProcessor.java?rev=1815285&r1=1815284&r2=1815285&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/util/FunctionIndexProcessor.java (original)
+++ jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/util/FunctionIndexProcessor.java Wed Nov 15 03:33:46 2017
@@ -25,6 +25,7 @@ import java.util.Deque;
 import org.apache.jackrabbit.oak.api.PropertyState;
 import org.apache.jackrabbit.oak.api.Type;
 import org.apache.jackrabbit.oak.commons.PathUtils;
+import org.apache.jackrabbit.oak.plugins.memory.EmptyPropertyState;
 import org.apache.jackrabbit.oak.plugins.memory.PropertyStates;
 import org.apache.jackrabbit.oak.spi.query.QueryConstants;
 import org.apache.jackrabbit.oak.spi.state.NodeState;
@@ -41,7 +42,9 @@ public class FunctionIndexProcessor {
             LoggerFactory.getLogger(FunctionIndexProcessor.class);
     
     private String remaining;
-    
+
+    private static final PropertyState EMPTY_PROPERTY_STATE = EmptyPropertyState.emptyProperty("empty", Type.STRINGS);
+
     private FunctionIndexProcessor(String function) {
         this.remaining = function;
     }
@@ -83,12 +86,13 @@ public class FunctionIndexProcessor {
                 ps = calculateFunction(token, stack);
             }
             if (ps == null) {
-                // currently, all operations involving null return null
-                return null;
+                ps = EMPTY_PROPERTY_STATE;
             }
             stack.push(ps);
         }
-        return stack.pop();
+
+        PropertyState ret = stack.pop();
+        return ret==EMPTY_PROPERTY_STATE ? null : ret;
     }
     
     /**
@@ -107,6 +111,18 @@ public class FunctionIndexProcessor {
     private static PropertyState calculateFunction(String functionName, 
             Deque<PropertyState> stack) {
         PropertyState ps = stack.pop();
+        if ("coalesce".equals(functionName)) {
+            // coalesce (a, b) => (a != null ? a : b)
+            // we pop stack again to consume the second parameter
+            // also, if ps is EMPTY_PROPERTY_STATE, then newly popped value is to be used
+            PropertyState ps2 = stack.pop();
+            if (ps == EMPTY_PROPERTY_STATE) {
+                ps = ps2;
+            }
+        }
+        if (ps == EMPTY_PROPERTY_STATE) {
+            return ps;
+        }
         Type<?> type = null;
         ArrayList<Object> values = new ArrayList<Object>(ps.count());
         for (int i = 0; i < ps.count(); i++) {
@@ -115,6 +131,9 @@ public class FunctionIndexProcessor {
             if ("lower".equals(functionName)) {
                 x = s.toLowerCase();
                 type = Type.STRING;
+            } else if ("coalesce".equals(functionName)) {
+                x = s;
+                type = Type.STRING;
             } else if ("upper".equals(functionName)) {
                 x = s.toUpperCase();
                 type = Type.STRING;
@@ -197,18 +216,37 @@ public class FunctionIndexProcessor {
         if (match("fn:lower-case(") || match("lower(")) {
             return "lower*" + parse() + read(")");
         }
+        if (match("fn:coalesce(") || match("coalesce(")) {
+            return "coalesce*" + parse() + readCommaAndWhitespace() + parse() + read(")");
+        }
         if (match("fn:string-length(") || match("length(")) {
             return "length*" + parse() + read(")");
         }
-        int end = remaining.indexOf(')');
-        if (end >= 0) {
-            remaining = remaining.substring(0, end);
-        }
-        if (remaining.startsWith("[")) {
-            return property(remaining.substring(1, remaining.lastIndexOf(']')).replaceAll("]]", "]"));
-        }
+
         // property name
-        return property(remaining.replaceAll("@", ""));
+        if (match("[")) {
+            String prop = remaining;
+            int indexOfComma = remaining.indexOf(",");
+            if (indexOfComma > 0) {
+                prop = remaining.substring(0, indexOfComma);
+            }
+            prop = prop.substring(0, prop.lastIndexOf(']'));
+            remaining = remaining.substring(prop.length() + 1);
+            return property(prop.replaceAll("]]", "]"));
+        } else {
+            String prop = remaining;
+            int paren = remaining.indexOf(')');
+            int comma = remaining.indexOf(',');
+            int end = comma;
+            if (paren >=0) {
+                end = (end < 0) ? paren : Math.min(end, paren);
+            }
+            if (end >= 0) {
+                prop = remaining.substring(0, end);
+            }
+            remaining = remaining.substring(prop.length());
+            return property(prop.replaceAll("@", ""));
+        }
     }
     
     String property(String p) {
@@ -219,6 +257,16 @@ public class FunctionIndexProcessor {
         match(string);
         return "";
     }
+
+    private String
+    readCommaAndWhitespace() {
+        while (match(" ")) {
+        }
+        match(",");
+        while (match(" ")) {
+        }
+        return "*";
+    }
     
     private boolean match(String string) {
         if (remaining.startsWith(string)) {

Modified: jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/util/IndexDefinitionBuilder.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/util/IndexDefinitionBuilder.java?rev=1815285&r1=1815284&r2=1815285&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/util/IndexDefinitionBuilder.java (original)
+++ jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/util/IndexDefinitionBuilder.java Wed Nov 15 03:33:46 2017
@@ -236,7 +236,7 @@ public final class IndexDefinitionBuilde
                     propTree = getOrCreateChild(getPropsTree(), propDefnNodeName);
                 }
                 propRule = new PropertyRule(this, propTree, name, regex);
-                props.put(name, propRule);
+                props.put(name != null ? name : propDefnNodeName, propRule);
             }
             return propRule;
         }
@@ -301,7 +301,9 @@ public final class IndexDefinitionBuilde
         private PropertyRule(IndexRule indexRule, Tree propTree, String name, boolean regex) {
             this.indexRule = indexRule;
             this.propTree = propTree;
-            propTree.setProperty(LuceneIndexConstants.PROP_NAME, name);
+            if (name != null) {
+                propTree.setProperty(LuceneIndexConstants.PROP_NAME, name);
+            }
             if (regex) {
                 propTree.setProperty(LuceneIndexConstants.PROP_IS_REGEX, true);
             }
@@ -405,6 +407,11 @@ public final class IndexDefinitionBuilde
             return this;
         }
 
+        public PropertyRule function(String fn) {
+            propTree.setProperty(LuceneIndexConstants.PROP_FUNCTION, fn);
+            return this;
+        }
+
         public IndexRule enclosingRule(){
             return indexRule;
         }

Modified: jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/FunctionIndexTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/FunctionIndexTest.java?rev=1815285&r1=1815284&r2=1815285&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/FunctionIndexTest.java (original)
+++ jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/FunctionIndexTest.java Wed Nov 15 03:33:46 2017
@@ -18,12 +18,14 @@
  */
 package org.apache.jackrabbit.oak.plugins.index.lucene;
 
+import static java.util.Arrays.asList;
 import static org.apache.jackrabbit.oak.api.QueryEngine.NO_BINDINGS;
 import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.INDEX_DEFINITIONS_NAME;
 import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.INDEX_DEFINITIONS_NODE_TYPE;
 import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.REINDEX_PROPERTY_NAME;
 import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.TYPE_PROPERTY_NAME;
 import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertThat;
 
 import java.text.ParseException;
@@ -40,6 +42,7 @@ import org.apache.jackrabbit.oak.api.Res
 import org.apache.jackrabbit.oak.api.ResultRow;
 import org.apache.jackrabbit.oak.api.Tree;
 import org.apache.jackrabbit.oak.api.Type;
+import org.apache.jackrabbit.oak.plugins.index.lucene.util.IndexDefinitionBuilder;
 import org.apache.jackrabbit.oak.plugins.index.nodetype.NodeTypeIndexProvider;
 import org.apache.jackrabbit.oak.plugins.index.property.PropertyIndexEditorProvider;
 import org.apache.jackrabbit.oak.plugins.memory.MemoryNodeStore;
@@ -280,7 +283,63 @@ public class FunctionIndexTest extends A
         assertThat(explainXpath(queryXPath), containsString("lucene:upper"));
         assertQuery(queryXPath, "xpath", paths);
     }
-    
+
+    @Test
+    public void coalesce() throws Exception{
+        IndexDefinitionBuilder idxb = new IndexDefinitionBuilder().noAsync();
+        idxb.indexRule("nt:base").property("foo", null).function(
+                "lower(coalesce([jcr:content/foo2], coalesce([jcr:content/foo], localname())))"
+        );
+
+        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("foo2", "BAR");
+        rootTree.addChild("b").addChild("jcr:content").setProperty("foo", "bAr");
+        Tree child = rootTree.addChild("c").addChild("jcr:content");
+        child.setProperty("foo", "bar");
+        child.setProperty("foo2", "bar1");
+        rootTree.addChild("bar");
+
+        root.commit();
+
+        assertPlanAndQuery(
+                "select * from [nt:base] where lower(coalesce([jcr:content/foo2], coalesce([jcr:content/foo], localname()))) = 'bar'",
+                "lucene:test1(/oak:index/test1)", asList("/a", "/b", "/bar"));
+    }
+
+    @Test
+    public void coalesceOrdering() throws Exception{
+        IndexDefinitionBuilder idxb = new IndexDefinitionBuilder().noAsync();
+        idxb.indexRule("nt:base").property("foo", null).function(
+                "coalesce([jcr:content/foo2], [jcr:content/foo])"
+        ).ordered();
+
+        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("foo2", "a");
+        rootTree.addChild("b").addChild("jcr:content").setProperty("foo", "b");
+        Tree child = rootTree.addChild("c").addChild("jcr:content");
+        child.setProperty("foo", "c");
+        child.setProperty("foo2", "a1");
+
+        root.commit();
+
+        assertOrderedPlanAndQuery(
+                "select * from [nt:base] order by coalesce([jcr:content/foo2], [jcr:content/foo])",
+                "lucene:test1(/oak:index/test1)", asList("/a", "/c", "/b"));
+
+        assertOrderedPlanAndQuery(
+                "select * from [nt:base] order by coalesce([jcr:content/foo2], [jcr:content/foo]) DESC",
+                "lucene:test1(/oak:index/test1)", asList("/b", "/c", "/a"));
+    }
+
+
     protected String explain(String query){
         String explain = "explain " + query;
         return executeQuery(explain, "JCR-SQL2").get(0);
@@ -293,7 +352,17 @@ public class FunctionIndexTest extends A
         String plan = row.getValue("plan").getValue(Type.STRING);
         return plan;
     }
-    
+
+    private void assertOrderedPlanAndQuery(String query, String planExpectation, List<String> paths) {
+        List<String> result = assertPlanAndQuery(query, planExpectation, paths);
+        assertEquals("Ordering doesn't match", paths, result);
+    }
+
+    private List<String> assertPlanAndQuery(String query, String planExpectation, List<String> paths){
+        assertThat(explain(query), containsString(planExpectation));
+        return assertQuery(query, paths);
+    }
+
     protected Tree createIndex(String name, Set<String> propNames) {
         Tree index = root.getTree("/");
         return createIndex(index, name, propNames);

Modified: jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/util/FunctionIndexProcessorTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/util/FunctionIndexProcessorTest.java?rev=1815285&r1=1815284&r2=1815285&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/util/FunctionIndexProcessorTest.java (original)
+++ jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/util/FunctionIndexProcessorTest.java Wed Nov 15 03:33:46 2017
@@ -20,6 +20,7 @@ package org.apache.jackrabbit.oak.plugin
 
 import static org.junit.Assert.assertEquals;
 
+import java.text.ParseException;
 import java.util.Arrays;
 
 import org.junit.Test;
@@ -58,6 +59,14 @@ public class FunctionIndexProcessorTest
         checkConvert(
                 "fn:lower-case(fn:upper-case(test/@data))", 
                 "function*lower*upper*@test/data");
+        checkConvert("fn:coalesce(jcr:content/@foo2, jcr:content/@foo)",
+                "function*coalesce*@jcr:content/foo2*@jcr:content/foo");
+        checkConvert("fn:coalesce(jcr:content/@foo2,fn:lower-case(jcr:content/@foo))",
+                "function*coalesce*@jcr:content/foo2*lower*@jcr:content/foo");
+        checkConvert("fn:coalesce(jcr:content/@foo2,fn:coalesce(jcr:content/@foo, fn:lower-case(fn:name())))",
+                "function*coalesce*@jcr:content/foo2*coalesce*@jcr:content/foo*lower*@:name");
+        checkConvert("fn:coalesce(fn:coalesce(jcr:content/@foo2,jcr:content/@foo), fn:coalesce(@a:b, @c:d))",
+                "function*coalesce*coalesce*@jcr:content/foo2*@jcr:content/foo*coalesce*@a:b*@c:d");
     }
 
     @Test
@@ -87,6 +96,14 @@ public class FunctionIndexProcessorTest
         checkConvert(
                 "[strange[0]]]", 
                 "function*@strange[0]");
+        checkConvert("coalesce([jcr:content/foo2],[jcr:content/foo])",
+                "function*coalesce*@jcr:content/foo2*@jcr:content/foo");
+        checkConvert("coalesce([jcr:content/foo2], lower([jcr:content/foo]))",
+                "function*coalesce*@jcr:content/foo2*lower*@jcr:content/foo");
+        checkConvert("coalesce([jcr:content/foo2] , coalesce([jcr:content/foo],lower(name())))",
+                "function*coalesce*@jcr:content/foo2*coalesce*@jcr:content/foo*lower*@:name");
+        checkConvert("coalesce(coalesce([jcr:content/foo2],[jcr:content/foo]), coalesce([a:b], [c:d]))",
+                "function*coalesce*coalesce*@jcr:content/foo2*@jcr:content/foo*coalesce*@a:b*@c:d");
     }
 
     private static void checkConvert(String function, String expectedPolishNotation) {