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