Index: oak-core/src/main/java/org/apache/jackrabbit/oak/query/Query.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/query/Query.java (date 1433224669000) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/query/Query.java (date 1433224740000) @@ -95,4 +95,11 @@ boolean isMeasureOrExplainEnabled(); void setInternal(boolean internal); + + /** + * Returns whether the results will be sorted by index. The query must already be prepared. + * + * @return if sorted by index + */ + boolean isSortableIndex(); } Index: oak-core/src/main/java/org/apache/jackrabbit/oak/query/QueryImpl.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/query/QueryImpl.java (date 1433224669000) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/query/QueryImpl.java (date 1433224740000) @@ -150,6 +150,8 @@ private boolean prepared; private ExecutionContext context; + private boolean isSortableIndex; + private final NamePathMapper namePathMapper; private double estimatedCost; @@ -474,6 +476,44 @@ } final RowIterator rowIt = new RowIterator(context.getBaseState()); Comparator orderBy; + if (isSortableIndex) { + orderBy = null; + } else { + orderBy = ResultRowImpl.getComparator(orderings); + } + Iterator it = + FilterIterators.newCombinedFilter(rowIt, distinct, limit, offset, orderBy, settings); + if (measure) { + // return the measuring iterator delegating the readCounts to the rowIterator + it = new MeasuringIterator(this, it) { + @Override + protected void setColumns(ColumnImpl[] col) { + columns = col; + } + + @Override + protected long getReadCount() { + return rowIt.getReadCount(); + } + + @Override + protected Map getSelectorScanCount() { + Map selectorReadCounts = Maps.newHashMap(); + for (SelectorImpl selector : selectors) { + selectorReadCounts.put(selector.getSelectorName(), selector.getScanCount()); + } + return selectorReadCounts; + } + }; + } + return it; + } + + public boolean isSortableIndex() { + return isSortableIndex; + } + + private boolean isSortUsingIndex() { boolean sortUsingIndex = false; // TODO add issue about order by optimization for multiple selectors if (orderings != null && selectors.size() == 1) { @@ -508,40 +548,10 @@ } } } - if (sortUsingIndex) { - orderBy = null; - } else { - orderBy = ResultRowImpl.getComparator(orderings); + return sortUsingIndex; - } + } - Iterator it = - FilterIterators.newCombinedFilter(rowIt, distinct, limit, offset, orderBy, settings); - if (measure) { - // return the measuring iterator delegating the readCounts to the rowIterator - it = new MeasuringIterator(this, it) { - @Override - protected void setColumns(ColumnImpl[] col) { - columns = col; - } - @Override + @Override - protected long getReadCount() { - return rowIt.getReadCount(); - } - - @Override - protected Map getSelectorScanCount() { - Map selectorReadCounts = Maps.newHashMap(); - for (SelectorImpl selector : selectors) { - selectorReadCounts.put(selector.getSelectorName(), selector.getScanCount()); - } - return selectorReadCounts; - } - }; - } - return it; - } - - @Override public String getPlan() { return source.getPlan(context.getBaseState()); } @@ -563,6 +573,7 @@ if (sources.size() <= 1) { // simple case (no join) estimatedCost = source.prepare().getEstimatedCost(); + isSortableIndex = isSortUsingIndex(); return; } @@ -596,7 +607,7 @@ } estimatedCost = result.prepare().getEstimatedCost(); source = result; - + isSortableIndex = isSortUsingIndex(); } private static SourceImpl buildJoin(SourceImpl result, SourceImpl last, List conditions) { Index: oak-core/src/main/java/org/apache/jackrabbit/oak/query/UnionQueryImpl.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/query/UnionQueryImpl.java (date 1433224669000) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/query/UnionQueryImpl.java (date 1433224740000) @@ -21,6 +21,7 @@ import java.util.List; import java.util.Map; +import com.google.common.collect.ImmutableList; import com.google.common.collect.Maps; import org.apache.jackrabbit.oak.api.PropertyValue; @@ -260,9 +261,15 @@ leftIter = ((MeasuringIterator) leftRows).getDelegate(); rightIter = ((MeasuringIterator) rightRows).getDelegate(); } - + // Since sorted by index use a merge iterator + if (isSortableIndex()) { - it = FilterIterators + it = FilterIterators + .newCombinedFilter(Iterators.mergeSorted(ImmutableList.of(leftIter, rightIter), orderBy), distinct, + limit, offset, null, settings); + } else { + it = FilterIterators .newCombinedFilter(Iterators.concat(leftIter, rightIter), distinct, limit, offset, orderBy, settings); + } if (measure) { // return the measuring iterator for the union @@ -306,5 +313,10 @@ @Override public void setInternal(boolean isInternal) { this.isInternal = isInternal; + } + + @Override + public boolean isSortableIndex() { + return left.isSortableIndex() && right.isSortableIndex(); } } Index: oak-core/src/main/java/org/apache/jackrabbit/oak/query/package-info.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/query/package-info.java (date 1433224669000) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/query/package-info.java (date 1433224740000) @@ -14,9 +14,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -@Version("1.0") +@Version("2.0") @Export(optional = "provide:=true") package org.apache.jackrabbit.oak.query; import aQute.bnd.annotation.Version; -import aQute.bnd.annotation.Export; \ No newline at end of file +import aQute.bnd.annotation.Export; 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 1433224669000) +++ oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndexTest.java (date 1433224740000) @@ -399,8 +399,7 @@ String luceneQuery = explain.substring(0, explain.indexOf('\n')); assertEquals("[nt:unstructured] as [content] /* lucene:test1(/oak:index/test1) " + "+(tags:Products:A tags:Products:A/B) " + - "+(tags:DocTypes:A tags:DocTypes:B tags:DocTypes:C tags:ProblemType:A)", - luceneQuery); + "+(tags:DocTypes:A tags:DocTypes:B tags:DocTypes:C tags:ProblemType:A)", luceneQuery); } @Test @@ -522,7 +521,8 @@ assertQuery("select [jcr:path] from [nt:base] where [propa] = 20", asList("/test/b")); assertQuery("select [jcr:path] from [nt:base] where [propa] <= 20", asList("/test/b", "/test/a")); assertQuery("select [jcr:path] from [nt:base] where [propa] < 20", asList("/test/a")); - assertQuery("select [jcr:path] from [nt:base] where [propa] = 20 or [propa] = 10 ", asList("/test/b", "/test/a")); + assertQuery("select [jcr:path] from [nt:base] where [propa] = 20 or [propa] = 10 ", + asList("/test/b", "/test/a")); assertQuery("select [jcr:path] from [nt:base] where [propa] > 10 and [propa] < 30", asList("/test/b")); assertQuery("select [jcr:path] from [nt:base] where [propa] in (10,20)", asList("/test/b", "/test/a")); assertQuery("select [jcr:path] from [nt:base] where propa is not null", asList("/test/a", "/test/b", "/test/c")); @@ -665,8 +665,10 @@ test.addChild("d").setProperty("propb", "foo"); root.commit(); - assertQuery("select [jcr:path] from [nt:base] where [propa] >= " + dt("15/02/2014"), asList("/test/b", "/test/c")); - assertQuery("select [jcr:path] from [nt:base] where [propa] <=" + dt("15/03/2014"), asList("/test/b", "/test/a")); + assertQuery("select [jcr:path] from [nt:base] where [propa] >= " + dt("15/02/2014"), + asList("/test/b", "/test/c")); + assertQuery("select [jcr:path] from [nt:base] where [propa] <=" + dt("15/03/2014"), + asList("/test/b", "/test/a")); assertQuery("select [jcr:path] from [nt:base] where [propa] < " + dt("14/03/2014"), asList("/test/a")); assertQuery("select [jcr:path] from [nt:base] where [propa] > "+ dt("15/02/2014") + " and [propa] < " + dt("13/04/2014"), asList("/test/b")); assertQuery("select [jcr:path] from [nt:base] where propa is not null", asList("/test/a", "/test/b", "/test/c")); @@ -684,10 +686,8 @@ test.addChild("c").setProperty("propa", "humpy"); root.commit(); - assertQuery("select [jcr:path] from [nt:base] where propa like 'hum%'", - asList("/test/a", "/test/c")); - assertQuery("select [jcr:path] from [nt:base] where propa like '%ty'", - asList("/test/a", "/test/b")); + assertQuery("select [jcr:path] from [nt:base] where propa like 'hum%'", asList("/test/a", "/test/c")); + assertQuery("select [jcr:path] from [nt:base] where propa like '%ty'", asList("/test/a", "/test/b")); assertQuery("select [jcr:path] from [nt:base] where propa like '%ump%'", asList("/test/a", "/test/b", "/test/c")); } @@ -751,7 +751,7 @@ root.commit(); assertQuery("select [jcr:path] from [nt:base] as s where ISDESCENDANTNODE(s, '/test/test2') and propa = 'a'", - asList("/test/test2/test3/a")); + asList("/test/test2/test3/a")); } @Test public void sortQueriesWithLong() throws Exception { @@ -817,7 +817,8 @@ void assertSortedLong() throws CommitFailedException { List tuples = createDataForLongProp(); assertOrderedQuery("select [jcr:path] from [nt:base] where [bar] = 'baz' order by [foo]", getSortedPaths(tuples, OrderDirection.ASC)); - assertOrderedQuery("select [jcr:path] from [nt:base] where [bar] = 'baz' order by [foo] DESC", getSortedPaths(tuples, OrderDirection.DESC)); + assertOrderedQuery("select [jcr:path] from [nt:base] where [bar] = 'baz' order by [foo] DESC", + getSortedPaths(tuples, OrderDirection.DESC)); } private List createDataForLongProp() throws CommitFailedException { @@ -868,10 +869,10 @@ } root.commit(); - assertOrderedQuery("select [jcr:path] from [nt:base] where [bar] = 'baz' order by [foo]", getSortedPaths(tuples, OrderDirection.ASC)); - assertOrderedQuery( - "select [jcr:path] from [nt:base] where [bar] = 'baz' order by [foo] DESC", + assertOrderedQuery("select [jcr:path] from [nt:base] where [bar] = 'baz' order by [foo]", + getSortedPaths(tuples, OrderDirection.ASC)); + assertOrderedQuery("select [jcr:path] from [nt:base] where [bar] = 'baz' order by [foo] DESC", - getSortedPaths(tuples, OrderDirection.DESC)); + getSortedPaths(tuples, OrderDirection.DESC)); } @Test @@ -906,10 +907,10 @@ } root.commit(); - assertOrderedQuery("select [jcr:path] from [nt:base] where [bar] = 'baz' order by [foo]", getSortedPaths(tuples, OrderDirection.ASC)); - assertOrderedQuery( - "select [jcr:path] from [nt:base] where [bar] = 'baz' order by [foo] DESC", + assertOrderedQuery("select [jcr:path] from [nt:base] where [bar] = 'baz' order by [foo]", + getSortedPaths(tuples, OrderDirection.ASC)); + assertOrderedQuery("select [jcr:path] from [nt:base] where [bar] = 'baz' order by [foo] DESC", - getSortedPaths(tuples, OrderDirection.DESC)); + getSortedPaths(tuples, OrderDirection.DESC)); } @Test @@ -978,14 +979,13 @@ root.commit(); // Add the path of property added as timestamp string in the sorted list - assertOrderedQuery("select [jcr:path] from [nt:base] where [bar] = 'baz' order by [foo]", - Lists.newArrayList(Iterables.concat(Lists.newArrayList("/test/n0"), - getSortedPaths(tuples, OrderDirection.ASC)))); + assertOrderedQuery("select [jcr:path] from [nt:base] where [bar] = 'baz' order by [foo]", Lists.newArrayList( + Iterables.concat(Lists.newArrayList("/test/n0"), getSortedPaths(tuples, OrderDirection.ASC)))); // Append the path of property added as timestamp string to the sorted list assertOrderedQuery( "select [jcr:path] from [nt:base] where [bar] = 'baz' order by [foo] DESC", Lists - .newArrayList(Iterables.concat(getSortedPaths(tuples, OrderDirection.DESC), - Lists.newArrayList("/test/n0")))); + .newArrayList(Iterables + .concat(getSortedPaths(tuples, OrderDirection.DESC), Lists.newArrayList("/test/n0")))); } @Test @@ -1395,7 +1395,53 @@ // scan count scans the whole result set String query = "measure /jcr:root//element(*, nt:base)[(@propa = 'fooa' or @propb = 'foob')] order by @propc"; - assertThat(measureWithLimit(query, XPATH, 100), containsString("scanCount: 200")); + assertThat(measureWithLimit(query, XPATH, 100), containsString("scanCount: 101")); + } + + + @Test + public void unionSortQueries() throws Exception { + // Index Definition + Tree idx = createIndex("test1", of("propa", "propb", "propc", "propd")); + idx.setProperty(createProperty(ORDERED_PROP_NAMES, of("propd"), STRINGS)); + useV2(idx); + + // create test data + Tree test = root.getTree("/").addChild("test"); + root.commit(); + + int seed = -3; + for (int i = 0; i < 5; i++) { + Tree a = test.addChild("a" + i); + a.setProperty("propa", "a" + i); + seed += 3; + a.setProperty("propd", seed); + } + + seed = -2; + for (int i = 0; i < 5; i++) { + Tree a = test.addChild("b" + i); + a.setProperty("propb", "b" + i); + seed += 3; + a.setProperty("propd", seed); + } + seed = -1; + for (int i = 0; i < 5; i++) { + Tree a = test.addChild("c" + i); + a.setProperty("propc", "c" + i); + seed += 3; + a.setProperty("propd", seed); + } + root.commit(); + + assertQuery( + "/jcr:root//element(*, nt:base)[(@propa = 'a4' or @propb = 'b3')] order by @propd", + XPATH, + asList("/test/b3", "/test/a4")); + assertQuery( + "/jcr:root//element(*, nt:base)[(@propa = 'a3' or @propb = 'b0' or @propc = 'c2')] order by @propd", + XPATH, + asList("/test/b0", "/test/c2", "/test/a3")); } private Tree createFileNode(Tree tree, String name, String content, String mimeType){