jackrabbit-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From alexparvule...@apache.org
Subject svn commit: r1161475 - in /jackrabbit/trunk/jackrabbit-core/src: main/java/org/apache/jackrabbit/core/query/ main/java/org/apache/jackrabbit/core/query/lucene/ main/java/org/apache/jackrabbit/core/query/lucene/join/ main/java/org/apache/jackrabbit/core...
Date Thu, 25 Aug 2011 10:00:14 GMT
Author: alexparvulescu
Date: Thu Aug 25 10:00:13 2011
New Revision: 1161475

URL: http://svn.apache.org/viewvc?rev=1161475&view=rev
Log:
JCR-2959 SQL2 QueryEngine should use Lucene for sort
 - work in progress

Added:
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/sort/
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/sort/AbstractFieldComparator.java   (with props)
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/sort/DynamicOperandFieldComparator.java   (with props)
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/sort/DynamicOperandFieldComparatorSource.java   (with props)
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/sort/RowComparator.java   (with props)
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/sort/ValueComparableWrapper.java   (with props)
    jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/SQL2OffsetLimitTest.java   (with props)
    jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/SQL2OrderByTest.java   (with props)
Modified:
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/QueryImpl.java
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/QueryObjectModelImpl.java
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/FieldComparatorBase.java
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/JackrabbitIndexSearcher.java
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/LuceneQueryFactory.java
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/SharedFieldComparatorSource.java
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/SortedLuceneQueryHits.java
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/QueryEngine.java
    jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/TestAll.java

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/QueryImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/QueryImpl.java?rev=1161475&r1=1161474&r2=1161475&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/QueryImpl.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/QueryImpl.java Thu Aug 25 10:00:13 2011
@@ -20,8 +20,6 @@ import static org.apache.jackrabbit.spi.
 import static org.apache.jackrabbit.spi.commons.name.NameConstants.JCR_STATEMENT;
 import static org.apache.jackrabbit.spi.commons.name.NameConstants.NT_QUERY;
 
-import java.text.NumberFormat;
-
 import javax.jcr.ItemExistsException;
 import javax.jcr.ItemNotFoundException;
 import javax.jcr.Node;
@@ -135,14 +133,10 @@ public class QueryImpl extends AbstractQ
                         return "query.execute(" + statement + ")";
                     }
                 });
-        
+
         if (log.isDebugEnabled()) {
             time = System.currentTimeMillis() - time;
-            NumberFormat format = NumberFormat.getNumberInstance();
-            format.setMinimumFractionDigits(2);
-            format.setMaximumFractionDigits(2);
-            String seconds = format.format((double) time / 1000);
-            log.debug("executed in " + seconds + " s. (" + statement + ")");
+            log.debug("executed in {} ms. ({})", time, statement);
         }
         return result;
     }

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/QueryObjectModelImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/QueryObjectModelImpl.java?rev=1161475&r1=1161474&r2=1161475&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/QueryObjectModelImpl.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/QueryObjectModelImpl.java Thu Aug 25 10:00:13 2011
@@ -16,7 +16,6 @@
  */
 package org.apache.jackrabbit.core.query;
 
-import java.text.NumberFormat;
 import java.util.HashMap;
 import java.util.Map;
 
@@ -124,11 +123,7 @@ public class QueryObjectModelImpl extend
                 getConstraint(), getOrderings(), offset, limit);
         if (log.isDebugEnabled()) {
             time = System.currentTimeMillis() - time;
-            NumberFormat format = NumberFormat.getNumberInstance();
-            format.setMinimumFractionDigits(2);
-            format.setMaximumFractionDigits(2);
-            String seconds = format.format((double) time / 1000);
-            log.debug("executed in " + seconds + " s. (" + statement + ")");
+            log.debug("executed in {} ms. ({})", time, statement);
         }
         return qr;
     }

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/FieldComparatorBase.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/FieldComparatorBase.java?rev=1161475&r1=1161474&r2=1161475&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/FieldComparatorBase.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/FieldComparatorBase.java Thu Aug 25 10:00:13 2011
@@ -25,7 +25,7 @@ import java.io.IOException;
  * Abstract base class for <code>FieldComparator</code> implementations
  * which are based on values in the form of <code>Comparables</code>.
  */
-abstract class FieldComparatorBase extends FieldComparator {
+abstract public class FieldComparatorBase extends FieldComparator {
 
     /**
      * The bottom value.

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/JackrabbitIndexSearcher.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/JackrabbitIndexSearcher.java?rev=1161475&r1=1161474&r2=1161475&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/JackrabbitIndexSearcher.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/JackrabbitIndexSearcher.java Thu Aug 25 10:00:13 2011
@@ -106,8 +106,8 @@ public class JackrabbitIndexSearcher
             if (sort.getSort().length == 0) {
                 hits = new LuceneQueryHits(reader, this, query);
             } else {
-                hits = new SortedLuceneQueryHits(
-                        reader, this, query, sort, resultFetchHint);
+                hits = new SortedLuceneQueryHits(this, query, sort,
+                        resultFetchHint);
             }
         }
         return hits;

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/LuceneQueryFactory.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/LuceneQueryFactory.java?rev=1161475&r1=1161474&r2=1161475&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/LuceneQueryFactory.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/LuceneQueryFactory.java Thu Aug 25 10:00:13 2011
@@ -107,6 +107,7 @@ import org.apache.lucene.queryParser.Que
 import org.apache.lucene.search.BooleanClause.Occur;
 import org.apache.lucene.search.BooleanQuery;
 import org.apache.lucene.search.Query;
+import org.apache.lucene.search.Sort;
 
 /**
  * Factory that creates Lucene queries from QOM elements.
@@ -166,10 +167,30 @@ public class LuceneQueryFactory {
         this.primaryTypeField = nsMappings.translateName(JCR_PRIMARYTYPE);
     }
 
-    public List<Row> execute(
-            Map<String, PropertyValue> columns, Selector selector,
-            Constraint constraint) throws RepositoryException, IOException {
+    /**
+     * @param columns
+     * @param selector
+     * @param constraint
+     * @param externalSort
+     *            if <code>true</code> it means that the lqf should just let the
+     *            QueryEngine take care of sorting and applying applying offset
+     *            and limit constraints
+     * @param offsetIn
+     *            used in pagination
+     * @param limitIn
+     *            used in pagination
+     * @return a list of rows
+     * @throws RepositoryException
+     * @throws IOException
+     */
+    public List<Row> execute(Map<String, PropertyValue> columns,
+            Selector selector, Constraint constraint, Sort sort,
+            boolean externalSort, long offsetIn, long limitIn)
+            throws RepositoryException, IOException {
         final IndexReader reader = index.getIndexReader(true);
+        final int offset = offsetIn < 0 ? 0 : (int) offsetIn;
+        final int limit = limitIn < 0 ? Integer.MAX_VALUE : (int) limitIn;
+
         QueryHits hits = null;
         try {
             JackrabbitIndexSearcher searcher = new JackrabbitIndexSearcher(
@@ -192,25 +213,47 @@ public class LuceneQueryFactory {
             }
 
             List<Row> rows = new ArrayList<Row>();
-            hits = searcher.evaluate(qp.mainQuery);
+
+            // TODO depending on the filters, we could push the offset info
+            // into the searcher
+            hits = searcher.evaluate(qp.mainQuery, sort, offset + limit);
+            int currentNode = 0;
+            int addedNodes = 0;
+
             ScoreNode node = hits.nextScoreNode();
             while (node != null) {
+                Row row = null;
                 try {
-                    Row row = new SelectorRow(
-                            columns, evaluator, selector.getSelectorName(),
+                    row = new SelectorRow(columns, evaluator,
+                            selector.getSelectorName(),
                             session.getNodeById(node.getNodeId()),
                             node.getScore());
-                    if (filter.evaluate(row)) {
-                        rows.add(row);
-                    }
                 } catch (ItemNotFoundException e) {
                     // skip the node
                 }
+                if (row != null && filter.evaluate(row)) {
+                    if (externalSort) {
+                        // return everything and not worry about sort
+                        rows.add(row);
+                    } else {
+                        // apply limit and offset rules locally
+                        if (currentNode >= offset
+                                && currentNode - offset < limit) {
+                            rows.add(row);
+                            addedNodes++;
+                        }
+                        currentNode++;
+                        // end the loop when going over the limit
+                        if (addedNodes == limit) {
+                            break;
+                        }
+                    }
+                }
                 node = hits.nextScoreNode();
             }
             return rows;
         } finally {
-            if(hits != null){
+            if (hits != null) {
                 hits.close();
             }
             Util.closeOrRelease(reader);

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/SharedFieldComparatorSource.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/SharedFieldComparatorSource.java?rev=1161475&r1=1161474&r2=1161475&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/SharedFieldComparatorSource.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/SharedFieldComparatorSource.java Thu Aug 25 10:00:13 2011
@@ -20,6 +20,7 @@ package org.apache.jackrabbit.core.query
 import org.apache.jackrabbit.core.HierarchyManager;
 import org.apache.jackrabbit.core.id.NodeId;
 import org.apache.jackrabbit.core.id.PropertyId;
+import org.apache.jackrabbit.core.query.lucene.sort.AbstractFieldComparator;
 import org.apache.jackrabbit.core.state.ItemStateManager;
 import org.apache.jackrabbit.core.state.PropertyState;
 import org.apache.jackrabbit.core.value.InternalValue;
@@ -34,8 +35,6 @@ import org.apache.lucene.search.FieldCom
 import org.apache.lucene.search.FieldComparatorSource;
 
 import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
 
 /**
  * Implements a <code>FieldComparatorSource</code> for <code>FieldComparator</code>s which
@@ -110,122 +109,6 @@ public class SharedFieldComparatorSource
     }
 
     /**
-     * Abstract base class for <code>FieldComparator</code>s which keep their values
-     * (<code>Comparable</code>s) in an array.
-     */
-    private abstract static class AbstractFieldComparator extends FieldComparatorBase {
-
-        /**
-         * The values for comparing.
-         */
-        private final Comparable[] values;
-
-        /**
-         * The index readers.
-         */
-
-        protected final List<IndexReader> readers = new ArrayList<IndexReader>();
-        /**
-         * The document number starts for the {@link #readers}.
-         */
-        protected int[] starts;
-
-        /**
-         * Create a new instance with the given number of values.
-         *
-         * @param numHits  the number of values
-         */
-        protected AbstractFieldComparator(int numHits) {
-            values = new Comparable[numHits];
-        }
-
-        /**
-         * Returns the reader index for document <code>n</code>.
-         *
-         * @param n document number.
-         * @return the reader index.
-         */
-        protected final int readerIndex(int n) {
-            int lo = 0;
-            int hi = readers.size() - 1;
-
-            while (hi >= lo) {
-                int mid = (lo + hi) >> 1;
-                int midValue = starts[mid];
-                if (n < midValue) {
-                    hi = mid - 1;
-                }
-                else if (n > midValue) {
-                    lo = mid + 1;
-                }
-                else {
-                    while (mid + 1 < readers.size() && starts[mid + 1] == midValue) {
-                        mid++;
-                    }
-                    return mid;
-                }
-            }
-            return hi;
-        }
-
-        /**
-         * Add the given value to the values array
-         *
-         * @param slot   index into values
-         * @param value  value for adding
-         */
-        @Override
-        public void setValue(int slot, Comparable value) {
-            values[slot] = value;
-        }
-
-        /**
-         * Return a value from the values array
-         *
-         * @param slot  index to retrieve
-         * @return  the retrieved value
-         */
-        @Override
-        public Comparable getValue(int slot) {
-            return values[slot];
-        }
-
-        @Override
-        public void setNextReader(IndexReader reader, int docBase) throws IOException {
-            getIndexReaders(readers, reader);
-
-            int maxDoc = 0;
-            starts = new int[readers.size() + 1];
-
-            for (int i = 0; i < readers.size(); i++) {
-                IndexReader r = readers.get(i);
-                starts[i] = maxDoc;
-                maxDoc += r.maxDoc();
-            }
-            starts[readers.size()] = maxDoc;
-        }
-
-        /**
-         * Checks if <code>reader</code> is of type {@link MultiIndexReader} and if
-         * so calls itself recursively for each reader within the
-         * <code>MultiIndexReader</code> or otherwise adds the reader to the list.
-         *
-         * @param readers  list of index readers.
-         * @param reader   reader to decompose
-         */
-        private static void getIndexReaders(List<IndexReader> readers, IndexReader reader) {
-            if (reader instanceof MultiIndexReader) {
-                for (IndexReader r : ((MultiIndexReader) reader).getIndexReaders()) {
-                    getIndexReaders(readers, r);
-                }
-            }
-            else {
-                readers.add(reader);
-            }
-        }
-    }
-
-    /**
      * A <code>FieldComparator</code> which works for order by clauses with properties
      * directly on the result nodes.
      */

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/SortedLuceneQueryHits.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/SortedLuceneQueryHits.java?rev=1161475&r1=1161474&r2=1161475&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/SortedLuceneQueryHits.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/SortedLuceneQueryHits.java Thu Aug 25 10:00:13 2011
@@ -16,8 +16,10 @@
  */
 package org.apache.jackrabbit.core.query.lucene;
 
+import java.io.IOException;
+
 import org.apache.jackrabbit.core.id.NodeId;
-import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.search.IndexSearcher;
 import org.apache.lucene.search.Query;
 import org.apache.lucene.search.ScoreDoc;
 import org.apache.lucene.search.Sort;
@@ -25,11 +27,6 @@ import org.apache.lucene.search.TopField
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
 /**
  * Wraps a lucene query result and adds a close method that allows to release
  * resources after a query has been executed and the results have been read
@@ -53,14 +50,9 @@ public final class SortedLuceneQueryHits
     private static final int MIN_FETCH_SIZE = 32;
 
     /**
-     * The IndexReader in use by the lucene hits.
-     */
-    private final IndexReader reader;
-
-    /**
      * The index searcher.
      */
-    private final JackrabbitIndexSearcher searcher;
+    private final IndexSearcher searcher;
 
     /**
      * The query to execute.
@@ -80,7 +72,7 @@ public final class SortedLuceneQueryHits
     /**
      * The score docs.
      */
-    private final List<ScoreDoc> scoreDocs = new ArrayList<ScoreDoc>();
+    private ScoreDoc[] scoreDocs = new ScoreDoc[0];
 
     /**
      * The total number of hits.
@@ -88,26 +80,30 @@ public final class SortedLuceneQueryHits
     private int size;
 
     /**
-     * Number of hits to retrieve.
+     * Number of hits to be pre-fetched from the lucene index (will be around 2x
+     * resultFetchHint).
      */
     private int numHits;
 
+    private int offset = 0;
+
     /**
      * Creates a new <code>QueryHits</code> instance wrapping <code>hits</code>.
-     *
-     * @param reader          the IndexReader in use.
-     * @param searcher        the index searcher.
-     * @param query           the query to execute.
-     * @param sort            the sort criteria.
-     * @param resultFetchHint a hint on how many results should be fetched.
-     * @throws IOException if an error occurs while reading from the index.
-     */
-    public SortedLuceneQueryHits(IndexReader reader,
-                                 JackrabbitIndexSearcher searcher,
-                                 Query query,
-                                 Sort sort,
-                                 long resultFetchHint) throws IOException {
-        this.reader = reader;
+     * 
+     * @param searcher
+     *            the index searcher.
+     * @param query
+     *            the query to execute.
+     * @param sort
+     *            the sort criteria.
+     * @param resultFetchHint
+     *            a hint on how many results should be pre-fetched from the
+     *            lucene index.
+     * @throws IOException
+     *             if an error occurs while reading from the index.
+     */
+    public SortedLuceneQueryHits(IndexSearcher searcher, Query query,
+            Sort sort, long resultFetchHint) throws IOException {
         this.searcher = searcher;
         this.query = query;
         this.sort = sort;
@@ -131,13 +127,13 @@ public final class SortedLuceneQueryHits
         if (++hitIndex >= size) {
             // no more score nodes
             return null;
-        } else if (hitIndex >= scoreDocs.size()) {
+        } else if (hitIndex - offset >= scoreDocs.length) {
             // refill at least numHits or twice hitIndex
             this.numHits = Math.max(this.numHits, hitIndex * 2);
             getHits();
         }
-        ScoreDoc doc = scoreDocs.get(hitIndex);
-        String uuid = reader.document(doc.doc,
+        ScoreDoc doc = scoreDocs[hitIndex - offset];
+        String uuid = searcher.doc(doc.doc,
                 FieldSelectors.UUID).get(FieldNames.UUID);
         NodeId id = new NodeId(uuid);
         return new ScoreNode(id, doc.score, doc.doc);
@@ -159,11 +155,10 @@ public final class SortedLuceneQueryHits
         TopFieldCollector collector = TopFieldCollector.create(sort, numHits, false, true, false, false);
         searcher.search(query, collector);
         size = collector.getTotalHits();
-        ScoreDoc[] docs = collector.topDocs().scoreDocs;
-        scoreDocs.addAll(Arrays.asList(docs).subList(scoreDocs.size(), docs.length));
-        log.debug("getHits() {}/{}", scoreDocs.size(), numHits);
+        offset += scoreDocs.length;
+        scoreDocs = collector.topDocs(offset, numHits).scoreDocs;
+        log.debug("getHits() {}/{}", scoreDocs.length, numHits);
         // double hits for next round
         numHits *= 2;
     }
-
 }

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/QueryEngine.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/QueryEngine.java?rev=1161475&r1=1161474&r2=1161475&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/QueryEngine.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/QueryEngine.java Thu Aug 25 10:00:13 2011
@@ -18,13 +18,13 @@ package org.apache.jackrabbit.core.query
 
 import static javax.jcr.query.qom.QueryObjectModelConstants.JCR_JOIN_TYPE_LEFT_OUTER;
 import static javax.jcr.query.qom.QueryObjectModelConstants.JCR_JOIN_TYPE_RIGHT_OUTER;
-import static javax.jcr.query.qom.QueryObjectModelConstants.JCR_ORDER_DESCENDING;
 
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.Comparator;
+import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
@@ -48,18 +48,23 @@ import javax.jcr.query.RowIterator;
 import javax.jcr.query.qom.Column;
 import javax.jcr.query.qom.Constraint;
 import javax.jcr.query.qom.Join;
-import javax.jcr.query.qom.Operand;
 import javax.jcr.query.qom.Ordering;
 import javax.jcr.query.qom.PropertyValue;
+import javax.jcr.query.qom.QueryObjectModelConstants;
 import javax.jcr.query.qom.QueryObjectModelFactory;
 import javax.jcr.query.qom.Selector;
 import javax.jcr.query.qom.Source;
 
 import org.apache.commons.io.IOUtils;
+import org.apache.jackrabbit.JcrConstants;
 import org.apache.jackrabbit.commons.JcrUtils;
 import org.apache.jackrabbit.commons.iterator.RowIteratorAdapter;
 import org.apache.jackrabbit.commons.query.qom.OperandEvaluator;
 import org.apache.jackrabbit.core.query.lucene.LuceneQueryFactory;
+import org.apache.jackrabbit.core.query.lucene.sort.DynamicOperandFieldComparatorSource;
+import org.apache.jackrabbit.core.query.lucene.sort.RowComparator;
+import org.apache.lucene.search.Sort;
+import org.apache.lucene.search.SortField;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -70,58 +75,16 @@ public class QueryEngine {
      */
     private static final Logger log = LoggerFactory
             .getLogger(QueryEngine.class);
+    
+    //TODO remove this when the implementation is stable
+    public static final String NATIVE_SORT_SYSTEM_PROPERTY = "useNativeSort";
 
-    private static final int printIndentStep = 4;
-
-    /**
-     * Row comparator.
-     */
-    private static class RowComparator implements Comparator<Row> {
-
-        private final ValueComparator comparator = new ValueComparator();
-
-        private final Ordering[] orderings;
-
-        private final OperandEvaluator evaluator;
-
-        private RowComparator(Ordering[] orderings, OperandEvaluator evaluator) {
-            this.orderings = orderings;
-            this.evaluator = evaluator;
-        }
-
-        public int compare(Row a, Row b) {
-            try {
-                for (Ordering ordering : orderings) {
-                    Operand operand = ordering.getOperand();
-                    Value[] va = evaluator.getValues(operand, a);
-                    Value[] vb = evaluator.getValues(operand, b);
-                    int d = compare(va, vb);
-                    if (d != 0) {
-                        if (JCR_ORDER_DESCENDING.equals(ordering.getOrder())) {
-                            return -d;
-                        } else {
-                            return d;
-                        }
-                    }
-                }
-                return 0;
-            } catch (RepositoryException e) {
-                throw new RuntimeException("Unable to compare rows " + a
-                        + " and " + b, e);
-            }
-        }
-
-        private int compare(Value[] a, Value[] b) {
-            for (int i = 0; i < a.length && i < b.length; i++) {
-                int d = comparator.compare(a[i], b[i]);
-                if (d != 0) {
-                    return d;
-                }
-            }
-            return a.length - b.length;
-        }
+    private static final boolean NATIVE_SORT = Boolean.valueOf(System
+            .getProperty(NATIVE_SORT_SYSTEM_PROPERTY, "false"));
 
-    }
+    private static final int printIndentStep = 4;
+    
+    private final Session session;
 
     private final LuceneQueryFactory lqf;
 
@@ -135,8 +98,8 @@ public class QueryEngine {
 
     public QueryEngine(Session session, LuceneQueryFactory lqf,
             Map<String, Value> variables) throws RepositoryException {
+        this.session = session;
         this.lqf = lqf;
-
         Workspace workspace = session.getWorkspace();
         this.ntManager = workspace.getNodeTypeManager();
         this.qomFactory = workspace.getQueryManager().getQOMFactory();
@@ -151,8 +114,8 @@ public class QueryEngine {
         long time = System.currentTimeMillis();
         QueryResult qr = execute(columns, source, constraint, orderings,
                 offset, limit, 2);
-        log.debug("SQL2 QUERY execute took {} ms.", System.currentTimeMillis()
-                - time);
+        log.debug("SQL2 QUERY execute took {} ms. native sort is {}.",
+                System.currentTimeMillis() - time, NATIVE_SORT);
         return qr;
     }
 
@@ -251,7 +214,7 @@ public class QueryEngine {
             return new SimpleQueryResult(merger.getColumnNames(),
                     merger.getSelectorNames(), new RowIteratorAdapter(allRows));
         }
-        
+
         Set<Row> leftRows = buildLeftRowsJoin(csInfo, leftCo, printIndentation
                 + printIndentStep);
         if (log.isDebugEnabled()) {
@@ -483,23 +446,79 @@ public class QueryEngine {
         String[] columnNames = columnMap.keySet().toArray(
                 new String[columnMap.size()]);
 
+        Sort sort = new Sort();
+        if (NATIVE_SORT) {
+            sort = new Sort(createSortFields(orderings, session));
+        }
+
+        // if true it means that the LuceneQueryFactory should just let the
+        // QueryEngine take care of sorting and applying offset and limit
+        // constraints
+        boolean externalSort = !NATIVE_SORT;
+        RowIterator rows = null;
         try {
-            RowIterator rows = new RowIteratorAdapter(lqf.execute(columnMap,
-                    selector, constraint));
-            QueryResult result = new SimpleQueryResult(columnNames,
-                    selectorNames, rows);
-            return sort(result, orderings, evaluator, offset, limit);
+            rows = new RowIteratorAdapter(lqf.execute(columnMap, selector,
+                    constraint, sort, externalSort, offset, limit));
         } catch (IOException e) {
             throw new RepositoryException("Failed to access the query index", e);
         } finally {
-            if (log.isDebugEnabled()) {
-                time = System.currentTimeMillis() - time;
-                log.debug(genString(printIndentation) + "SQL2 SELECT took "
-                        + time + " ms. selector: " + selector + ", columns: "
-                        + Arrays.toString(columnNames) + ", constraint: "
-                        + constraint);
+            log.debug(
+                    "{}SQL2 SELECT took {} ms. selector: {}, columns: {}, constraint: {}, offset {}, limit {}",
+                    new Object[] { genString(printIndentation),
+                            System.currentTimeMillis() - time, selector,
+                            Arrays.toString(columnNames), constraint, offset,
+                            limit });
+        }
+        QueryResult result = new SimpleQueryResult(columnNames, selectorNames,
+                rows);
+        if (NATIVE_SORT) {
+            return result;
+        }
+
+        long timeSort = System.currentTimeMillis();
+        QueryResult sorted = sort(result, orderings, evaluator, offset, limit);
+        log.debug("{}SQL2 SORT took {} ms.", genString(printIndentation),
+                System.currentTimeMillis() - timeSort);
+        return sorted;
+    }
+
+    public SortField[] createSortFields(Ordering[] orderings, Session session)
+            throws RepositoryException {
+
+        if (orderings == null || orderings.length == 0) {
+            return new SortField[] { SortField.FIELD_SCORE };
+        }
+        // orderings[] -> (property, ordering)
+        Map<String, Ordering> orderByProperties = new HashMap<String, Ordering>();
+        for (Ordering o : orderings) {
+            final String p = o.toString();
+            if (!orderByProperties.containsKey(p)) {
+                orderByProperties.put(p, o);
+            }
+        }
+        final DynamicOperandFieldComparatorSource dofcs = new DynamicOperandFieldComparatorSource(
+                session, evaluator, orderByProperties);
+
+        List<SortField> sortFields = new ArrayList<SortField>();
+
+        // as it turn out, orderByProperties.keySet() doesn't keep the original
+        // insertion order
+        for (Ordering o : orderings) {
+            final String p = o.toString();
+            // order on jcr:score does not use the natural order as
+            // implemented in lucene. score ascending in lucene means that
+            // higher scores are first. JCR specs that lower score values
+            // are first.
+            boolean isAsc = QueryObjectModelConstants.JCR_ORDER_ASCENDING
+                    .equals(o.getOrder());
+            if (JcrConstants.JCR_SCORE.equals(p)) {
+                sortFields.add(new SortField(null, SortField.SCORE, !isAsc));
+            } else {
+                // TODO use native sort if available
+                sortFields.add(new SortField(p, dofcs, isAsc));
             }
         }
+        return sortFields.toArray(new SortField[sortFields.size()]);
     }
 
     private Map<String, PropertyValue> getColumnMap(Column[] columns,

Added: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/sort/AbstractFieldComparator.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/sort/AbstractFieldComparator.java?rev=1161475&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/sort/AbstractFieldComparator.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/sort/AbstractFieldComparator.java Thu Aug 25 10:00:13 2011
@@ -0,0 +1,141 @@
+/*
+ * 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.core.query.lucene.sort;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.jackrabbit.core.query.lucene.FieldComparatorBase;
+import org.apache.jackrabbit.core.query.lucene.MultiIndexReader;
+import org.apache.lucene.index.IndexReader;
+
+/**
+ * Abstract base class for <code>FieldComparator</code>s which keep their values
+ * (<code>Comparable</code>s) in an array.
+ */
+public abstract class AbstractFieldComparator extends FieldComparatorBase {
+
+    /**
+     * The values for comparing.
+     */
+    private final Comparable[] values;
+
+    /**
+     * The index readers.
+     */
+
+    protected final List<IndexReader> readers = new ArrayList<IndexReader>();
+    /**
+     * The document number starts for the {@link #readers}.
+     */
+    protected int[] starts;
+
+    /**
+     * Create a new instance with the given number of values.
+     *
+     * @param numHits  the number of values
+     */
+    protected AbstractFieldComparator(int numHits) {
+        values = new Comparable[numHits];
+    }
+
+    /**
+     * Returns the reader index for document <code>n</code>.
+     *
+     * @param n document number.
+     * @return the reader index.
+     */
+    protected final int readerIndex(int n) {
+        int lo = 0;
+        int hi = readers.size() - 1;
+
+        while (hi >= lo) {
+            int mid = (lo + hi) >> 1;
+            int midValue = starts[mid];
+            if (n < midValue) {
+                hi = mid - 1;
+            }
+            else if (n > midValue) {
+                lo = mid + 1;
+            }
+            else {
+                while (mid + 1 < readers.size() && starts[mid + 1] == midValue) {
+                    mid++;
+                }
+                return mid;
+            }
+        }
+        return hi;
+    }
+
+    /**
+     * Add the given value to the values array
+     *
+     * @param slot   index into values
+     * @param value  value for adding
+     */
+    @Override
+    public void setValue(int slot, Comparable value) {
+        values[slot] = value;
+    }
+
+    /**
+     * Return a value from the values array
+     *
+     * @param slot  index to retrieve
+     * @return  the retrieved value
+     */
+    @Override
+    public Comparable getValue(int slot) {
+        return values[slot];
+    }
+
+    @Override
+    public void setNextReader(IndexReader reader, int docBase) throws IOException {
+        getIndexReaders(readers, reader);
+
+        int maxDoc = 0;
+        starts = new int[readers.size() + 1];
+
+        for (int i = 0; i < readers.size(); i++) {
+            IndexReader r = readers.get(i);
+            starts[i] = maxDoc;
+            maxDoc += r.maxDoc();
+        }
+        starts[readers.size()] = maxDoc;
+    }
+
+    /**
+     * Checks if <code>reader</code> is of type {@link MultiIndexReader} and if
+     * so calls itself recursively for each reader within the
+     * <code>MultiIndexReader</code> or otherwise adds the reader to the list.
+     *
+     * @param readers  list of index readers.
+     * @param reader   reader to decompose
+     */
+    private static void getIndexReaders(List<IndexReader> readers, IndexReader reader) {
+        if (reader instanceof MultiIndexReader) {
+            for (IndexReader r : ((MultiIndexReader) reader).getIndexReaders()) {
+                getIndexReaders(readers, r);
+            }
+        }
+        else {
+            readers.add(reader);
+        }
+    }
+}
\ No newline at end of file

Propchange: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/sort/AbstractFieldComparator.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/sort/DynamicOperandFieldComparator.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/sort/DynamicOperandFieldComparator.java?rev=1161475&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/sort/DynamicOperandFieldComparator.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/sort/DynamicOperandFieldComparator.java Thu Aug 25 10:00:13 2011
@@ -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.core.query.lucene.sort;
+
+import javax.jcr.Node;
+import javax.jcr.Session;
+import javax.jcr.Value;
+import javax.jcr.query.qom.Ordering;
+
+import org.apache.jackrabbit.commons.query.qom.OperandEvaluator;
+import org.apache.jackrabbit.core.query.lucene.FieldNames;
+import org.apache.lucene.document.Document;
+import org.apache.lucene.index.IndexReader;
+
+public final class DynamicOperandFieldComparator extends
+        AbstractFieldComparator {
+
+    private final Session session;
+    private final OperandEvaluator evaluator;
+    private final Ordering ordering;
+    private final boolean reversed;
+
+    public DynamicOperandFieldComparator(final Session session,
+            final OperandEvaluator evaluator, final Ordering ordering,
+            int numHits, final boolean reversed) {
+        super(numHits);
+        this.session = session;
+        this.evaluator = evaluator;
+        this.ordering = ordering;
+        this.reversed = reversed;
+    }
+
+    @Override
+    protected Comparable sortValue(int doc) {
+        try {
+            int idx = readerIndex(doc);
+            IndexReader reader = readers.get(idx);
+            Document document = reader.document(doc - starts[idx]);
+            final String uuid = document.get(FieldNames.UUID);
+            final Node n = session.getNodeByIdentifier(uuid);
+            Value[] v = evaluator.getValues(ordering.getOperand(), n);
+            return new ValueComparableWrapper(v, reversed);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
+}
\ No newline at end of file

Propchange: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/sort/DynamicOperandFieldComparator.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/sort/DynamicOperandFieldComparatorSource.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/sort/DynamicOperandFieldComparatorSource.java?rev=1161475&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/sort/DynamicOperandFieldComparatorSource.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/sort/DynamicOperandFieldComparatorSource.java Thu Aug 25 10:00:13 2011
@@ -0,0 +1,53 @@
+/*
+ * 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.core.query.lucene.sort;
+
+import java.io.IOException;
+import java.util.Map;
+
+import javax.jcr.Session;
+import javax.jcr.query.qom.Ordering;
+
+import org.apache.jackrabbit.commons.query.qom.OperandEvaluator;
+import org.apache.lucene.search.FieldComparator;
+import org.apache.lucene.search.FieldComparatorSource;
+
+public final class DynamicOperandFieldComparatorSource extends
+        FieldComparatorSource {
+
+    private static final long serialVersionUID = 1L;
+
+    private final Session session;
+    private final OperandEvaluator evaluator;
+    private Map<String, Ordering> orderByProperties;
+
+    public DynamicOperandFieldComparatorSource(final Session session,
+            final OperandEvaluator evaluator,
+            final Map<String, Ordering> orderByProperties) {
+        this.session = session;
+        this.evaluator = evaluator;
+        this.orderByProperties = orderByProperties;
+    }
+
+    @Override
+    public FieldComparator newComparator(String fieldname, int numHits,
+            int sortPos, boolean reversed) throws IOException {
+        final Ordering o = orderByProperties.get(fieldname);
+        return new DynamicOperandFieldComparator(session, evaluator, o,
+                numHits, reversed);
+    }
+}
\ No newline at end of file

Propchange: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/sort/DynamicOperandFieldComparatorSource.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/sort/RowComparator.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/sort/RowComparator.java?rev=1161475&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/sort/RowComparator.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/sort/RowComparator.java Thu Aug 25 10:00:13 2011
@@ -0,0 +1,80 @@
+/*
+ * 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.core.query.lucene.sort;
+
+import static javax.jcr.query.qom.QueryObjectModelConstants.JCR_ORDER_DESCENDING;
+
+import java.util.Comparator;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.Value;
+import javax.jcr.query.Row;
+import javax.jcr.query.qom.Operand;
+import javax.jcr.query.qom.Ordering;
+
+import org.apache.jackrabbit.commons.query.qom.OperandEvaluator;
+import org.apache.jackrabbit.core.query.lucene.join.ValueComparator;
+
+/**
+ * Row comparator.
+ */
+public class RowComparator implements Comparator<Row> {
+
+    private final ValueComparator comparator = new ValueComparator();
+
+    private final Ordering[] orderings;
+
+    private final OperandEvaluator evaluator;
+
+    public RowComparator(Ordering[] orderings, OperandEvaluator evaluator) {
+        this.orderings = orderings;
+        this.evaluator = evaluator;
+    }
+
+    public int compare(Row a, Row b) {
+        try {
+            for (Ordering ordering : orderings) {
+                Operand operand = ordering.getOperand();
+                Value[] va = evaluator.getValues(operand, a);
+                Value[] vb = evaluator.getValues(operand, b);
+                int d = compare(va, vb);
+                if (d != 0) {
+                    if (JCR_ORDER_DESCENDING.equals(ordering.getOrder())) {
+                        return -d;
+                    } else {
+                        return d;
+                    }
+                }
+            }
+            return 0;
+        } catch (RepositoryException e) {
+            throw new RuntimeException("Unable to compare rows " + a + " and "
+                    + b, e);
+        }
+    }
+
+    private int compare(Value[] a, Value[] b) {
+        for (int i = 0; i < a.length && i < b.length; i++) {
+            int d = comparator.compare(a[i], b[i]);
+            if (d != 0) {
+                return d;
+            }
+        }
+        return a.length - b.length;
+    }
+
+}
\ No newline at end of file

Propchange: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/sort/RowComparator.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/sort/ValueComparableWrapper.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/sort/ValueComparableWrapper.java?rev=1161475&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/sort/ValueComparableWrapper.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/sort/ValueComparableWrapper.java Thu Aug 25 10:00:13 2011
@@ -0,0 +1,58 @@
+/*
+ * 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.core.query.lucene.sort;
+
+import javax.jcr.Value;
+
+import org.apache.jackrabbit.core.query.lucene.join.ValueComparator;
+
+class ValueComparableWrapper implements Comparable<ValueComparableWrapper> {
+    private final ValueComparator comparator = new ValueComparator();
+
+    private final Value[] v;
+    private final boolean reversed;
+
+    public ValueComparableWrapper(final Value[] v, final boolean reversed) {
+        this.v = v;
+        this.reversed = reversed;
+    }
+
+    public Value[] getValue() {
+        return v;
+    }
+
+    public int compareTo(ValueComparableWrapper o) {
+        final int d = compare(v, o.getValue());
+        if (d != 0) {
+            if (reversed) {
+                return -d;
+            }
+            return -d;
+        }
+        return 0;
+    }
+
+    private int compare(Value[] a, Value[] b) {
+        for (int i = 0; i < a.length && i < b.length; i++) {
+            int d = comparator.compare(a[i], b[i]);
+            if (d != 0) {
+                return d;
+            }
+        }
+        return a.length - b.length;
+    }
+}
\ No newline at end of file

Propchange: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/sort/ValueComparableWrapper.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/SQL2OffsetLimitTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/SQL2OffsetLimitTest.java?rev=1161475&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/SQL2OffsetLimitTest.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/SQL2OffsetLimitTest.java Thu Aug 25 10:00:13 2011
@@ -0,0 +1,167 @@
+/*
+ * 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.core.query;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.query.Query;
+import javax.jcr.query.QueryResult;
+import javax.jcr.query.Row;
+
+import org.apache.jackrabbit.commons.JcrUtils;
+
+/**
+ * Test case for queries with JCR_SQL2 having offset and limit clauses
+ * 
+ * Inspired by <a
+ * href="https://issues.apache.org/jira/browse/JCR-2830">JCR-2830</a>
+ * 
+ */
+public class SQL2OffsetLimitTest extends AbstractQueryTest {
+
+    private final List<String> c = Arrays.asList("a", "b", "c", "d", "e");
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        for (String s : c) {
+            testRootNode.addNode(s);
+        }
+        testRootNode.getSession().save();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        for (Node c : JcrUtils.getChildNodes(testRootNode)) {
+            testRootNode.getSession().removeItem(c.getPath());
+        }
+        testRootNode.getSession().save();
+        super.tearDown();
+    }
+
+    private Query newQuery() throws Exception {
+        return qm.createQuery("SELECT * FROM [nt:base] WHERE ISCHILDNODE(["
+                + testRoot + "]) order by [jcr:score] ", Query.JCR_SQL2);
+    }
+
+    public void testNoConstraints() throws Exception {
+        List<String> expected = new ArrayList<String>(c);
+        Query q = newQuery();
+
+        List<String> out = qrToPaths(q.execute());
+        assertEquals(c.size(), out.size());
+        for (String s : out) {
+            assertTrue(expected.remove(s));
+        }
+        assertTrue(expected.isEmpty());
+    }
+
+    public void testLimitEqSize() throws Exception {
+        List<String> expected = new ArrayList<String>(c);
+        Query q = newQuery();
+        q.setOffset(0);
+        q.setLimit(c.size());
+
+        List<String> out = qrToPaths(q.execute());
+        assertEquals(c.size(), out.size());
+        for (String s : out) {
+            assertTrue(expected.remove(s));
+        }
+        assertTrue(expected.isEmpty());
+    }
+
+    public void testLimitGtSize() throws Exception {
+        List<String> expected = new ArrayList<String>(c);
+        Query q = newQuery();
+        q.setOffset(0);
+        q.setLimit(c.size() * 2);
+
+        List<String> out = qrToPaths(q.execute());
+        assertEquals(c.size(), out.size());
+        for (String s : out) {
+            assertTrue(expected.remove(s));
+        }
+        assertTrue(expected.isEmpty());
+    }
+
+    public void testOffsetEqSize() throws Exception {
+        Query q = newQuery();
+        q.setOffset(c.size() - 1);
+        List<String> out = qrToPaths(q.execute());
+        assertEquals(1, out.size());
+    }
+
+    public void testOffsetGtSize() throws Exception {
+        Query q = newQuery();
+        q.setOffset(c.size() * 2);
+        List<String> out = qrToPaths(q.execute());
+        assertTrue(out.isEmpty());
+    }
+
+    public void testSimplePagination() throws Exception {
+        List<String> expected = new ArrayList<String>(c);
+        Query q = newQuery();
+
+        for (int i = 0; i < c.size(); i++) {
+            q.setOffset(i);
+            q.setLimit(1);
+            List<String> out = qrToPaths(q.execute());
+            assertEquals(1, out.size());
+            assertTrue(expected.remove(out.get(0)));
+        }
+        assertTrue(expected.isEmpty());
+    }
+
+    public void test2BigPages() throws Exception {
+        List<String> expected = new ArrayList<String>(c);
+        Query q = newQuery();
+
+        int p1 = (int) (c.size() * 0.8);
+        int p2 = c.size() - p1;
+
+        q.setOffset(0);
+        q.setLimit(p1);
+        List<String> out1 = qrToPaths(q.execute());
+        assertEquals(p1, out1.size());
+        for (String s : out1) {
+            assertTrue(expected.remove(s));
+        }
+
+        q.setOffset(p1);
+        q.setLimit(p2);
+        List<String> out2 = qrToPaths(q.execute());
+        assertEquals(p2, out2.size());
+        for (String s : out2) {
+            assertTrue(expected.remove(s));
+        }
+
+        assertTrue(expected.isEmpty());
+    }
+
+    private List<String> qrToPaths(QueryResult qr) throws RepositoryException {
+        List<String> ret = new ArrayList<String>();
+        for (Row row : JcrUtils.getRows(qr)) {
+            Node n = row.getNode();
+            ret.add(n.getPath().replace(n.getParent().getPath() + "/", ""));
+        }
+        return ret;
+    }
+}

Propchange: jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/SQL2OffsetLimitTest.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/SQL2OrderByTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/SQL2OrderByTest.java?rev=1161475&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/SQL2OrderByTest.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/SQL2OrderByTest.java Thu Aug 25 10:00:13 2011
@@ -0,0 +1,159 @@
+/*
+ * 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.core.query;
+
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.RepositoryException;
+import javax.jcr.query.QueryResult;
+
+import org.apache.jackrabbit.commons.JcrUtils;
+
+/**
+ * Tests queries with order by.
+ */
+public class SQL2OrderByTest extends AbstractQueryTest {
+
+    // TODO order by aggregate test?
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        for (Node c : JcrUtils.getChildNodes(testRootNode)) {
+            testRootNode.getSession().removeItem(c.getPath());
+        }
+        testRootNode.getSession().save();
+        super.tearDown();
+    }
+
+    public void testOrderByScore() throws RepositoryException {
+        Node n1 = testRootNode.addNode("node1");
+        Node n2 = testRootNode.addNode("node2");
+        Node n3 = testRootNode.addNode("node3");
+
+        n1.setProperty("text", "aaa");
+        n1.setProperty("value", 3);
+        testRootNode.getSession().save();
+
+        n2.setProperty("text", "bbb");
+        n2.setProperty("value", 2);
+        testRootNode.getSession().save();
+
+        n3.setProperty("text", "ccc");
+        n3.setProperty("value", 2);
+        testRootNode.getSession().save();
+
+        QueryResult qr = executeSQL2Query("SELECT * FROM [nt:base] WHERE ISCHILDNODE(["
+                + testRoot + "]) ORDER BY [jcr:score]");
+        checkSeq(qr, new Node[] { n1, n2, n3 });
+
+    }
+
+    public void testOrderByVal() throws RepositoryException {
+
+        Node n1 = testRootNode.addNode("node1");
+        Node n2 = testRootNode.addNode("node2");
+        Node n3 = testRootNode.addNode("node3");
+
+        n1.setProperty("text", "aaa");
+        n1.setProperty("value", 3);
+        n2.setProperty("text", "bbb");
+        n2.setProperty("value", 2);
+        n3.setProperty("text", "ccc");
+        n3.setProperty("value", 1);
+
+        testRootNode.getSession().save();
+
+        QueryResult qr = executeSQL2Query("SELECT * FROM [nt:base] WHERE ISCHILDNODE(["
+                + testRoot + "]) ORDER BY [value] desc");
+        checkSeq(qr, new Node[] { n1, n2, n3 });
+    }
+
+    public void testOrderByVal2() throws RepositoryException {
+
+        Node n1 = testRootNode.addNode("node1");
+        Node n2 = testRootNode.addNode("node2");
+        Node n3 = testRootNode.addNode("node3");
+
+        n1.setProperty("text", "aaa");
+        n1.setProperty("value", 3);
+        n2.setProperty("text", "bbb");
+        n2.setProperty("value", 2);
+        n3.setProperty("text", "ccc");
+        n3.setProperty("value", 1);
+
+        testRootNode.getSession().save();
+
+        QueryResult qr = executeSQL2Query("SELECT * FROM [nt:base] WHERE ISCHILDNODE(["
+                + testRoot + "]) ORDER BY [value]");
+        checkSeq(qr, new Node[] { n3, n2, n1 });
+    }
+
+    public void testOrderByFunction() throws RepositoryException {
+
+        Node n1 = testRootNode.addNode("node1", "nt:unstructured");
+        Node n2 = testRootNode.addNode("node2", "nt:unstructured");
+        Node n3 = testRootNode.addNode("node3", "nt:unstructured");
+
+        n1.setProperty("t", "aa");
+        n1.setProperty("value", 3);
+        n2.setProperty("t", "bbb");
+        n2.setProperty("value", 2);
+        n3.setProperty("t", "ccc");
+        n3.setProperty("value", 1);
+
+        testRootNode.getSession().save();
+
+        QueryResult qr = executeSQL2Query("SELECT * FROM [nt:base] as sissy WHERE ISCHILDNODE(["
+                + testRoot + "]) ORDER BY LENGTH([t]), t");
+        checkSeq(qr, new Node[] { n1, n2, n3 });
+    }
+
+    public void testOrderByFunction2() throws RepositoryException {
+
+        Node n1 = testRootNode.addNode("node1", "nt:unstructured");
+        Node n2 = testRootNode.addNode("node2", "nt:unstructured");
+        Node n3 = testRootNode.addNode("node3", "nt:unstructured");
+
+        n1.setProperty("t", "aa");
+        n1.setProperty("value", 3);
+        n2.setProperty("t", "bbb");
+        n2.setProperty("value", 2);
+        n3.setProperty("t", "ccc");
+        n3.setProperty("value", 1);
+
+        testRootNode.getSession().save();
+
+        QueryResult qr = executeSQL2Query("SELECT * FROM [nt:base] as sissy WHERE ISCHILDNODE(["
+                + testRoot + "]) ORDER BY LENGTH([t]), t desc");
+        checkSeq(qr, new Node[] { n1, n3, n2 });
+    }
+
+    private void checkSeq(QueryResult qr, Node[] nodes)
+            throws RepositoryException {
+        NodeIterator ni = qr.getNodes();
+        for (Node n : nodes) {
+            assertTrue(ni.hasNext());
+            assertEquals(n.getPath(), ni.nextNode().getPath());
+        }
+    }
+
+}

Propchange: jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/SQL2OrderByTest.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Modified: jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/TestAll.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/TestAll.java?rev=1161475&r1=1161474&r2=1161475&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/TestAll.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/TestAll.java Thu Aug 25 10:00:13 2011
@@ -70,6 +70,8 @@ public class TestAll extends TestCase {
         suite.addTestSuite(SQL2PathEscapingTest.class);
         suite.addTestSuite(SQL2QueryResultTest.class);
         suite.addTestSuite(LimitedAccessQueryTest.class);
+        suite.addTestSuite(SQL2OffsetLimitTest.class);
+        suite.addTestSuite(SQL2OrderByTest.class);
 
         return suite;
     }



Mime
View raw message