Index: src/test/java/org/apache/jackrabbit/core/query/LimitAndOffsetTest.java =================================================================== --- src/test/java/org/apache/jackrabbit/core/query/LimitAndOffsetTest.java (revision 0) +++ src/test/java/org/apache/jackrabbit/core/query/LimitAndOffsetTest.java (revision 0) @@ -0,0 +1,91 @@ +package org.apache.jackrabbit.core.query; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; +import javax.jcr.query.InvalidQueryException; +import javax.jcr.query.Query; +import javax.jcr.query.QueryManager; +import javax.jcr.query.QueryResult; + +import org.apache.jackrabbit.api.JackrabbitQuery; + +public class LimitAndOffsetTest extends AbstractQueryTest { + + private Node node1; + private Node node2; + private Node node3; + + private JackrabbitQuery query; + + protected void setUp() throws Exception { + super.setUp(); + + node1 = testRootNode.addNode("foo"); + node1.setProperty("name", "1"); + node2 = testRootNode.addNode("foo"); + node2.setProperty("name", "2"); + node3 = testRootNode.addNode("foo"); + node3.setProperty("name", "3"); + + testRootNode.save(); + + query = createXPathQuery("/jcr:root" + testRoot + "/* order by @name"); + } + + private JackrabbitQuery createXPathQuery(String xpath) + throws InvalidQueryException, RepositoryException { + QueryManager queryManager = superuser.getWorkspace().getQueryManager(); + return (JackrabbitQuery) queryManager.createQuery(xpath, Query.XPATH); + } + + public void testLimit() throws Exception { + query.setLimit(1); + QueryResult result = query.execute(); + checkResult(result, new Node[] { node1 }); + + query.setLimit(2); + result = query.execute(); + checkResult(result, new Node[] { node1, node2 }); + + query.setLimit(3); + result = query.execute(); + checkResult(result, new Node[] { node1, node2, node3 }); + } + + public void testOffset() throws Exception { + query.setOffset(0); + QueryResult result = query.execute(); + checkResult(result, new Node[] { node1, node2, node3 }); + + query.setOffset(1); + result = query.execute(); + checkResult(result, new Node[] { node2, node3 }); + + query.setOffset(2); + result = query.execute(); + checkResult(result, new Node[] { node3 }); + } + + public void testOffsetAndLimit() throws Exception { + query.setOffset(0); + query.setLimit(1); + QueryResult result = query.execute(); + checkResult(result, new Node[] { node1 }); + + query.setOffset(1); + query.setLimit(1); + result = query.execute(); + checkResult(result, new Node[] { node2 }); + + query.setOffset(1); + query.setLimit(2); + result = query.execute(); + checkResult(result, new Node[] { node2, node3 }); + + query.setOffset(0); + query.setLimit(2); + result = query.execute(); + checkResult(result, new Node[] { node1, node2 }); + } + +} Index: src/main/java/org/apache/jackrabbit/core/query/ExecutableQuery.java =================================================================== --- src/main/java/org/apache/jackrabbit/core/query/ExecutableQuery.java (revision 562512) +++ src/main/java/org/apache/jackrabbit/core/query/ExecutableQuery.java (working copy) @@ -28,9 +28,11 @@ /** * Executes this query and returns a {@link QueryResult}. + * @param offset the offset in the total result set + * @param limit the maximum result size * * @return a QueryResult * @throws RepositoryException if an error occurs */ - QueryResult execute() throws RepositoryException; + QueryResult execute(long offset, long limit) throws RepositoryException; } Index: src/main/java/org/apache/jackrabbit/core/query/AbstractQueryImpl.java =================================================================== --- src/main/java/org/apache/jackrabbit/core/query/AbstractQueryImpl.java (revision 562512) +++ src/main/java/org/apache/jackrabbit/core/query/AbstractQueryImpl.java (working copy) @@ -16,10 +16,10 @@ */ package org.apache.jackrabbit.core.query; +import org.apache.jackrabbit.api.JackrabbitQuery; import org.apache.jackrabbit.core.SessionImpl; import org.apache.jackrabbit.core.ItemManager; -import javax.jcr.query.Query; import javax.jcr.query.InvalidQueryException; import javax.jcr.Node; import javax.jcr.RepositoryException; @@ -27,7 +27,7 @@ /** * Defines common initialization methods for all query implementations. */ -public abstract class AbstractQueryImpl implements Query { +public abstract class AbstractQueryImpl implements JackrabbitQuery { /** * Initializes a query instance from a query string. Index: src/main/java/org/apache/jackrabbit/core/query/lucene/QueryImpl.java =================================================================== --- src/main/java/org/apache/jackrabbit/core/query/lucene/QueryImpl.java (revision 562512) +++ src/main/java/org/apache/jackrabbit/core/query/lucene/QueryImpl.java (working copy) @@ -126,10 +126,12 @@ /** * Executes this query and returns a {@link QueryResult}. * + * @param offset the offset in the total result set + * @param limit the maximum result size * @return a QueryResult * @throws RepositoryException if an error occurs */ - public QueryResult execute() throws RepositoryException { + public QueryResult execute(long offset, long limit) throws RepositoryException { if (log.isDebugEnabled()) { log.debug("Executing query: \n" + root.dump()); } @@ -163,7 +165,7 @@ return new LazyQueryResultImpl(index, itemMgr, session.getNamespaceResolver(), session.getAccessManager(), this, query, getSelectProperties(), - orderProperties, ascSpecs, documentOrder); + orderProperties, ascSpecs, documentOrder, offset, limit); } /** Index: src/main/java/org/apache/jackrabbit/core/query/lucene/LazyQueryResultImpl.java =================================================================== --- src/main/java/org/apache/jackrabbit/core/query/lucene/LazyQueryResultImpl.java (revision 562512) +++ src/main/java/org/apache/jackrabbit/core/query/lucene/LazyQueryResultImpl.java (working copy) @@ -125,6 +125,16 @@ private ExcerptProvider excerptProvider; /** + * The offset in the total result set + */ + private final long offset; + + /** + * The maximum size of this result if limit > 0 + */ + private final long limit; + + /** * Creates a new query result. * * @param index the search index where the query is executed. @@ -141,6 +151,8 @@ * @param orderSpecs the order specs, one for each order property name. * @param documentOrder if true the result is returned in * document order. + * @param limit the maximum result size + * @param offset the offset in the total result set */ public LazyQueryResultImpl(SearchIndex index, ItemManager itemMgr, @@ -151,7 +163,9 @@ QName[] selectProps, QName[] orderProps, boolean[] orderSpecs, - boolean documentOrder) throws RepositoryException { + boolean documentOrder, + long offset, + long limit) throws RepositoryException { this.index = index; this.itemMgr = itemMgr; this.resolver = resolver; @@ -162,6 +176,8 @@ this.orderProps = orderProps; this.orderSpecs = orderSpecs; this.docOrder = orderProps.length == 0 && documentOrder; + this.offset = offset; + this.limit = limit; // if document order is requested get all results right away getResults(docOrder ? Integer.MAX_VALUE : index.getResultFetchSize()); } @@ -242,11 +258,19 @@ * @throws RepositoryException if an error occurs while executing the * query. */ - private void getResults(int size) throws RepositoryException { + private void getResults(long size) throws RepositoryException { if (log.isDebugEnabled()) { log.debug("getResults(" + size + ")"); } - if (resultNodes.size() >= size) { + + long maxResultSize = size; + + // is there any limit? + if (limit > 0) { + maxResultSize = limit; + } + + if (resultNodes.size() >= maxResultSize) { // we already have them all return; } @@ -263,12 +287,16 @@ int start = resultNodes.size() + invalid; int max = Math.min(result.length(), numResults); - for (int i = start; i < max && resultNodes.size() < size; i++) { + for (int i = start; i < max && resultNodes.size() < maxResultSize; i++) { NodeId id = NodeId.valueOf(result.doc(i).get(FieldNames.UUID)); // check access try { if (accessMgr.isGranted(id, AccessManager.READ)) { - resultNodes.add(new ScoreNode(id, result.score(i))); + if (i < offset) { + invalid++; + } else { + resultNodes.add(new ScoreNode(id, result.score(i))); + } } else { invalid++; } Index: src/main/java/org/apache/jackrabbit/core/query/QueryImpl.java =================================================================== --- src/main/java/org/apache/jackrabbit/core/query/QueryImpl.java (revision 562512) +++ src/main/java/org/apache/jackrabbit/core/query/QueryImpl.java (working copy) @@ -89,6 +89,16 @@ private boolean initialized = false; /** + * The maximum result size + */ + private long limit; + + /** + * The offset in the total result set + */ + private long offset; + + /** * @inheritDoc */ public void init(SessionImpl session, @@ -136,7 +146,7 @@ public QueryResult execute() throws RepositoryException { checkInitialized(); long time = System.currentTimeMillis(); - QueryResult result = query.execute(); + QueryResult result = query.execute(offset, limit); if (log.isDebugEnabled()) { time = System.currentTimeMillis() - time; NumberFormat format = NumberFormat.getNumberInstance(); @@ -207,7 +217,25 @@ throw new RepositoryException(e.getMessage(), e); } } + + /** + * Sets the maximum size of the result set. + * + * @param limit new maximum size of the result set + */ + public void setLimit(long limit) { + this.limit = limit; + } + /** + * Sets the start offset of the result set. + * + * @param offset new start offset of the result set + */ + public void setOffset(long offset) { + this.offset = offset; + } + //-----------------------------< internal >--------------------------------- /**