diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/query/QueryImpl.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/query/QueryImpl.java index 8b58dc9..25e3b7b 100644 --- a/oak-core/src/main/java/org/apache/jackrabbit/oak/query/QueryImpl.java +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/query/QueryImpl.java @@ -625,19 +625,16 @@ public class QueryImpl implements Query { OrderEntry e = list.get(i); OrderingImpl o = orderings[i]; DynamicOperandImpl op = o.getOperand(); - if (!(op instanceof PropertyValueImpl)) { - // ordered by a function: currently not supported - canSortByIndex = false; - break; - } // we only have one selector, so no need to check that // TODO support joins - String pn = ((PropertyValueImpl) op).getPropertyName(); - if (!pn.equals(e.getPropertyName())) { + String pn = op.getOrderEntryPropertyName(selectors.get(0)); + + if (pn == null || !pn.equals(e.getPropertyName())) { // ordered by another property canSortByIndex = false; break; } + if (o.isDescending() != (e.getOrder() == Order.DESCENDING)) { // ordered ascending versus descending canSortByIndex = false; diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/DynamicOperandImpl.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/DynamicOperandImpl.java index af68bac..6ce460c 100644 --- a/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/DynamicOperandImpl.java +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/DynamicOperandImpl.java @@ -21,6 +21,7 @@ import java.util.Set; import org.apache.jackrabbit.oak.api.PropertyValue; import org.apache.jackrabbit.oak.query.index.FilterImpl; +import org.apache.jackrabbit.oak.spi.query.QueryConstants; import org.apache.jackrabbit.oak.spi.query.QueryIndex.OrderEntry; /** @@ -114,4 +115,17 @@ public abstract class DynamicOperandImpl extends AstElement { */ public abstract OrderEntry getOrderEntry(SelectorImpl s, OrderingImpl o); + /** + * + * @param s + * @return the property name as defined in the OrderEntry for the DynamicOperand + */ + public String getOrderEntryPropertyName(SelectorImpl s) { + String fn = getFunction(s); + if (fn != null) { + return QueryConstants.FUNCTION_RESTRICTION_PREFIX + fn; + } + return null; + } + } diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/FullTextSearchScoreImpl.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/FullTextSearchScoreImpl.java index 128d2f4..a950d66 100644 --- a/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/FullTextSearchScoreImpl.java +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/FullTextSearchScoreImpl.java @@ -118,4 +118,9 @@ public class FullTextSearchScoreImpl extends DynamicOperandImpl { return null; } + @Override + public String getOrderEntryPropertyName(SelectorImpl s) { + return null; + } + } diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/LowerCaseImpl.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/LowerCaseImpl.java index f8c3316..97ab71d 100644 --- a/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/LowerCaseImpl.java +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/LowerCaseImpl.java @@ -151,4 +151,5 @@ public class LowerCaseImpl extends DynamicOperandImpl { return null; } + } diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/PropertyValueImpl.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/PropertyValueImpl.java index 33b27ad..7177501 100644 --- a/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/PropertyValueImpl.java +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/PropertyValueImpl.java @@ -59,6 +59,7 @@ public class PropertyValueImpl extends DynamicOperandImpl { return selectorName; } + public String getPropertyName() { return propertyName; } @@ -180,4 +181,13 @@ public class PropertyValueImpl extends DynamicOperandImpl { OrderEntry.Order.DESCENDING : OrderEntry.Order.ASCENDING); } + @Override + public String getOrderEntryPropertyName(SelectorImpl s) { + if (!s.equals(selector)) { + // ordered by a different selector + return null; + } + return normalizePropertyName(propertyName); + } + } diff --git a/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/FunctionIndexTest.java b/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/FunctionIndexTest.java index 1df5418..191ae6d 100644 --- a/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/FunctionIndexTest.java +++ b/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/FunctionIndexTest.java @@ -24,6 +24,7 @@ import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.INDEX_DEFIN 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.apache.jackrabbit.oak.plugins.index.lucene.TestUtil.useV2; import static org.hamcrest.CoreMatchers.containsString; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; @@ -241,6 +242,632 @@ public class FunctionIndexTest extends AbstractQueryTest { } @Test + public void testOrdering2() throws Exception { + Tree index = root.getTree("/"); + Tree indexDefn = createTestIndexNode(index, LuceneIndexConstants.TYPE_LUCENE); + useV2(indexDefn); + indexDefn.setProperty(LuceneIndexConstants.TEST_MODE, true); + indexDefn.setProperty(FulltextIndexConstants.EVALUATE_PATH_RESTRICTION, true); + Tree props = TestUtil.newRulePropTree(indexDefn, "nt:unstructured"); + props.getParent().setProperty(FulltextIndexConstants.INDEX_NODE_NAME, true); + TestUtil.enableForFullText(props, FulltextIndexConstants.REGEX_ALL_PROPS, true); + Tree upper = TestUtil.enableFunctionIndex(props, "upper([foo])"); + upper.setProperty(FulltextIndexConstants.PROP_ORDERED, true); + + root.commit(); + + Tree test = root.getTree("/").addChild("test"); + test.setProperty("jcr:primaryType", "nt:unstructured", Type.NAME); + + + Tree a = test.addChild("n1"); + a.setProperty("jcr:primaryType", "nt:unstructured", Type.NAME); + a.setProperty("foo", "hello"); + + a = test.addChild("n2"); + a.setProperty("jcr:primaryType", "nt:unstructured", Type.NAME); + a.setProperty("foo", "World!"); + a = test.addChild("n3"); + a.setProperty("jcr:primaryType", "nt:unstructured", Type.NAME); + a.setProperty("foo", "Hallo"); + a = test.addChild("n4"); + a.setProperty("jcr:primaryType", "nt:unstructured", Type.NAME); + a.setProperty("foo", "10%"); + a = test.addChild("n5"); + a.setProperty("jcr:primaryType", "nt:unstructured", Type.NAME); + a.setProperty("foo", "10 percent"); + + a = test.addChild("n0"); + a.setProperty("jcr:primaryType", "nt:unstructured", Type.NAME); + a = test.addChild("n9"); + a.setProperty("jcr:primaryType", "nt:unstructured", Type.NAME); + + String query = "select a.[foo]\n" + + "\t from [nt:unstructured] as a\n" + + "\t where a.foo is not null and isdescendantnode(a , '/test') order by upper(a.foo)"; + + root.commit(); + + assertThat(explain(query), containsString("lucene:test-index(/oak:index/test-index)")); + + List result = executeQuery(query, SQL2); + assertEquals("Ordering doesn't match", asList("10 percent", "10%", "Hallo", "hello", "World!"), result); + + } + + + /* + Test order by func(a),func(b) + order by func(b),func(a) + func(a) DESC,func(b) + func(a),func(b)DESC + where both func(a) and func(b) have ordered set = true + Correct ordering is effectively served by the index + */ + @Test + public void testOrdering3() throws Exception { + + Tree index = root.getTree("/"); + Tree indexDefn = createTestIndexNode(index, LuceneIndexConstants.TYPE_LUCENE); + useV2(indexDefn); + indexDefn.setProperty(LuceneIndexConstants.TEST_MODE, true); + indexDefn.setProperty(FulltextIndexConstants.EVALUATE_PATH_RESTRICTION, true); + Tree props = TestUtil.newRulePropTree(indexDefn, "nt:unstructured"); + props.getParent().setProperty(FulltextIndexConstants.INDEX_NODE_NAME, true); + TestUtil.enableForFullText(props, FulltextIndexConstants.REGEX_ALL_PROPS, true); + + Tree upper = TestUtil.enableFunctionIndex(props, "upper([foo])"); + upper.setProperty(FulltextIndexConstants.PROP_ORDERED, true); + + Tree upper2 = TestUtil.enableFunctionIndex(props, "upper([foo2])"); + upper2.setProperty(FulltextIndexConstants.PROP_ORDERED, true); + + + root.commit(); + + Tree test = root.getTree("/").addChild("test"); + test.setProperty("jcr:primaryType", "nt:unstructured", Type.NAME); + + + Tree a = test.addChild("n1"); + a.setProperty("jcr:primaryType", "nt:unstructured", Type.NAME); + a.setProperty("foo", "a1"); + a.setProperty("foo2", "b2"); + + a = test.addChild("n2"); + a.setProperty("jcr:primaryType", "nt:unstructured", Type.NAME); + a.setProperty("foo", "a2"); + a.setProperty("foo2", "b3"); + + a = test.addChild("n3"); + a.setProperty("jcr:primaryType", "nt:unstructured", Type.NAME); + a.setProperty("foo", "a3"); + a.setProperty("foo2", "b1"); + + a = test.addChild("n4"); + a.setProperty("jcr:primaryType", "nt:unstructured", Type.NAME); + a.setProperty("foo", "a1"); + a.setProperty("foo2", "b3"); + + a = test.addChild("n5"); + a.setProperty("jcr:primaryType", "nt:unstructured", Type.NAME); + a.setProperty("foo", "a1"); + a.setProperty("foo2", "b1"); + + + String query = "select a.[foo],a.[foo2]\n" + + "\t from [nt:unstructured] as a\n" + + "\t where a.foo is not null and isdescendantnode(a , '/test') order by upper(a.foo),upper(a.foo2)"; + + root.commit(); + + List result = executeQuery(query, SQL2); + + assertEquals("Ordering doesn't match", asList("a1, b1", "a1, b2", "a1, b3", "a2, b3", "a3, b1"), result); + + query = "select a.[foo2],a.[foo]\n" + + "\t from [nt:unstructured] as a\n" + + "\t where a.foo is not null and isdescendantnode(a , '/test') order by upper(a.foo2),upper(a.foo)"; + + result = executeQuery(query, SQL2); + + assertEquals("Ordering doesn't match", asList("b1, a1", "b1, a3", "b2, a1", "b3, a1", "b3, a2"), result); + + query = "select a.[foo],a.[foo2]\n" + + "\t from [nt:unstructured] as a\n" + + "\t where a.foo is not null and isdescendantnode(a , '/test') order by upper(a.foo) DESC, upper(a.foo2)"; + result = executeQuery(query, SQL2); + + assertEquals("Ordering doesn't match", asList("a3, b1", "a2, b3", "a1, b1", "a1, b2", "a1, b3"), result); + + query = "select a.[foo],a.[foo2]\n" + + "\t from [nt:unstructured] as a\n" + + "\t where a.foo is not null and isdescendantnode(a , '/test') order by upper(a.foo), upper(a.foo2) DESC"; + + result = executeQuery(query, SQL2); + + assertEquals("Ordering doesn't match", asList("a1, b3", "a1, b2", "a1, b1", "a2, b3", "a3, b1"), result); + + + } + + /* + Test order by func(a),func(b) + order by func(b),func(a) + func(a) DESC,func(b) + func(a),func(b)DESC + where only func(a) is ordered by index + The effective ordering in this case will be done by QueryEngine + */ + @Test + public void testOrdering4() throws Exception { + Tree index = root.getTree("/"); + Tree indexDefn = createTestIndexNode(index, LuceneIndexConstants.TYPE_LUCENE); + useV2(indexDefn); + indexDefn.setProperty(LuceneIndexConstants.TEST_MODE, true); + indexDefn.setProperty(FulltextIndexConstants.EVALUATE_PATH_RESTRICTION, true); + Tree props = TestUtil.newRulePropTree(indexDefn, "nt:unstructured"); + props.getParent().setProperty(FulltextIndexConstants.INDEX_NODE_NAME, true); + TestUtil.enableForFullText(props, FulltextIndexConstants.REGEX_ALL_PROPS, true); + + Tree upper = TestUtil.enableFunctionIndex(props, "upper([foo])"); + upper.setProperty(FulltextIndexConstants.PROP_ORDERED, true); + + TestUtil.enableFunctionIndex(props, "upper([foo2])"); + + + root.commit(); + + Tree test = root.getTree("/").addChild("test"); + test.setProperty("jcr:primaryType", "nt:unstructured", Type.NAME); + + + Tree a = test.addChild("n1"); + a.setProperty("jcr:primaryType", "nt:unstructured", Type.NAME); + a.setProperty("foo", "a1"); + a.setProperty("foo2", "b2"); + + a = test.addChild("n2"); + a.setProperty("jcr:primaryType", "nt:unstructured", Type.NAME); + a.setProperty("foo", "a2"); + a.setProperty("foo2", "b3"); + + a = test.addChild("n3"); + a.setProperty("jcr:primaryType", "nt:unstructured", Type.NAME); + a.setProperty("foo", "a3"); + a.setProperty("foo2", "b1"); + + a = test.addChild("n4"); + a.setProperty("jcr:primaryType", "nt:unstructured", Type.NAME); + a.setProperty("foo", "a1"); + a.setProperty("foo2", "b3"); + + a = test.addChild("n5"); + a.setProperty("jcr:primaryType", "nt:unstructured", Type.NAME); + a.setProperty("foo", "a1"); + a.setProperty("foo2", "b1"); + + + String query = "select a.[foo],a.[foo2]\n" + + "\t from [nt:unstructured] as a\n" + + "\t where a.foo is not null and isdescendantnode(a , '/test') order by upper(a.foo),upper(a.foo2)"; + + root.commit(); + + List result = executeQuery(query, SQL2); + + assertEquals("Ordering doesn't match", asList("a1, b1", "a1, b2", "a1, b3", "a2, b3", "a3, b1"), result); + + query = "select a.[foo2],a.[foo]\n" + + "\t from [nt:unstructured] as a\n" + + "\t where a.foo is not null and isdescendantnode(a , '/test') order by upper(a.foo2),upper(a.foo)"; + + result = executeQuery(query, SQL2); + + assertEquals("Ordering doesn't match", asList("b1, a1", "b1, a3", "b2, a1", "b3, a1", "b3, a2"), result); + + query = "select a.[foo],a.[foo2]\n" + + "\t from [nt:unstructured] as a\n" + + "\t where a.foo is not null and isdescendantnode(a , '/test') order by upper(a.foo) DESC, upper(a.foo2)"; + result = executeQuery(query, SQL2); + + assertEquals("Ordering doesn't match", asList("a3, b1", "a2, b3", "a1, b1", "a1, b2", "a1, b3"), result); + + query = "select a.[foo],a.[foo2]\n" + + "\t from [nt:unstructured] as a\n" + + "\t where a.foo is not null and isdescendantnode(a , '/test') order by upper(a.foo), upper(a.foo2) DESC"; + + result = executeQuery(query, SQL2); + + assertEquals("Ordering doesn't match", asList("a1, b3", "a1, b2", "a1, b1", "a2, b3", "a3, b1"), result); + + } + + /* + Test order by func(a),b + order by b,func(a) + order by func(a) DESC,b + order by func(a),b DESC + where both b and func(a) have ordered=true + */ + @Test + public void testOrdering5() throws Exception { + Tree index = root.getTree("/"); + Tree indexDefn = createTestIndexNode(index, LuceneIndexConstants.TYPE_LUCENE); + useV2(indexDefn); + indexDefn.setProperty(LuceneIndexConstants.TEST_MODE, true); + indexDefn.setProperty(FulltextIndexConstants.EVALUATE_PATH_RESTRICTION, true); + Tree props = TestUtil.newRulePropTree(indexDefn, "nt:unstructured"); + props.getParent().setProperty(FulltextIndexConstants.INDEX_NODE_NAME, true); + TestUtil.enableForFullText(props, FulltextIndexConstants.REGEX_ALL_PROPS, true); + + Tree upper = TestUtil.enableFunctionIndex(props, "upper([foo])"); + upper.setProperty(FulltextIndexConstants.PROP_ORDERED, true); + + + Tree upper2 = TestUtil.enablePropertyIndex(props, "foo2", false); + upper2.setProperty(FulltextIndexConstants.PROP_ORDERED, true); + + + root.commit(); + + Tree test = root.getTree("/").addChild("test"); + test.setProperty("jcr:primaryType", "nt:unstructured", Type.NAME); + + + Tree a = test.addChild("n1"); + a.setProperty("jcr:primaryType", "nt:unstructured", Type.NAME); + a.setProperty("foo", "a1"); + a.setProperty("foo2", "b2"); + + a = test.addChild("n2"); + a.setProperty("jcr:primaryType", "nt:unstructured", Type.NAME); + a.setProperty("foo", "a2"); + a.setProperty("foo2", "b3"); + + a = test.addChild("n3"); + a.setProperty("jcr:primaryType", "nt:unstructured", Type.NAME); + a.setProperty("foo", "a3"); + a.setProperty("foo2", "b1"); + + a = test.addChild("n4"); + a.setProperty("jcr:primaryType", "nt:unstructured", Type.NAME); + a.setProperty("foo", "a1"); + a.setProperty("foo2", "b3"); + + a = test.addChild("n5"); + a.setProperty("jcr:primaryType", "nt:unstructured", Type.NAME); + a.setProperty("foo", "a1"); + a.setProperty("foo2", "b1"); + + + String query = "select a.[foo],a.[foo2]\n" + + "\t from [nt:unstructured] as a\n" + + "\t where a.foo is not null and isdescendantnode(a , '/test') order by upper(a.foo),a.foo2"; + + root.commit(); + + List result = executeQuery(query, SQL2); + + + assertEquals("Ordering doesn't match", asList("a1, b1", "a1, b2", "a1, b3", "a2, b3", "a3, b1"), result); + + query = "select a.[foo2],a.[foo]\n" + + "\t from [nt:unstructured] as a\n" + + "\t where a.foo is not null and isdescendantnode(a , '/test') order by a.foo2,upper(a.foo)"; + + result = executeQuery(query, SQL2); + + assertEquals("Ordering doesn't match", asList("b1, a1", "b1, a3", "b2, a1", "b3, a1", "b3, a2"), result); + + query = "select a.[foo],a.[foo2]\n" + + "\t from [nt:unstructured] as a\n" + + "\t where a.foo is not null and isdescendantnode(a , '/test') order by upper(a.foo) DESC, a.foo2"; + result = executeQuery(query, SQL2); + + assertEquals("Ordering doesn't match", asList("a3, b1", "a2, b3", "a1, b1", "a1, b2", "a1, b3"), result); + + query = "select a.[foo],a.[foo2]\n" + + "\t from [nt:unstructured] as a\n" + + "\t where a.foo is not null and isdescendantnode(a , '/test') order by upper(a.foo), a.foo2 DESC"; + + result = executeQuery(query, SQL2); + + assertEquals("Ordering doesn't match", asList("a1, b3", "a1, b2", "a1, b1", "a2, b3", "a3, b1"), result); + + } + + /* + Test order by func(a),b + orrder by b,func(a) + order by func(a) DESC,b + order by func(a),b DESC + where func(a) does not have ordered = true + */ + @Test + public void testOrdering6() throws Exception { + Tree index = root.getTree("/"); + Tree indexDefn = createTestIndexNode(index, LuceneIndexConstants.TYPE_LUCENE); + useV2(indexDefn); + indexDefn.setProperty(LuceneIndexConstants.TEST_MODE, true); + indexDefn.setProperty(FulltextIndexConstants.EVALUATE_PATH_RESTRICTION, true); + Tree props = TestUtil.newRulePropTree(indexDefn, "nt:unstructured"); + props.getParent().setProperty(FulltextIndexConstants.INDEX_NODE_NAME, true); + TestUtil.enableForFullText(props, FulltextIndexConstants.REGEX_ALL_PROPS, true); + + TestUtil.enableFunctionIndex(props, "upper([foo])"); + + + Tree upper2 = TestUtil.enablePropertyIndex(props, "foo2", false); + upper2.setProperty(FulltextIndexConstants.PROP_ORDERED, true); + + + root.commit(); + + Tree test = root.getTree("/").addChild("test"); + test.setProperty("jcr:primaryType", "nt:unstructured", Type.NAME); + + + Tree a = test.addChild("n1"); + a.setProperty("jcr:primaryType", "nt:unstructured", Type.NAME); + a.setProperty("foo", "a1"); + a.setProperty("foo2", "b2"); + + a = test.addChild("n2"); + a.setProperty("jcr:primaryType", "nt:unstructured", Type.NAME); + a.setProperty("foo", "a2"); + a.setProperty("foo2", "b3"); + + a = test.addChild("n3"); + a.setProperty("jcr:primaryType", "nt:unstructured", Type.NAME); + a.setProperty("foo", "a3"); + a.setProperty("foo2", "b1"); + + a = test.addChild("n4"); + a.setProperty("jcr:primaryType", "nt:unstructured", Type.NAME); + a.setProperty("foo", "a1"); + a.setProperty("foo2", "b3"); + + a = test.addChild("n5"); + a.setProperty("jcr:primaryType", "nt:unstructured", Type.NAME); + a.setProperty("foo", "a1"); + a.setProperty("foo2", "b1"); + + + String query = "select a.[foo],a.[foo2]\n" + + "\t from [nt:unstructured] as a\n" + + "\t where a.foo is not null and isdescendantnode(a , '/test') order by upper(a.foo),a.foo2"; + + root.commit(); + + List result = executeQuery(query, SQL2); + + assertEquals("Ordering doesn't match", asList("a1, b1", "a1, b2", "a1, b3", "a2, b3", "a3, b1"), result); + + query = "select a.[foo2],a.[foo]\n" + + "\t from [nt:unstructured] as a\n" + + "\t where a.foo is not null and isdescendantnode(a , '/test') order by a.foo2,upper(a.foo)"; + + result = executeQuery(query, SQL2); + + assertEquals("Ordering doesn't match", asList("b1, a1", "b1, a3", "b2, a1", "b3, a1", "b3, a2"), result); + + query = "select a.[foo],a.[foo2]\n" + + "\t from [nt:unstructured] as a\n" + + "\t where a.foo is not null and isdescendantnode(a , '/test') order by upper(a.foo) DESC, a.foo2"; + result = executeQuery(query, SQL2); + + assertEquals("Ordering doesn't match", asList("a3, b1", "a2, b3", "a1, b1", "a1, b2", "a1, b3"), result); + + query = "select a.[foo],a.[foo2]\n" + + "\t from [nt:unstructured] as a\n" + + "\t where a.foo is not null and isdescendantnode(a , '/test') order by upper(a.foo), a.foo2 DESC"; + + result = executeQuery(query, SQL2); + + assertEquals("Ordering doesn't match", asList("a1, b3", "a1, b2", "a1, b1", "a2, b3", "a3, b1"), result); + } + + /* + Testing order by for + different function implementations + */ + @Test + public void testOrdering7() throws Exception { + Tree index = root.getTree("/"); + Tree indexDefn = createTestIndexNode(index, LuceneIndexConstants.TYPE_LUCENE); + useV2(indexDefn); + indexDefn.setProperty(LuceneIndexConstants.TEST_MODE, true); + indexDefn.setProperty(FulltextIndexConstants.EVALUATE_PATH_RESTRICTION, true); + Tree props = TestUtil.newRulePropTree(indexDefn, "nt:unstructured"); + props.getParent().setProperty(FulltextIndexConstants.INDEX_NODE_NAME, true); + TestUtil.enableForFullText(props, FulltextIndexConstants.REGEX_ALL_PROPS, true); + + Tree fn = TestUtil.enableFunctionIndex(props, "upper([foo])"); + fn.setProperty(FulltextIndexConstants.PROP_ORDERED, true); + + fn = TestUtil.enableFunctionIndex(props, "lower([foo])"); + fn.setProperty(FulltextIndexConstants.PROP_ORDERED, true); + + fn = TestUtil.enableFunctionIndex(props, "length([foo])"); + fn.setProperty(FulltextIndexConstants.PROP_ORDERED, true); + // Any function property trying to sory by length needs to explicitly set the type to Long + fn.setProperty(FulltextIndexConstants.PROP_TYPE, "Long"); + + fn = TestUtil.enableFunctionIndex(props, "coalesce([foo2],[foo])"); + fn.setProperty(FulltextIndexConstants.PROP_ORDERED, true); + + fn = TestUtil.enableFunctionIndex(props, "name()"); + fn.setProperty(FulltextIndexConstants.PROP_ORDERED, true); + + fn = TestUtil.enableFunctionIndex(props, "localname()"); + fn.setProperty(FulltextIndexConstants.PROP_ORDERED, true); + + fn = TestUtil.enableFunctionIndex(props, "lower(coalesce([foo2], coalesce([foo], localname())))"); + fn.setProperty(FulltextIndexConstants.PROP_ORDERED, true); + + fn = TestUtil.enableFunctionIndex(props, "length(coalesce([foo], coalesce([foo2], localname())))"); + fn.setProperty(FulltextIndexConstants.PROP_ORDERED, true); + fn.setProperty(FulltextIndexConstants.PROP_TYPE, "Long"); + + root.commit(); + + Tree test = root.getTree("/").addChild("test"); + test.setProperty("jcr:primaryType", "nt:unstructured", Type.NAME); + + + Tree a = test.addChild("d1"); + a.setProperty("jcr:primaryType", "nt:unstructured", Type.NAME); + a.setProperty("foo", "c"); + + a = test.addChild("d2"); + a.setProperty("jcr:primaryType", "nt:unstructured", Type.NAME); + a.setProperty("foo", "bbbb"); + a.setProperty("foo2", "22"); + + + a = test.addChild("d3"); + a.setProperty("jcr:primaryType", "nt:unstructured", Type.NAME); + a.setProperty("foo", "aa"); + + a = test.addChild("jcr:content"); + a.setProperty("jcr:primaryType", "nt:unstructured", Type.NAME); + a.setProperty("foo", "test"); + a.setProperty("foo2", "11"); + + root.commit(); + + String query = "select [jcr:path]\n" + + "\t from [nt:unstructured] as a\n" + + "\t where isdescendantnode(a , '/test') order by coalesce([foo2],[foo]) "; + + List result = executeQuery(query, SQL2); + + assertEquals("Ordering doesn't match", asList("/test/jcr:content", "/test/d2", "/test/d3", "/test/d1"), result); + + query = "select a.[foo]\n" + + "\t from [nt:unstructured] as a\n" + + "\t where isdescendantnode(a , '/test') order by lower([a].[foo])"; + + result = executeQuery(query, SQL2); + + assertEquals("Ordering doesn't match", asList("aa", "bbbb", "c", "test"), result); + + query = "select [jcr:path]\n" + + "\t from [nt:unstructured] as a\n" + + "\t where isdescendantnode(a , '/test') order by localname() "; + + result = executeQuery(query, SQL2); + + assertEquals("Ordering doesn't match", asList("/test/jcr:content", "/test/d1", "/test/d2", "/test/d3"), result); + + + query = "select [jcr:path]\n" + + "\t from [nt:unstructured] as a\n" + + "\t where isdescendantnode(a , '/test') order by name() "; + + result = executeQuery(query, SQL2); + + assertEquals("Ordering doesn't match", asList("/test/d1", "/test/d2", "/test/d3", "/test/jcr:content"), result); + + query = "select [jcr:path]\n" + + "\t from [nt:unstructured] as a\n" + + "\t where isdescendantnode(a , '/test') order by lower(coalesce([a].[foo2], coalesce([a].[foo], localname())))"; + + result = executeQuery(query, SQL2); + + assertEquals("Ordering doesn't match", asList("/test/jcr:content", "/test/d2", "/test/d3", "/test/d1"), result); + + query = "select [jcr:path]\n" + + "\t from [nt:unstructured] as a\n" + + "\t where isdescendantnode(a , '/test') order by lower(coalesce([a].[foo2], coalesce([a].[foo], localname()))) DESC"; + + result = executeQuery(query, SQL2); + + assertEquals("Ordering doesn't match", asList("/test/d1", "/test/d3", "/test/d2", "/test/jcr:content"), result); + + query = "select [jcr:path]\n" + + "\t from [nt:unstructured] as a\n" + + "\t where a.[foo] is not null AND isdescendantnode(a , '/test') order by length([a].[foo]) DESC, localname()"; + + result = executeQuery(query, SQL2); + + assertEquals("Ordering doesn't match", asList("/test/jcr:content", "/test/d2", "/test/d3", "/test/d1"), result); + + query = "select [jcr:path]\n" + + "\t from [nt:unstructured] as a\n" + + "\t where a.[foo] is not null AND isdescendantnode(a , '/test') order by length([a].[foo]), localname()"; + + result = executeQuery(query, SQL2); + + assertEquals("Ordering doesn't match", asList("/test/d1", "/test/d3", "/test/jcr:content", "/test/d2"), result); + + + query = "select [jcr:path]\n" + + "\t from [nt:unstructured] as a\n" + + "\t where a.[foo] is not null AND isdescendantnode(a , '/test') order by length(coalesce([foo], coalesce([foo2], localname()))), localname() DESC"; + + result = executeQuery(query, SQL2); + + assertEquals("Ordering doesn't match", asList("/test/d1", "/test/d3", "/test/d2", "/test/jcr:content"), result); + + + } + + @Test + public void testOrdering() throws Exception { + Tree luceneIndex = createIndex("upper", Collections.emptySet()); + Tree nonFunc = luceneIndex.addChild(FulltextIndexConstants.INDEX_RULES) + .addChild("nt:base") + .addChild(FulltextIndexConstants.PROP_NODE) + .addChild("foo"); + nonFunc.setProperty(FulltextIndexConstants.PROP_ORDERED, true); + nonFunc.setProperty(FulltextIndexConstants.PROP_PROPERTY_INDEX, true); + nonFunc.setProperty("name", "foo"); + + Tree func = luceneIndex.getChild(FulltextIndexConstants.INDEX_RULES) + .getChild("nt:base") + .getChild(FulltextIndexConstants.PROP_NODE) + .addChild("fooUpper"); + func.setProperty(FulltextIndexConstants.PROP_ORDERED, true); + func.setProperty(FulltextIndexConstants.PROP_FUNCTION, "fn:upper-case(@foo)"); + func.setProperty(FulltextIndexConstants.PROP_PROPERTY_INDEX, true); + + root.commit(); + + Tree test = root.getTree("/").addChild("test"); + test.setProperty("jcr:primaryType", "nt:unstructured", Type.NAME); + + List paths = Lists.newArrayList(); + for (int idx = 0; idx < 10; idx++) { + paths.add("/test/n" + idx); + if (idx % 2 == 0) continue; + Tree a = test.addChild("n" + idx); + a.setProperty("jcr:primaryType", "nt:unstructured", Type.NAME); + a.setProperty("foo", "bar" + idx); + + } + for (int idx = 0; idx < 10; idx++) { + if (idx % 2 != 0) continue; + Tree a = test.addChild("n" + idx); + a.setProperty("jcr:primaryType", "nt:unstructured", Type.NAME); + a.setProperty("foo", "bar" + idx); + } + root.commit(); + + String query = "/jcr:root//element(*, nt:unstructured) [jcr:like(fn:upper-case(@foo),'BAR%')] order by foo"; + assertThat(explainXpath(query), containsString("lucene:upper")); + List result = assertQuery(query, "xpath", paths); + assertEquals("Ordering doesn't match", paths, result); + + + query = "/jcr:root//element(*, nt:unstructured) [jcr:like(fn:upper-case(@foo),'BAR%')] order by fn:upper-case(@foo)"; + assertThat(explainXpath(query), containsString("lucene:upper")); + List result2 = assertQuery(query, "xpath", paths); + assertEquals("Ordering doesn't match", paths, result2); + } + + @Test public void upperCaseRelative() throws Exception { Tree luceneIndex = createIndex("upper", Collections.emptySet()); Tree func = luceneIndex.addChild(FulltextIndexConstants.INDEX_RULES) @@ -393,6 +1020,7 @@ public class FunctionIndexTest extends AbstractQueryTest { } } + /* Given an index def with 2 orderable property definitions(Relative) for same property - one with function and one without Order by should give correct results diff --git a/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/spi/query/FulltextIndexPlanner.java b/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/spi/query/FulltextIndexPlanner.java index 82226a4..2d2da8c 100644 --- a/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/spi/query/FulltextIndexPlanner.java +++ b/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/spi/query/FulltextIndexPlanner.java @@ -884,7 +884,7 @@ public class FulltextIndexPlanner { orderEntries.add(IndexDefinition.NATIVE_SORT_ORDER); } for (PropertyDefinition functionIndex : rule.getFunctionRestrictions()) { - if (o.getPropertyName().equals(functionIndex.function)) { + if (functionIndex.ordered && o.getPropertyName().equals(functionIndex.function)) { // can manage any order desc/asc orderEntries.add(o); result.sortedProperties.add(functionIndex);