Index: oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndex.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndex.java (date 1418142375000) +++ oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndex.java (revision ) @@ -94,6 +94,7 @@ import org.slf4j.LoggerFactory; import static com.google.common.base.Preconditions.checkState; +import static com.google.common.collect.Lists.newArrayListWithCapacity; import static org.apache.jackrabbit.JcrConstants.JCR_MIXINTYPES; import static org.apache.jackrabbit.JcrConstants.JCR_PRIMARYTYPE; import static org.apache.jackrabbit.oak.api.Type.LONG; @@ -101,6 +102,7 @@ import static org.apache.jackrabbit.oak.commons.PathUtils.denotesRoot; import static org.apache.jackrabbit.oak.commons.PathUtils.getParentPath; import static org.apache.jackrabbit.oak.plugins.index.lucene.FieldNames.PATH; +import static org.apache.jackrabbit.oak.plugins.index.lucene.IndexDefinition.NATIVE_SORT_ORDER; import static org.apache.jackrabbit.oak.plugins.index.lucene.LuceneIndexConstants.VERSION; import static org.apache.jackrabbit.oak.plugins.index.lucene.TermFactory.newAncestorTerm; import static org.apache.jackrabbit.oak.plugins.index.lucene.TermFactory.newFulltextTerm; @@ -366,19 +368,37 @@ if (sortOrder == null || sortOrder.isEmpty()) { return null; } - SortField[] fields = new SortField[sortOrder.size()]; + + List fieldsList = newArrayListWithCapacity(sortOrder.size()); PlanResult planResult = pr(plan); for (int i = 0; i < sortOrder.size(); i++) { - PropertyDefinition pd = planResult.getOrderedProperty(i); OrderEntry oe = sortOrder.get(i); + if (!isDefaultSort(oe)) { + PropertyDefinition pd = planResult.getOrderedProperty(i); - boolean reverse = oe.getOrder() != OrderEntry.Order.ASCENDING; - String propName = oe.getPropertyName(); - propName = FieldNames.createDocValFieldName(propName); + boolean reverse = oe.getOrder() != OrderEntry.Order.ASCENDING; + String propName = oe.getPropertyName(); + propName = FieldNames.createDocValFieldName(propName); - fields[i] = new SortField(propName, toLuceneSortType(oe, pd), reverse); + fieldsList.add(new SortField(propName, toLuceneSortType(oe, pd), reverse)); - } + } - return new Sort(fields); - } + } + if (fieldsList.isEmpty()) { + return null; + } else { + return new Sort(fieldsList.toArray(new SortField[0])); + } + } + + /** + * Identifies the default sort order used by the index (@jcr:score descending) + * + * @param oe order entry + * @return + */ + private static boolean isDefaultSort(OrderEntry oe) { + return oe.getPropertyName().equals(NATIVE_SORT_ORDER.getPropertyName()); + } + private static SortField.Type toLuceneSortType(OrderEntry oe, PropertyDefinition defn) { Type t = oe.getPropertyType(); checkState(t != null, "Type cannot be null"); @@ -466,12 +486,14 @@ List orders = plan.getSortOrder(); for (int i = 0; i < orders.size(); i++) { OrderEntry oe = orders.get(i); + if (!isDefaultSort(oe)) { - PropertyDefinition pd = planResult.getOrderedProperty(i); - PropertyRestriction orderRest = new PropertyRestriction(); - orderRest.propertyName = oe.getPropertyName(); - Query q = createQuery(orderRest, pd); - if (q != null) { - qs.add(q); + PropertyDefinition pd = planResult.getOrderedProperty(i); + PropertyRestriction orderRest = new PropertyRestriction(); + orderRest.propertyName = oe.getPropertyName(); + Query q = createQuery(orderRest, pd); + if (q != null) { + qs.add(q); + } } } } Index: oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndexTest.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndexTest.java (date 1418142375000) +++ oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndexTest.java (revision ) @@ -32,10 +32,13 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; +import com.google.common.collect.Maps; import org.apache.jackrabbit.JcrConstants; import org.apache.jackrabbit.oak.Oak; import org.apache.jackrabbit.oak.api.CommitFailedException; import org.apache.jackrabbit.oak.api.ContentRepository; +import org.apache.jackrabbit.oak.api.PropertyValue; +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.IndexConstants; @@ -52,6 +55,7 @@ import static com.google.common.collect.ImmutableSet.of; import static java.util.Arrays.asList; +import static org.apache.jackrabbit.oak.api.QueryEngine.NO_MAPPINGS; import static org.apache.jackrabbit.oak.api.Type.STRINGS; 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; @@ -743,6 +747,64 @@ // verify results ordering // which should be /test/c (boost = 4.0), /test/a(boost = 2.0), /test/b (1.0) assertOrderedQuery(queryString, asList("/test/c", "/test/a", "/test/b"), XPATH, true); + } + + @Test + public void sortQueriesWithJcrScore() throws Exception { + Tree idx = createIndex("test1", of("propa", "n0", "n1", "n2")); + root.commit(); + + Tree test = root.getTree("/").addChild("test"); + for(int i = 3; i > 0; i--){ + Tree child = test.addChild("n" + i); + child.setProperty("propa", "foo"); + } + root.commit(); + + // Descending matches with lucene native sort + String query = + "measure select [jcr:path] from [nt:base] where [propa] = 'foo' order by [jcr:score] desc"; + assertThat(measureLimit(query, SQL2, 1), containsString("scanCount: 1")); + + // Ascending needs to be sorted by query engine + query = + "measure select [jcr:path] from [nt:base] where [propa] = 'foo' order by [jcr:score]"; + assertThat(measureLimit(query, SQL2, 1), containsString("scanCount: 3")); + } + + @Test + public void sortFulltextQueriesWithJcrScore() throws Exception { + // Index Definition + Tree idx = createIndex("test1", of("propa")); + idx.setProperty(LuceneIndexConstants.FULL_TEXT_ENABLED, true); + + // create test data + Tree test = root.getTree("/").addChild("test"); + root.commit(); + test.addChild("a").setProperty("propa", "foo"); + test.addChild("b").setProperty("propa", "foo"); + test.addChild("c").setProperty("propa", "foo"); + root.commit(); + + // Descending matches with lucene native sort + String query = "measure //*[jcr:contains(., 'foo' )] order by @jcr:score descending"; + assertThat(measureLimit(query, XPATH, 1), containsString("scanCount: 1")); + + // Ascending needs to be sorted by query engine + query = "measure //*[jcr:contains(., 'foo' )] order by @jcr:score"; + assertThat(measureLimit(query, XPATH, 1), containsString("scanCount: 3")); + } + + private String measureLimit(String query, String lang, int limit) throws ParseException { + List result = Lists.newArrayList( + qe.executeQuery(query, lang, limit, 0, Maps.newHashMap(), + NO_MAPPINGS).getRows()); + + String measure = ""; + if (result.size() > 0) { + measure = result.get(0).toString(); + } + return measure; } private void assertOrderedQuery(String sql, List paths) { Index: oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexPlanner.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexPlanner.java (date 1418142375000) +++ oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexPlanner.java (revision ) @@ -313,8 +313,12 @@ && !o.getPropertyType().isArray()) { orderEntries.add(o); //Lucene can manage any order desc/asc result.sortedProperties.add(pd); + } else if (o.getPropertyName().equals(IndexDefinition.NATIVE_SORT_ORDER.getPropertyName())) { + // Supports jcr:score descending natively + orderEntries.add(IndexDefinition.NATIVE_SORT_ORDER); } } + //TODO Should we return order entries only when all order clauses are satisfied return orderEntries; } Index: oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexDefinition.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexDefinition.java (date 1418142375000) +++ oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexDefinition.java (revision ) @@ -49,6 +49,7 @@ import org.apache.jackrabbit.oak.plugins.memory.PropertyStates; import org.apache.jackrabbit.oak.plugins.nodetype.ReadOnlyNodeTypeManager; import org.apache.jackrabbit.oak.plugins.tree.ImmutableTree; +import org.apache.jackrabbit.oak.spi.query.QueryIndex.OrderEntry; import org.apache.jackrabbit.oak.spi.state.ChildNodeEntry; import org.apache.jackrabbit.oak.spi.state.NodeBuilder; import org.apache.jackrabbit.oak.spi.state.NodeState; @@ -64,6 +65,7 @@ import static com.google.common.collect.Lists.newArrayListWithCapacity; import static com.google.common.collect.Maps.newHashMap; import static com.google.common.collect.Sets.newHashSet; +import static org.apache.jackrabbit.JcrConstants.JCR_SCORE; import static org.apache.jackrabbit.JcrConstants.NT_BASE; import static org.apache.jackrabbit.oak.api.Type.NAMES; import static org.apache.jackrabbit.oak.commons.PathUtils.isAbsolute; @@ -113,6 +115,12 @@ static final int TYPES_ALLOW_NONE = PropertyType.UNDEFINED; static final int TYPES_ALLOW_ALL = -1; + + /** + * native sort order + */ + static final OrderEntry NATIVE_SORT_ORDER = new OrderEntry(JCR_SCORE, Type.UNDEFINED, + OrderEntry.Order.DESCENDING); private final boolean fullTextEnabled; Index: oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndex.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndex.java (date 1418142375000) +++ oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndex.java (revision ) @@ -26,6 +26,7 @@ import static org.apache.jackrabbit.oak.commons.PathUtils.getName; import static org.apache.jackrabbit.oak.commons.PathUtils.getParentPath; import static org.apache.jackrabbit.oak.plugins.index.lucene.FieldNames.PATH; +import static org.apache.jackrabbit.oak.plugins.index.lucene.IndexDefinition.NATIVE_SORT_ORDER; import static org.apache.jackrabbit.oak.plugins.index.lucene.LuceneIndexConstants.VERSION; import static org.apache.jackrabbit.oak.plugins.index.lucene.TermFactory.newFulltextTerm; import static org.apache.jackrabbit.oak.plugins.index.lucene.TermFactory.newPathTerm; @@ -50,6 +51,7 @@ import com.google.common.collect.AbstractIterator; +import com.google.common.collect.ImmutableList; import com.google.common.collect.Queues; import com.google.common.collect.Sets; @@ -373,7 +375,8 @@ .setFulltextIndex(true) .setEstimatedEntryCount(0) //TODO Fake it to provide constant cost for now .setIncludesNodeData(false) // we should not include node data - .setDelayed(true); //Lucene is always async + .setDelayed(true) //Lucene is always async + .setSortOrder(ImmutableList.of(NATIVE_SORT_ORDER)); // Native index sort order } /**