Author: reschke
Date: Fri Jan 31 18:21:15 2020
New Revision: 1873427

URL: http://svn.apache.org/viewvc?rev=1873427&view=rev
Log:
OAK-8845 | Function-based indexing don't support ordering  (merged r1873010 and 
r1873070 into 1.22)

Modified:
    jackrabbit/oak/branches/1.22/   (props changed)
    
jackrabbit/oak/branches/1.22/oak-core/src/main/java/org/apache/jackrabbit/oak/query/QueryImpl.java
    
jackrabbit/oak/branches/1.22/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/DynamicOperandImpl.java
    
jackrabbit/oak/branches/1.22/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/FullTextSearchScoreImpl.java
    
jackrabbit/oak/branches/1.22/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/LowerCaseImpl.java
    
jackrabbit/oak/branches/1.22/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/PropertyValueImpl.java
    
jackrabbit/oak/branches/1.22/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/FunctionIndexTest.java
    
jackrabbit/oak/branches/1.22/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/spi/query/FulltextIndexPlanner.java
    
jackrabbit/oak/branches/1.22/oak-solr-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/solr/query/SolrQueryIndex.java

Propchange: jackrabbit/oak/branches/1.22/
------------------------------------------------------------------------------
  Merged /jackrabbit/oak/trunk:r1873010,1873070

Modified: 
jackrabbit/oak/branches/1.22/oak-core/src/main/java/org/apache/jackrabbit/oak/query/QueryImpl.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.22/oak-core/src/main/java/org/apache/jackrabbit/oak/query/QueryImpl.java?rev=1873427&r1=1873426&r2=1873427&view=diff
==============================================================================
--- 
jackrabbit/oak/branches/1.22/oak-core/src/main/java/org/apache/jackrabbit/oak/query/QueryImpl.java
 (original)
+++ 
jackrabbit/oak/branches/1.22/oak-core/src/main/java/org/apache/jackrabbit/oak/query/QueryImpl.java
 Fri Jan 31 18:21:15 2020
@@ -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;

Modified: 
jackrabbit/oak/branches/1.22/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/DynamicOperandImpl.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.22/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/DynamicOperandImpl.java?rev=1873427&r1=1873426&r2=1873427&view=diff
==============================================================================
--- 
jackrabbit/oak/branches/1.22/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/DynamicOperandImpl.java
 (original)
+++ 
jackrabbit/oak/branches/1.22/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/DynamicOperandImpl.java
 Fri Jan 31 18:21:15 2020
@@ -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
      */
     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;
+    }
+
 }

Modified: 
jackrabbit/oak/branches/1.22/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/FullTextSearchScoreImpl.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.22/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/FullTextSearchScoreImpl.java?rev=1873427&r1=1873426&r2=1873427&view=diff
==============================================================================
--- 
jackrabbit/oak/branches/1.22/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/FullTextSearchScoreImpl.java
 (original)
+++ 
jackrabbit/oak/branches/1.22/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/FullTextSearchScoreImpl.java
 Fri Jan 31 18:21:15 2020
@@ -118,4 +118,9 @@ public class FullTextSearchScoreImpl ext
         return null;
     }
 
+    @Override
+    public String getOrderEntryPropertyName(SelectorImpl s) {
+        return null;
+    }
+
 }

Modified: 
jackrabbit/oak/branches/1.22/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/LowerCaseImpl.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.22/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/LowerCaseImpl.java?rev=1873427&r1=1873426&r2=1873427&view=diff
==============================================================================
--- 
jackrabbit/oak/branches/1.22/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/LowerCaseImpl.java
 (original)
+++ 
jackrabbit/oak/branches/1.22/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/LowerCaseImpl.java
 Fri Jan 31 18:21:15 2020
@@ -149,6 +149,7 @@ public class LowerCaseImpl extends Dynam
                 OrderEntry.Order.DESCENDING : OrderEntry.Order.ASCENDING);
         }
         return null;
-    }        
+    }
+
 
 }

Modified: 
jackrabbit/oak/branches/1.22/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/PropertyValueImpl.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.22/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/PropertyValueImpl.java?rev=1873427&r1=1873426&r2=1873427&view=diff
==============================================================================
--- 
jackrabbit/oak/branches/1.22/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/PropertyValueImpl.java
 (original)
+++ 
jackrabbit/oak/branches/1.22/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/PropertyValueImpl.java
 Fri Jan 31 18:21:15 2020
@@ -59,6 +59,7 @@ public class PropertyValueImpl extends D
         return selectorName;
     }
 
+
     public String getPropertyName() {
         return propertyName;
     }
@@ -180,4 +181,13 @@ public class PropertyValueImpl extends D
             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);
+    }
+
 }

Modified: 
jackrabbit/oak/branches/1.22/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/FunctionIndexTest.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.22/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/FunctionIndexTest.java?rev=1873427&r1=1873426&r2=1873427&view=diff
==============================================================================
--- 
jackrabbit/oak/branches/1.22/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/FunctionIndexTest.java
 (original)
+++ 
jackrabbit/oak/branches/1.22/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/FunctionIndexTest.java
 Fri Jan 31 18:21:15 2020
@@ -24,6 +24,7 @@ import static org.apache.jackrabbit.oak.
 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 A
     }
 
     @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<String> 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<String> 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<String> 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<String> 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<String> 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<String> 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.<String>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<String> 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<String> 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<String> result2 = assertQuery(query, "xpath", paths);
+        assertEquals("Ordering doesn't match", paths, result2);
+    }
+
+    @Test
     public void upperCaseRelative() throws Exception {
         Tree luceneIndex = createIndex("upper", 
Collections.<String>emptySet());
         Tree func = luceneIndex.addChild(FulltextIndexConstants.INDEX_RULES)
@@ -393,6 +1020,7 @@ public class FunctionIndexTest extends A
         }
 
     }
+
     /*
     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

Modified: 
jackrabbit/oak/branches/1.22/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/spi/query/FulltextIndexPlanner.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.22/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/spi/query/FulltextIndexPlanner.java?rev=1873427&r1=1873426&r2=1873427&view=diff
==============================================================================
--- 
jackrabbit/oak/branches/1.22/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/spi/query/FulltextIndexPlanner.java
 (original)
+++ 
jackrabbit/oak/branches/1.22/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/spi/query/FulltextIndexPlanner.java
 Fri Jan 31 18:21:15 2020
@@ -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);

Modified: 
jackrabbit/oak/branches/1.22/oak-solr-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/solr/query/SolrQueryIndex.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.22/oak-solr-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/solr/query/SolrQueryIndex.java?rev=1873427&r1=1873426&r2=1873427&view=diff
==============================================================================
--- 
jackrabbit/oak/branches/1.22/oak-solr-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/solr/query/SolrQueryIndex.java
 (original)
+++ 
jackrabbit/oak/branches/1.22/oak-solr-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/solr/query/SolrQueryIndex.java
 Fri Jan 31 18:21:15 2020
@@ -30,6 +30,7 @@ import org.apache.jackrabbit.oak.api.Res
 import org.apache.jackrabbit.oak.commons.PathUtils;
 import org.apache.jackrabbit.oak.commons.json.JsopBuilder;
 import org.apache.jackrabbit.oak.commons.json.JsopWriter;
+import org.apache.jackrabbit.oak.plugins.index.search.FieldNames;
 import org.apache.jackrabbit.oak.plugins.index.search.util.LMSEstimator;
 import 
org.apache.jackrabbit.oak.plugins.index.solr.configuration.OakSolrConfiguration;
 import 
org.apache.jackrabbit.oak.plugins.index.solr.configuration.OakSolrConfigurationProvider;
@@ -579,12 +580,24 @@ public class SolrQueryIndex implements F
     private IndexPlan getIndexPlan(Filter filter, OakSolrConfiguration 
configuration, LMSEstimator estimator,
                                    List<OrderEntry> sortOrder, String path) {
         if (getMatchingFilterRestrictions(filter, configuration) > 0) {
+
+            // we can't order by functions
+            // so remove those entries from the plan's sort order
+            ArrayList<OrderEntry> sortOrder2 = new ArrayList<>();
+            if (sortOrder != null) {
+                for (OrderEntry e : sortOrder) {
+                    if 
(!e.getPropertyName().startsWith(FieldNames.FUNCTION_PREFIX)) {
+                        sortOrder2.add(e);
+                    }
+                }
+            }
+
             IndexPlan indexPlan = planBuilder(filter)
-                .setEstimatedEntryCount(estimator.estimate(filter))
-                .setSortOrder(sortOrder)
-                .setPlanName(path)
-                .setPathPrefix(getPathPrefix(path))
-                .build();
+                    .setEstimatedEntryCount(estimator.estimate(filter))
+                    .setSortOrder(sortOrder2)
+                    .setPlanName(path)
+                    .setPathPrefix(getPathPrefix(path))
+                    .build();
             log.debug("index plan {}", indexPlan);
             return indexPlan;
         } else {


Reply via email to