Index: oak-core/src/main/java/org/apache/jackrabbit/oak/api/Result.java =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/api/Result.java (revision 1683314) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/api/Result.java (working copy) @@ -59,4 +59,43 @@ */ long getSize(); + /** + * Get the number of rows, if known. If the size is not known, -1 is + * returned. + * + * @param precision the required precision + * @param max the maximum number that should be returned (Long.MAX_VALUE for + * unlimited). For EXACT, the cost of the operation is at most + * O(max). For approximations, the cost of the operation should + * be at most O(log max). + * @return the (approximate) size. If an implementation does know the exact + * value, it returns it (even if the value is higher than max). If + * the implementation does not know the value, and the child node + * count is higher than max, it returns Long.MAX_VALUE. + */ + long getSize(SizePrecision precision, long max); + + enum SizePrecision { + + /** + * If the exact number is needed. + */ + EXACT, + + /** + * If a good, and secure estimate is needed (the actual number can be + * lower or higher). This is supposed to be faster than exact count, but + * slower than a fast approximation. + */ + APPROXIMATION, + + /** + * If a rough estimate is needed (the actual number can be lower or + * higher). This is supposed to be faster than a good approximation. It + * could be (for example) the expected cost of the query. + */ + FAST_APPROXIMATION, + + } + } Index: oak-core/src/main/java/org/apache/jackrabbit/oak/api/package-info.java =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/api/package-info.java (revision 1683314) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/api/package-info.java (working copy) @@ -18,7 +18,7 @@ /** * Oak repository API */ -@Version("1.0") +@Version("2.0") @Export(optional = "provide:=true") package org.apache.jackrabbit.oak.api; Index: oak-core/src/main/java/org/apache/jackrabbit/oak/query/Query.java =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/query/Query.java (revision 1683314) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/query/Query.java (working copy) @@ -55,7 +55,21 @@ Iterator getRows(); + /** + * Get the size if known. + * + * @return the size, or -1 if unknown + */ long getSize(); + + /** + * Get the size if known. + * + * @param precision the required precision + * @param max the maximum nodes read (for an exact size) + * @return the size, or -1 if unknown + */ + long getSize(Result.SizePrecision precision, long max); void setExplain(boolean explain); Index: oak-core/src/main/java/org/apache/jackrabbit/oak/query/QueryImpl.java =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/query/QueryImpl.java (revision 1683314) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/query/QueryImpl.java (working copy) @@ -13,6 +13,7 @@ */ package org.apache.jackrabbit.oak.query; +import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; @@ -25,6 +26,7 @@ import org.apache.jackrabbit.oak.api.PropertyValue; import org.apache.jackrabbit.oak.api.Tree; import org.apache.jackrabbit.oak.api.Type; +import org.apache.jackrabbit.oak.api.Result.SizePrecision; import org.apache.jackrabbit.oak.namepath.JcrPathParser; import org.apache.jackrabbit.oak.namepath.NamePathMapper; import org.apache.jackrabbit.oak.query.ast.AndImpl; @@ -979,6 +981,12 @@ public long getSize() { return size; } + + @Override + public long getSize(SizePrecision precision, long max) { + // Note: DISTINCT is ignored + return Math.min(limit, source.getSize(precision, max)); + } public String getStatement() { return statement; @@ -996,5 +1004,20 @@ public ExecutionContext getExecutionContext() { return context; } + + /** + * Add two values, but don't let it overflow or underflow. + * + * @param x the first value + * @param y the second value + * @return the sum, or Long.MIN_VALUE for underflow, or Long.MAX_VALUE for + * overflow + */ + public static long saturatedAdd(long x, long y) { + BigInteger min = BigInteger.valueOf(Long.MIN_VALUE); + BigInteger max = BigInteger.valueOf(Long.MAX_VALUE); + BigInteger sum = BigInteger.valueOf(x).add(BigInteger.valueOf(y)); + return sum.min(max).max(min).longValue(); + } } Index: oak-core/src/main/java/org/apache/jackrabbit/oak/query/ResultImpl.java =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/query/ResultImpl.java (revision 1683314) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/query/ResultImpl.java (working copy) @@ -80,4 +80,9 @@ return query.getSize(); } + @Override + public long getSize(SizePrecision precision, long max) { + return query.getSize(precision, max); + } + } Index: oak-core/src/main/java/org/apache/jackrabbit/oak/query/UnionQueryImpl.java =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/query/UnionQueryImpl.java (revision 1683314) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/query/UnionQueryImpl.java (working copy) @@ -22,6 +22,7 @@ import org.apache.jackrabbit.oak.api.PropertyValue; import org.apache.jackrabbit.oak.api.Result; +import org.apache.jackrabbit.oak.api.Result.SizePrecision; import org.apache.jackrabbit.oak.api.Tree; import org.apache.jackrabbit.oak.query.ast.ColumnImpl; import org.apache.jackrabbit.oak.query.ast.OrderingImpl; @@ -144,8 +145,17 @@ public long getSize() { return size; } - + @Override + public long getSize(SizePrecision precision, long max) { + // Note: this does not respect the "unionAll == false" case + // (this can result in a larger reported size, but it is not a security problem) + return QueryImpl.saturatedAdd( + left.getSize(precision, max), + right.getSize(precision, max)); + } + + @Override public void setExplain(boolean explain) { this.explain = explain; } Index: oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/JoinImpl.java =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/JoinImpl.java (revision 1683314) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/JoinImpl.java (working copy) @@ -16,6 +16,7 @@ import java.util.ArrayList; import java.util.List; +import org.apache.jackrabbit.oak.api.Result.SizePrecision; import org.apache.jackrabbit.oak.query.plan.ExecutionPlan; import org.apache.jackrabbit.oak.query.plan.JoinExecutionPlan; import org.apache.jackrabbit.oak.spi.query.Filter; @@ -266,4 +267,10 @@ return left.isOuterJoinRightHandSide() || right.isOuterJoinRightHandSide(); } + @Override + public long getSize(SizePrecision precision, long max) { + // we don't know + return -1; + } + } Index: oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/SelectorImpl.java =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/SelectorImpl.java (revision 1683314) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/SelectorImpl.java (working copy) @@ -41,6 +41,7 @@ import org.apache.jackrabbit.oak.api.PropertyState; import org.apache.jackrabbit.oak.api.PropertyValue; +import org.apache.jackrabbit.oak.api.Result.SizePrecision; import org.apache.jackrabbit.oak.api.Tree; import org.apache.jackrabbit.oak.api.Type; import org.apache.jackrabbit.oak.commons.PathUtils; @@ -752,4 +753,12 @@ return query; } + @Override + public long getSize(SizePrecision precision, long max) { + if (cursor == null) { + return -1; + } + return cursor.getSize(precision, max); + } + } Index: oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/SourceImpl.java =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/SourceImpl.java (revision 1683314) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/SourceImpl.java (working copy) @@ -21,6 +21,7 @@ import java.util.Collections; import java.util.List; +import org.apache.jackrabbit.oak.api.Result.SizePrecision; import org.apache.jackrabbit.oak.query.plan.ExecutionPlan; import org.apache.jackrabbit.oak.spi.query.Filter; import org.apache.jackrabbit.oak.spi.state.NodeState; @@ -153,4 +154,13 @@ */ public abstract boolean isOuterJoinRightHandSide(); + /** + * Get the size if known. + * + * @param precision the required precision + * @param max the maximum nodes read (for an exact size) + * @return the size, or -1 if unknown + */ + public abstract long getSize(SizePrecision precision, long max); + } Index: oak-core/src/main/java/org/apache/jackrabbit/oak/query/package-info.java =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/query/package-info.java (revision 1683314) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/query/package-info.java (working copy) @@ -14,7 +14,7 @@ * 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; Index: oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/Cursor.java =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/Cursor.java (revision 1683314) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/Cursor.java (working copy) @@ -20,6 +20,8 @@ import java.util.Iterator; +import org.apache.jackrabbit.oak.api.Result.SizePrecision; + /** * A cursor to read a number of nodes sequentially. */ @@ -50,5 +52,14 @@ */ @Override IndexRow next(); + + /** + * Get the size if known. + * + * @param precision the required precision + * @param max the maximum nodes read (for an exact size) + * @return the size, or -1 if unknown + */ + long getSize(SizePrecision precision, long max); } Index: oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/Cursors.java =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/Cursors.java (revision 1683314) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/Cursors.java (working copy) @@ -24,6 +24,7 @@ import javax.annotation.Nullable; +import org.apache.jackrabbit.oak.api.Result.SizePrecision; import org.apache.jackrabbit.oak.commons.PathUtils; import org.apache.jackrabbit.oak.plugins.memory.MemoryChildNodeEntry; import org.apache.jackrabbit.oak.query.FilterIterators; @@ -124,6 +125,11 @@ throw new UnsupportedOperationException(); } + @Override + public long getSize(SizePrecision precision, long max) { + return -1; + } + } /** Index: oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/package-info.java =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/package-info.java (revision 1683314) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/package-info.java (working copy) @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -@Version("1.0.1") +@Version("2.0.0") @Export(optional = "provide:=true") package org.apache.jackrabbit.oak.spi.query; Index: oak-core/src/test/java/org/apache/jackrabbit/oak/query/AbstractQueryTest.java =================================================================== --- oak-core/src/test/java/org/apache/jackrabbit/oak/query/AbstractQueryTest.java (revision 1683314) +++ oak-core/src/test/java/org/apache/jackrabbit/oak/query/AbstractQueryTest.java (working copy) @@ -39,6 +39,7 @@ import org.apache.jackrabbit.oak.api.PropertyValue; import org.apache.jackrabbit.oak.api.QueryEngine; import org.apache.jackrabbit.oak.api.Result; +import org.apache.jackrabbit.oak.api.Result.SizePrecision; import org.apache.jackrabbit.oak.api.ResultRow; import org.apache.jackrabbit.oak.api.Root; import org.apache.jackrabbit.oak.api.Tree; @@ -265,6 +266,23 @@ protected List assertQuery(String sql, List expected) { return assertQuery(sql, SQL2, expected); } + + protected void assertResultSize(String query, String language, long expected) { + long time = System.currentTimeMillis(); + try { + Result result = executeQuery(query, language, NO_BINDINGS); + // currently needed to iterate to really execute the query + result.getRows().iterator().hasNext(); + long got = result.getSize(SizePrecision.APPROXIMATION, 0); + assertEquals(expected, got); + } catch (ParseException e) { + throw new RuntimeException(e); + } + time = System.currentTimeMillis() - time; + if (time > 10000 && !isDebugModeEnabled()) { + fail("Query took too long: " + query + " took " + time + " ms"); + } + } protected List assertQuery(String sql, String language, List expected) { Index: oak-core/src/test/java/org/apache/jackrabbit/oak/query/UtilsTest.java =================================================================== --- oak-core/src/test/java/org/apache/jackrabbit/oak/query/UtilsTest.java (revision 0) +++ oak-core/src/test/java/org/apache/jackrabbit/oak/query/UtilsTest.java (working copy) @@ -0,0 +1,61 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.jackrabbit.oak.query; + +import static org.junit.Assert.assertEquals; + +import java.util.Random; + +import org.junit.Test; + +public class UtilsTest { + + @Test + public void saturatedAdd() { + assertEquals(3, QueryImpl.saturatedAdd(1, 2)); + assertEquals(Long.MAX_VALUE, QueryImpl.saturatedAdd(1, Long.MAX_VALUE)); + assertEquals(Long.MAX_VALUE, QueryImpl.saturatedAdd(Long.MAX_VALUE, 1)); + assertEquals(Long.MAX_VALUE, QueryImpl.saturatedAdd(Long.MAX_VALUE, Long.MAX_VALUE)); + long[] test = {Long.MIN_VALUE, Long.MIN_VALUE + 1, Long.MIN_VALUE + 10, + -1000, -10, -1, 0, 1, 3, 10000, + Long.MAX_VALUE - 20, Long.MAX_VALUE - 1, Long.MAX_VALUE}; + Random r = new Random(1); + for (int i = 0; i < 10000; i++) { + long x = r.nextBoolean() ? test[r.nextInt(test.length)] : r + .nextLong(); + long y = r.nextBoolean() ? test[r.nextInt(test.length)] : r + .nextLong(); + long alt = altSaturatedAdd(x, y); + long got = QueryImpl.saturatedAdd(x, y); + assertEquals(x + "+" + y, alt, got); + } + } + + private static long altSaturatedAdd(long x, long y) { + // see also http://stackoverflow.com/questions/2632392/saturated-addition-of-two-signed-java-long-values + if (x > 0 != y > 0) { + // different signs + return x + y; + } else if (x > 0) { + // can overflow + return Long.MAX_VALUE - x < y ? Long.MAX_VALUE : x + y; + } else { + // can underflow + return Long.MIN_VALUE - x > y ? Long.MIN_VALUE : x + y; + } + } +} Index: oak-core/src/test/java/org/apache/jackrabbit/oak/spi/query/CursorsTest.java =================================================================== --- oak-core/src/test/java/org/apache/jackrabbit/oak/spi/query/CursorsTest.java (revision 1683314) +++ oak-core/src/test/java/org/apache/jackrabbit/oak/spi/query/CursorsTest.java (working copy) @@ -24,6 +24,7 @@ import java.util.Iterator; import org.apache.jackrabbit.oak.api.PropertyValue; +import org.apache.jackrabbit.oak.api.Result.SizePrecision; import org.apache.jackrabbit.oak.query.QueryEngineSettings; import org.junit.Test; @@ -99,6 +100,11 @@ public IndexRow next() { return rows.next(); } + + @Override + public long getSize(SizePrecision precision, long max) { + return -1; + } } Index: oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/query/PrefetchIterator.java =================================================================== --- oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/query/PrefetchIterator.java (revision 1683314) +++ oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/query/PrefetchIterator.java (working copy) @@ -22,6 +22,9 @@ import java.util.Iterator; import java.util.NoSuchElementException; +import org.apache.jackrabbit.oak.api.Result; +import org.apache.jackrabbit.oak.api.Result.SizePrecision; + /** * An iterator that pre-fetches a number of items in order to calculate the size * of the result if possible. This iterator loads at least a number of items, @@ -33,7 +36,11 @@ * @param the iterator data type */ public class PrefetchIterator implements Iterator { - + + private final boolean fastSize = Boolean.getBoolean("oak.fastQuerySize"); + + private final Result result; + private final Iterator it; private final long minPrefetch, timeout, maxPrefetch; private boolean prefetchDone; @@ -48,13 +55,19 @@ * @param timeout the maximum time to pre-fetch in milliseconds * @param max the maximum number of items to pre-fetch * @param size the size (prefetching is only required if -1) + * @param result (optional) the result to get the size from */ - PrefetchIterator(Iterator it, long min, long timeout, long max, long size) { + PrefetchIterator(Iterator it, long min, long timeout, long max, + long size, Result result) { this.it = it; this.minPrefetch = min; + if (fastSize) { + timeout = 0; + } this.timeout = timeout; this.maxPrefetch = max; this.size = size; + this.result = result; } @Override @@ -97,9 +110,14 @@ * @return the size, or -1 if unknown */ public long size() { - if (size != -1 || prefetchDone || position > maxPrefetch) { + if (size != -1) { return size; } + if (!fastSize) { + if (prefetchDone || position > maxPrefetch) { + return -1; + } + } prefetchDone = true; ArrayList list = new ArrayList(); long end; @@ -126,6 +144,11 @@ prefetchIterator = list.iterator(); position -= list.size(); } + if (size == -1 && fastSize) { + if (result != null) { + size = result.getSize(SizePrecision.EXACT, Long.MAX_VALUE); + } + } return size; } Index: oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/query/QueryResultImpl.java =================================================================== --- oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/query/QueryResultImpl.java (revision 1683314) +++ oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/query/QueryResultImpl.java (working copy) @@ -137,7 +137,7 @@ final PrefetchIterator prefIt = new PrefetchIterator( sessionDelegate.sync(rowIterator), PREFETCH_MIN, PREFETCH_TIMEOUT, PREFETCH_MAX, - result.getSize()); + result.getSize(), result); return new RowIteratorAdapter(prefIt) { @Override public long getSize() { @@ -151,9 +151,8 @@ if (tree != null && tree.exists()) { NodeDelegate node = new NodeDelegate(sessionDelegate, tree); return NodeImpl.createNode(node, sessionContext); - } else { - return null; } + return null; } @Override @@ -217,8 +216,8 @@ }; final PrefetchIterator> prefIt = new PrefetchIterator>( sessionDelegate.sync(nodeIterator), - PREFETCH_MIN, PREFETCH_TIMEOUT, PREFETCH_MAX, - result.getSize()); + PREFETCH_MIN, PREFETCH_TIMEOUT, PREFETCH_MAX, + result.getSize(), result); return new NodeIteratorAdapter(prefIt) { @Override public long getSize() { Index: oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/query/PrefetchIteratorTest.java =================================================================== --- oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/query/PrefetchIteratorTest.java (revision 1683314) +++ oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/query/PrefetchIteratorTest.java (working copy) @@ -36,7 +36,7 @@ Iterable s; PrefetchIterator it; s = seq(0, 100); - it = new PrefetchIterator(s.iterator(), 5, 0, 10, 200); + it = new PrefetchIterator(s.iterator(), 5, 0, 10, 200, null); // reports the 'wrong' value as it was set manually assertEquals(200, it.size()); } @@ -49,12 +49,12 @@ // long delay (10 ms per row) long timeout = 10; s = seq(0, 100, 10); - it = new PrefetchIterator(s.iterator(), 5, timeout, 1000, -1); + it = new PrefetchIterator(s.iterator(), 5, timeout, 1000, -1, null); assertEquals(-1, it.size()); // no delay s = seq(0, 100); - it = new PrefetchIterator(s.iterator(), 5, timeout, 1000, -1); + it = new PrefetchIterator(s.iterator(), 5, timeout, 1000, -1, null); assertEquals(100, it.size()); } @@ -68,7 +68,7 @@ long timeout = size % 3 == 0 ? 100 : 0; Iterable s = seq(0, size); PrefetchIterator it = - new PrefetchIterator(s.iterator(), 20, timeout, 30, -1); + new PrefetchIterator(s.iterator(), 20, timeout, 30, -1, null); for (int x : seq(0, readBefore)) { boolean hasNext = it.hasNext(); if (!hasNext) { Index: oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndex.java =================================================================== --- oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndex.java (revision 1683314) +++ oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndex.java (working copy) @@ -56,6 +56,7 @@ import org.apache.jackrabbit.JcrConstants; import org.apache.jackrabbit.oak.api.PropertyValue; +import org.apache.jackrabbit.oak.api.Result.SizePrecision; import org.apache.jackrabbit.oak.plugins.index.aggregate.NodeAggregator; import org.apache.jackrabbit.oak.plugins.index.lucene.IndexDefinition.IndexingRule; import org.apache.jackrabbit.oak.plugins.index.lucene.util.MoreLikeThisHelper; @@ -1132,6 +1133,12 @@ }; } + + @Override + public long getSize(SizePrecision precision, long max) { + // not yet supported + return -1; + } } private static class PathStoredFieldVisitor extends StoredFieldVisitor { Index: oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndex.java =================================================================== --- oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndex.java (revision 1683314) +++ oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndex.java (working copy) @@ -38,6 +38,7 @@ import com.google.common.collect.Queues; import com.google.common.collect.Sets; import org.apache.jackrabbit.oak.api.PropertyValue; +import org.apache.jackrabbit.oak.api.Result.SizePrecision; import org.apache.jackrabbit.oak.api.Type; import org.apache.jackrabbit.oak.commons.PathUtils; import org.apache.jackrabbit.oak.plugins.index.aggregate.NodeAggregator; @@ -92,6 +93,7 @@ import org.apache.lucene.search.TermQuery; import org.apache.lucene.search.TermRangeQuery; import org.apache.lucene.search.TopDocs; +import org.apache.lucene.search.TotalHitCountCollector; import org.apache.lucene.search.WildcardQuery; import org.apache.lucene.search.spell.SuggestWord; import org.apache.lucene.search.suggest.Lookup; @@ -449,7 +451,31 @@ this.lastSearchIndexerVersion = currentVersion; } }; - return new LucenePathCursor(itr, plan, settings); + SizeEstimator sizeEstimator = new SizeEstimator() { + @Override + public long getSize() { + IndexNode indexNode = acquireIndexNode(plan); + checkState(indexNode != null); + try { + IndexSearcher searcher = indexNode.getSearcher(); + LuceneRequestFacade luceneRequestFacade = getLuceneRequest(plan, searcher.getIndexReader()); + if (luceneRequestFacade.getLuceneRequest() instanceof Query) { + Query query = (Query) luceneRequestFacade.getLuceneRequest(); + LOG.debug("estimate size for query " + query); + TotalHitCountCollector collector = new TotalHitCountCollector(); + searcher.search(query, collector); + return collector.getTotalHits(); + } + LOG.debug("estimate size: not a Query: " + luceneRequestFacade.getLuceneRequest()); + } catch (IOException e) { + LOG.warn("query via {} failed.", LucenePropertyIndex.this, e); + } finally { + indexNode.release(); + } + return -1; + } + }; + return new LucenePathCursor(itr, plan, settings, sizeEstimator); } @Override @@ -1169,9 +1195,12 @@ private final Cursor pathCursor; private final String pathPrefix; LuceneResultRow currentRow; + private final SizeEstimator sizeEstimator; + private long estimatedSize; - LucenePathCursor(final Iterator it, final IndexPlan plan, QueryEngineSettings settings) { + LucenePathCursor(final Iterator it, final IndexPlan plan, QueryEngineSettings settings, SizeEstimator sizeEstimator) { pathPrefix = plan.getPathPrefix(); + this.sizeEstimator = sizeEstimator; Iterator pathIterator = new Iterator() { @Override @@ -1234,6 +1263,15 @@ }; } + + + @Override + public long getSize(SizePrecision precision, long max) { + if (estimatedSize != 0) { + return estimatedSize; + } + return estimatedSize = sizeEstimator.getSize(); + } } private static class PathStoredFieldVisitor extends StoredFieldVisitor { Index: oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/SizeEstimator.java =================================================================== --- oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/SizeEstimator.java (revision 0) +++ oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/SizeEstimator.java (working copy) @@ -0,0 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.jackrabbit.oak.plugins.index.lucene; + +public interface SizeEstimator { + + /** + * Get the estimated size, or -1 if not known. + * + * @return the size + */ + long getSize(); + +} Index: oak-lucene/src/test/java/org/apache/jackrabbit/oak/jcr/query/ResultSizeTest.java =================================================================== --- oak-lucene/src/test/java/org/apache/jackrabbit/oak/jcr/query/ResultSizeTest.java (revision 0) +++ oak-lucene/src/test/java/org/apache/jackrabbit/oak/jcr/query/ResultSizeTest.java (working copy) @@ -0,0 +1,62 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.jackrabbit.oak.jcr.query; + +import javax.jcr.Node; +import javax.jcr.Session; +import javax.jcr.query.Query; +import javax.jcr.query.QueryManager; + +import org.apache.jackrabbit.core.query.AbstractQueryTest; + +public class ResultSizeTest extends AbstractQueryTest { + + public void testResultSize() throws Exception { + Session session = superuser; + QueryManager qm = session.getWorkspace().getQueryManager(); + for (int i = 0; i < 200; i++) { + Node n = testRootNode.addNode("node" + i); + n.setProperty("text", "Hello World"); + } + session.save(); + + String xpath = "/jcr:root//*[jcr:contains(@text, 'Hello World')]"; + + Query q; + long result; + + // fast (insecure) case + System.setProperty("oak.fastQuerySize", "true"); + q = qm.createQuery(xpath, "xpath"); + result = q.execute().getRows().getSize(); + assertTrue("size: " + result, result > 150 && result < 250); + + q = qm.createQuery(xpath, "xpath"); + q.setLimit(90); + assertEquals(90, q.execute().getRows().getSize()); + + // default (secure) case + System.clearProperty("oak.fastQuerySize"); + q = qm.createQuery(xpath, "xpath"); + result = q.execute().getRows().getSize(); + assertEquals(-1, q.execute().getRows().getSize()); + + } + +} \ No newline at end of file Index: oak-solr-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/solr/query/SolrQueryIndex.java =================================================================== --- oak-solr-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/solr/query/SolrQueryIndex.java (revision 1683314) +++ oak-solr-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/solr/query/SolrQueryIndex.java (working copy) @@ -32,6 +32,7 @@ import com.google.common.collect.Queues; import com.google.common.collect.Sets; import org.apache.jackrabbit.oak.api.PropertyValue; +import org.apache.jackrabbit.oak.api.Result.SizePrecision; import org.apache.jackrabbit.oak.plugins.index.aggregate.NodeAggregator; import org.apache.jackrabbit.oak.plugins.index.solr.configuration.OakSolrConfiguration; import org.apache.jackrabbit.oak.query.QueryEngineSettings; @@ -541,6 +542,12 @@ }; } + + + @Override + public long getSize(SizePrecision precision, long max) { + return -1; + } } @Override