Index: src/main/java/org/apache/jackrabbit/core/query/lucene/QueryImpl.java
===================================================================
--- src/main/java/org/apache/jackrabbit/core/query/lucene/QueryImpl.java (revision 744177)
+++ src/main/java/org/apache/jackrabbit/core/query/lucene/QueryImpl.java (working copy)
@@ -31,6 +31,7 @@
import org.apache.jackrabbit.core.nodetype.PropertyDefinitionImpl;
import org.apache.jackrabbit.core.query.PropertyTypeRegistry;
import org.apache.jackrabbit.spi.Name;
+import org.apache.jackrabbit.spi.Path;
import org.apache.jackrabbit.spi.commons.name.NameConstants;
import org.apache.jackrabbit.spi.commons.name.NameFactoryImpl;
import org.apache.jackrabbit.spi.commons.query.AndQueryNode;
@@ -120,10 +121,10 @@
} else {
orderSpecs = new OrderQueryNode.OrderSpec[0];
}
- Name[] orderProperties = new Name[orderSpecs.length];
+ Path[] orderProperties = new Path[orderSpecs.length];
boolean[] ascSpecs = new boolean[orderSpecs.length];
for (int i = 0; i < orderSpecs.length; i++) {
- orderProperties[i] = orderSpecs[i].getProperty();
+ orderProperties[i] = orderSpecs[i].getPropertyPath();
ascSpecs[i] = orderSpecs[i].isAscending();
}
Index: src/main/java/org/apache/jackrabbit/core/query/lucene/QueryObjectModelImpl.java
===================================================================
--- src/main/java/org/apache/jackrabbit/core/query/lucene/QueryObjectModelImpl.java (revision 744177)
+++ src/main/java/org/apache/jackrabbit/core/query/lucene/QueryObjectModelImpl.java (working copy)
@@ -18,18 +18,22 @@
import org.apache.jackrabbit.core.query.PropertyTypeRegistry;
import org.apache.jackrabbit.spi.commons.query.jsr283.qom.QueryObjectModelConstants;
+import org.apache.jackrabbit.spi.commons.query.jsr283.qom.PropertyValue;
import org.apache.jackrabbit.spi.commons.query.qom.QueryObjectModelTree;
import org.apache.jackrabbit.spi.commons.query.qom.ColumnImpl;
import org.apache.jackrabbit.spi.commons.query.qom.OrderingImpl;
import org.apache.jackrabbit.spi.commons.query.qom.DefaultTraversingQOMTreeVisitor;
import org.apache.jackrabbit.spi.commons.query.qom.BindVariableValueImpl;
import org.apache.jackrabbit.spi.commons.query.qom.SelectorImpl;
+import org.apache.jackrabbit.spi.commons.name.PathFactoryImpl;
import org.apache.jackrabbit.core.SessionImpl;
import org.apache.jackrabbit.core.ItemManager;
import org.apache.jackrabbit.spi.Name;
+import org.apache.jackrabbit.spi.Path;
import org.apache.lucene.search.Query;
import javax.jcr.RepositoryException;
+import javax.jcr.UnsupportedRepositoryOperationException;
import javax.jcr.query.QueryResult;
/**
@@ -81,7 +85,7 @@
Name[] names = new Name[selectors.length];
for (int i = 0; i < names.length; i++) {
names[i] = selectors[i].getSelectorQName();
- };
+ }
return names;
}
@@ -110,10 +114,17 @@
}
OrderingImpl[] orderings = qomTree.getOrderings();
// TODO: there are many kinds of DynamicOperand that can be ordered by
- Name[] orderProps = new Name[orderings.length];
+ Path[] orderProps = new Path[orderings.length];
boolean[] orderSpecs = new boolean[orderings.length];
for (int i = 0; i < orderings.length; i++) {
orderSpecs[i] = orderings[i].getOrder() == QueryObjectModelConstants.ORDER_ASCENDING;
+ if (orderings[i].getOperand() instanceof PropertyValue) {
+ PropertyValue pv = (PropertyValue) orderings[i].getOperand();
+ orderProps[i] = PathFactoryImpl.getInstance().create(pv.getPropertyName());
+ } else {
+ throw new UnsupportedRepositoryOperationException("order by with" +
+ orderings[i].getOperand() + " not yet implemented");
+ }
}
return new QueryResultImpl(index, itemMgr,
session, session.getAccessManager(),
Index: src/main/java/org/apache/jackrabbit/core/query/lucene/QueryResultImpl.java
===================================================================
--- src/main/java/org/apache/jackrabbit/core/query/lucene/QueryResultImpl.java (revision 744177)
+++ src/main/java/org/apache/jackrabbit/core/query/lucene/QueryResultImpl.java (working copy)
@@ -21,6 +21,7 @@
import org.apache.jackrabbit.core.SessionImpl;
import org.apache.jackrabbit.core.security.AccessManager;
import org.apache.jackrabbit.spi.Name;
+import org.apache.jackrabbit.spi.Path;
import org.apache.lucene.search.Query;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -88,9 +89,9 @@
protected final Name[] selectProps;
/**
- * The names of properties to use for ordering the result set.
+ * The relative paths of properties to use for ordering the result set.
*/
- protected final Name[] orderProps;
+ protected final Path[] orderProps;
/**
* The order specifier for each of the order properties.
@@ -153,7 +154,7 @@
* @param spellSuggestion the spell suggestion or null if none
* is available.
* @param selectProps the select properties of the query.
- * @param orderProps the names of the order properties.
+ * @param orderProps the relative paths of the order properties.
* @param orderSpecs the order specs, one for each order property
* name.
* @param documentOrder if true the result is returned in
@@ -169,7 +170,7 @@
Query query,
SpellSuggestion spellSuggestion,
Name[] selectProps,
- Name[] orderProps,
+ Path[] orderProps,
boolean[] orderSpecs,
boolean documentOrder,
long offset,
Index: src/main/java/org/apache/jackrabbit/core/query/lucene/SearchIndex.java
===================================================================
--- src/main/java/org/apache/jackrabbit/core/query/lucene/SearchIndex.java (revision 744177)
+++ src/main/java/org/apache/jackrabbit/core/query/lucene/SearchIndex.java (working copy)
@@ -35,7 +35,6 @@
import org.apache.jackrabbit.core.state.ItemStateManager;
import org.apache.jackrabbit.extractor.DefaultTextExtractor;
import org.apache.jackrabbit.extractor.TextExtractor;
-import org.apache.jackrabbit.spi.commons.conversion.NamePathResolver;
import org.apache.jackrabbit.spi.Name;
import org.apache.jackrabbit.spi.Path;
import org.apache.jackrabbit.spi.PathFactory;
@@ -55,6 +54,7 @@
import org.apache.lucene.search.Sort;
import org.apache.lucene.search.SortField;
import org.apache.lucene.search.Similarity;
+import org.apache.lucene.search.SortComparatorSource;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.Fieldable;
@@ -66,7 +66,6 @@
import org.w3c.dom.Element;
import javax.jcr.RepositoryException;
-import javax.jcr.NamespaceException;
import javax.jcr.query.InvalidQueryException;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.DocumentBuilder;
@@ -213,11 +212,6 @@
private NamespaceMappings nsMappings;
/**
- * The name and path resolver used internally.
- */
- private NamePathResolver npResolver;
-
- /**
* The location of the search index.
*
SearchIndex is closed and cannot be used
* anymore.
*/
@@ -479,8 +478,10 @@
context.getNamespaceRegistry());
}
}
- npResolver = NamePathResolverImpl.create(nsMappings);
+ scs = new SharedFieldSortComparator(
+ FieldNames.PROPERTIES, context.getItemStateManager(),
+ context.getHierarchyManager(), nsMappings);
indexingConfig = createIndexingConfiguration(nsMappings);
analyzer.setIndexingConfig(indexingConfig);
@@ -728,7 +729,7 @@
public MultiColumnQueryHits executeQuery(SessionImpl session,
AbstractQueryImpl queryImpl,
Query query,
- Name[] orderProps,
+ Path[] orderProps,
boolean[] orderSpecs) throws IOException {
checkOpen();
@@ -913,24 +914,19 @@
* @param orderSpecs the order specs for the properties.
* @return an array of sort fields
*/
- protected SortField[] createSortFields(Name[] orderProps,
+ protected SortField[] createSortFields(Path[] orderProps,
boolean[] orderSpecs) {
List sortFields = new ArrayList();
for (int i = 0; i < orderProps.length; i++) {
- String prop = null;
- if (NameConstants.JCR_SCORE.equals(orderProps[i])) {
+ if (orderProps[i].getLength() == 1
+ && NameConstants.JCR_SCORE.equals(orderProps[i].getNameElement().getName())) {
// 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.
sortFields.add(new SortField(null, SortField.SCORE, orderSpecs[i]));
} else {
- try {
- prop = npResolver.getJCRName(orderProps[i]);
- } catch (NamespaceException e) {
- // will never happen
- }
- sortFields.add(new SortField(prop, SharedFieldSortComparator.PROPERTIES, !orderSpecs[i]));
+ sortFields.add(new SortField(orderProps[i].getString(), scs, !orderSpecs[i]));
}
}
return (SortField[]) sortFields.toArray(new SortField[sortFields.size()]);
Index: src/main/java/org/apache/jackrabbit/core/query/lucene/SharedFieldCache.java
===================================================================
--- src/main/java/org/apache/jackrabbit/core/query/lucene/SharedFieldCache.java (revision 744177)
+++ src/main/java/org/apache/jackrabbit/core/query/lucene/SharedFieldCache.java (working copy)
@@ -26,8 +26,6 @@
import java.util.HashMap;
import java.util.Map;
import java.util.WeakHashMap;
-import java.util.List;
-import java.util.ArrayList;
/**
* Implements a variant of the lucene class org.apache.lucene.search.FieldCacheImpl.
@@ -50,11 +48,6 @@
private static final int SPARSE_FACTOR = 100;
/**
- * All the term values, in natural order.
- */
- public final String[] lookup;
-
- /**
* Terms indexed by document id.
*/
private final String[] terms;
@@ -72,7 +65,7 @@
/**
* Creates one of these objects
*/
- public StringIndex(String[] terms, String[] lookup, int setValues) {
+ public StringIndex(String[] terms, int setValues) {
if (isSparse(terms, setValues)) {
this.sparse = true;
this.terms = null;
@@ -86,7 +79,6 @@
this.terms = terms;
this.termsMap = null;
}
- this.lookup = lookup;
}
public String getTerm(int i) {
@@ -147,7 +139,6 @@
* @param field name of the shared field.
* @param prefix the property name, will be used as term prefix.
* @param comparator the sort comparator instance.
- * @param includeLookup if true provides term lookup in StringIndex.
* @return a StringIndex that contains the field values and order
* information.
* @throws IOException if an error occurs while reading from the index.
@@ -155,8 +146,7 @@
public SharedFieldCache.StringIndex getStringIndex(IndexReader reader,
String field,
String prefix,
- SortComparator comparator,
- boolean includeLookup)
+ SortComparator comparator)
throws IOException {
if (reader instanceof ReadOnlyIndexReader) {
@@ -167,21 +157,12 @@
SharedFieldCache.StringIndex ret = lookup(reader, field, prefix, comparator);
if (ret == null) {
final String[] retArray = new String[reader.maxDoc()];
- List mterms = null;
- if (includeLookup) {
- mterms = new ArrayList();
- }
int setValues = 0;
if (retArray.length > 0) {
TermDocs termDocs = reader.termDocs();
TermEnum termEnum = reader.terms(new Term(field, prefix));
- // documents without a term will have a term number = 0
- // thus will be at the top, this needs to be in sync with
- // the implementation of FieldDocSortedHitQueue
- if (includeLookup) {
- mterms.add(null); // for documents with term number 0
- }
+ char[] tmp = new char[16];
try {
if (termEnum.term() == null) {
throw new RuntimeException("no terms in field " + field);
@@ -192,15 +173,20 @@
break;
}
- // store term text
- if (includeLookup) {
- mterms.add(term.text().substring(prefix.length()));
+ // make sure term is compacted
+ String text = term.text();
+ int len = text.length() - prefix.length();
+ if (tmp.length < len) {
+ // grow tmp
+ tmp = new char[len];
}
+ text.getChars(prefix.length(), text.length(), tmp, 0);
+ String value = new String(tmp, 0, len);
termDocs.seek(termEnum);
while (termDocs.next()) {
setValues++;
- retArray[termDocs.doc()] = term.text().substring(prefix.length());
+ retArray[termDocs.doc()] = value;
}
} while (termEnum.next());
} finally {
@@ -208,11 +194,7 @@
termEnum.close();
}
}
- String[] lookup = null;
- if (includeLookup) {
- lookup = (String[]) mterms.toArray(new String[mterms.size()]);
- }
- SharedFieldCache.StringIndex value = new SharedFieldCache.StringIndex(retArray, lookup, setValues);
+ SharedFieldCache.StringIndex value = new SharedFieldCache.StringIndex(retArray, setValues);
store(reader, field, prefix, comparator, value);
return value;
}
Index: src/main/java/org/apache/jackrabbit/core/query/lucene/SharedFieldSortComparator.java
===================================================================
--- src/main/java/org/apache/jackrabbit/core/query/lucene/SharedFieldSortComparator.java (revision 744177)
+++ src/main/java/org/apache/jackrabbit/core/query/lucene/SharedFieldSortComparator.java (working copy)
@@ -20,12 +20,26 @@
import java.util.ArrayList;
import java.util.List;
-import org.apache.jackrabbit.core.query.lucene.SharedFieldCache.StringIndex;
+import javax.jcr.PropertyType;
+
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.ScoreDocComparator;
import org.apache.lucene.search.SortComparator;
import org.apache.lucene.search.SortField;
+import org.apache.lucene.document.Document;
+import org.apache.jackrabbit.core.state.ItemStateManager;
+import org.apache.jackrabbit.core.state.PropertyState;
+import org.apache.jackrabbit.core.HierarchyManager;
+import org.apache.jackrabbit.core.NodeId;
+import org.apache.jackrabbit.core.PropertyId;
+import org.apache.jackrabbit.core.value.InternalValue;
+import org.apache.jackrabbit.spi.Path;
+import org.apache.jackrabbit.spi.PathFactory;
+import org.apache.jackrabbit.spi.commons.name.PathBuilder;
+import org.apache.jackrabbit.spi.commons.name.PathFactoryImpl;
+import org.apache.jackrabbit.spi.commons.conversion.IllegalNameException;
+import org.apache.jackrabbit.uuid.UUID;
/**
* Implements a SortComparator which knows how to sort on a lucene
@@ -41,171 +55,332 @@
public class SharedFieldSortComparator extends SortComparator {
/**
- * A SharedFieldSortComparator that is based on
- * {@link FieldNames#PROPERTIES}.
+ * The name of the shared field in the lucene index.
*/
- static final SortComparator PROPERTIES = new SharedFieldSortComparator(FieldNames.PROPERTIES);
+ private final String field;
/**
- * The name of the shared field in the lucene index.
+ * The item state manager.
*/
- private final String field;
+ private final ItemStateManager ism;
/**
- * If true ScoreDocComparator will returns term
- * values when {@link org.apache.lucene.search.ScoreDocComparator#sortValue(org.apache.lucene.search.ScoreDoc)}
- * is called, otherwise only a dummy value is returned.
+ * The hierarchy manager on top of {@link #ism}.
*/
- private final boolean createComparatorValues;
+ private final HierarchyManager hmgr;
/**
- * Creates a new SharedFieldSortComparator for a given shared
- * field.
- *
- * @param fieldname the shared field.
+ * The index internal namespace mappings.
*/
- public SharedFieldSortComparator(String fieldname) {
- this(fieldname, false);
- }
+ private final NamespaceMappings nsMappings;
/**
* Creates a new SharedFieldSortComparator for a given shared
* field.
*
- * @param fieldname the shared field.
- * @param createComparatorValues if true creates values
- * for the ScoreDocComparators.
- * @see #createComparatorValues
+ * @param fieldname the shared field.
+ * @param ism the item state manager of this workspace.
+ * @param hmgr the hierarchy manager of this workspace.
+ * @param nsMappings the index internal namespace mappings.
*/
- public SharedFieldSortComparator(String fieldname, boolean createComparatorValues) {
+ public SharedFieldSortComparator(String fieldname,
+ ItemStateManager ism,
+ HierarchyManager hmgr,
+ NamespaceMappings nsMappings) {
this.field = fieldname;
- this.createComparatorValues = createComparatorValues;
+ this.ism = ism;
+ this.hmgr = hmgr;
+ this.nsMappings = nsMappings;
}
/**
* Creates a new ScoreDocComparator for an embedded
* propertyName and a reader.
+ *
* @param reader the index reader.
- * @param propertyName the name of the property to sort.
+ * @param relPath the relative path to the property to sort on as returned
+ * by {@link Path#getString()}.
* @return a ScoreDocComparator for the
- * @throws IOException
- * @throws IOException
+ * @throws IOException if an error occurs while reading from the index.
*/
- public ScoreDocComparator newComparator(final IndexReader reader, final String propertyName) throws IOException {
+ public ScoreDocComparator newComparator(IndexReader reader,
+ String relPath)
+ throws IOException {
+ PathFactory factory = PathFactoryImpl.getInstance();
+ Path p = factory.create(relPath);
+ if (p.getLength() == 1) {
+ try {
+ return new SimpleScoreDocComparator(reader,
+ nsMappings.translatePropertyName(p.getNameElement().getName()));
+ } catch (IllegalNameException e) {
+ throw Util.createIOException(e);
+ }
+ } else {
+ return new RelPathScoreDocComparator(reader, p);
+ }
+ }
- final List readers = new ArrayList();
- getIndexReaders(readers, reader);
+ /**
+ * @throws UnsupportedOperationException always.
+ */
+ protected Comparable getComparable(String termtext) {
+ throw new UnsupportedOperationException();
+ }
- final SharedFieldCache.StringIndex[] indexes = new SharedFieldCache.StringIndex[readers.size()];
+ /**
+ * Checks if reader is of type {@link MultiIndexReader} and if
+ * that's the case calls this method recursively for each reader within the
+ * multi index reader; otherwise the reader is simply added to the list.
+ *
+ * @param readers the list of index readers.
+ * @param reader the reader to check.
+ */
+ private static void getIndexReaders(List readers, IndexReader reader) {
+ if (reader instanceof MultiIndexReader) {
+ IndexReader[] r = ((MultiIndexReader) reader).getIndexReaders();
+ for (int i = 0; i < r.length; i++) {
+ getIndexReaders(readers, r[i]);
+ }
+ } else {
+ readers.add(reader);
+ }
+ }
- int maxDoc = 0;
- final int[] starts = new int[readers.size() + 1];
+ /**
+ * Abstract base class of {@link ScoreDocComparator} implementations.
+ */
+ abstract class AbstractScoreDocComparator implements ScoreDocComparator {
- for (int i = 0; i < readers.size(); i++) {
- IndexReader r = (IndexReader) readers.get(i);
- starts[i] = maxDoc;
- maxDoc += r.maxDoc();
- indexes[i] = SharedFieldCache.INSTANCE.getStringIndex(r, field,
- FieldNames.createNamedValue(propertyName, ""),
- SharedFieldSortComparator.this, createComparatorValues);
- }
- starts[readers.size()] = maxDoc;
+ /**
+ * The index readers.
+ */
+ protected final List readers = new ArrayList();
- return new ScoreDocComparator() {
+ /**
+ * The document number starts for the {@link #readers}.
+ */
+ protected final int[] starts;
- public final int compare(final ScoreDoc i, final ScoreDoc j) {
- int idx1 = readerIndex(i.doc);
- int idx2 = readerIndex(j.doc);
+ public AbstractScoreDocComparator(IndexReader reader)
+ throws IOException {
+ getIndexReaders(readers, reader);
- String iTerm = indexes[idx1].getTerm(i.doc - starts[idx1]);
- String jTerm = indexes[idx2].getTerm(j.doc - starts[idx2]);
+ int maxDoc = 0;
+ this.starts = new int[readers.size() + 1];
- if (iTerm == jTerm) {
- return 0;
- } else if (iTerm == null) {
- return -1;
- } else if (jTerm == null) {
- return 1;
- } else {
- return iTerm.compareTo(jTerm);
- }
+ for (int i = 0; i < readers.size(); i++) {
+ IndexReader r = (IndexReader) readers.get(i);
+ starts[i] = maxDoc;
+ maxDoc += r.maxDoc();
}
+ starts[readers.size()] = maxDoc;
+ }
- /**
- * Returns an empty if no lookup table is available otherwise the
- * index term for the score doc i.
- *
- * @param i
- * the score doc.
- * @return the sort value if available.
- */
- public Comparable sortValue(final ScoreDoc i) {
- if (createComparatorValues) {
- int idx = readerIndex(i.doc);
- return indexes[idx].getTerm(i.doc - starts[idx]);
- } else {
- // return dummy value
- return "";
- }
- }
+ /**
+ * Compares sort values of i and j. If the
+ * sort values have differing types, then the sort order is defined on
+ * the type itself by calling compareTo() on the respective
+ * type class names.
+ *
+ * @param i first score doc.
+ * @param j second score doc.
+ * @return a negative integer if i should come before
+ * ji
+ * should come after j0 if they
+ * are equal
+ */
+ public int compare(ScoreDoc i, ScoreDoc j) {
+ Comparable iTerm = sortValue(i);
+ Comparable jTerm = sortValue(j);
- public int sortType() {
- return SortField.CUSTOM;
+ if (iTerm == jTerm) {
+ return 0;
+ } else if (iTerm == null) {
+ return -1;
+ } else if (jTerm == null) {
+ return 1;
+ } else if (iTerm.getClass() == jTerm.getClass()) {
+ return iTerm.compareTo(jTerm);
+ } else {
+ // differing types -> compare class names
+ String iName = iTerm.getClass().getName();
+ String jName = jTerm.getClass().getName();
+ return iName.compareTo(jName);
}
+ }
- /**
- * Returns the reader index for document n.
- *
- * @param n document number.
- * @return the reader index.
- */
- private int readerIndex(int n) {
- int lo = 0;
- int hi = readers.size() - 1;
+ public int sortType() {
+ return SortField.CUSTOM;
+ }
- 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;
+ /**
+ * Returns the reader index for document n.
+ *
+ * @param n document number.
+ * @return the reader index.
+ */
+ protected 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;
}
-
- };
+ return hi;
+ }
}
/**
- * @throws UnsupportedOperationException always.
+ * A score doc comparator that works for order by clauses with properties
+ * directly on the result nodes.
*/
- protected Comparable getComparable(String termtext) {
- throw new UnsupportedOperationException();
+ private final class SimpleScoreDocComparator extends AbstractScoreDocComparator {
+
+ /**
+ * The term look ups of the index segments.
+ */
+ protected final SharedFieldCache.StringIndex[] indexes;
+
+ public SimpleScoreDocComparator(IndexReader reader,
+ String propertyName)
+ throws IOException {
+ super(reader);
+ this.indexes = new SharedFieldCache.StringIndex[readers.size()];
+
+ for (int i = 0; i < readers.size(); i++) {
+ IndexReader r = (IndexReader) readers.get(i);
+ indexes[i] = SharedFieldCache.INSTANCE.getStringIndex(r, field,
+ FieldNames.createNamedValue(propertyName, ""),
+ SharedFieldSortComparator.this);
+ }
+ }
+
+ /**
+ * Returns the index term for the score doc i.
+ *
+ * @param i the score doc.
+ * @return the sort value if available.
+ */
+ public Comparable sortValue(ScoreDoc i) {
+ int idx = readerIndex(i.doc);
+ return indexes[idx].getTerm(i.doc - starts[idx]);
+ }
}
/**
- * Checks if reader is of type {@link MultiIndexReader} and if
- * that's the case calls this method recursively for each reader within the
- * multi index reader; otherwise the reader is simply added to the list.
- *
- * @param readers the list of index readers.
- * @param reader the reader to check.
+ * A score doc comparator that works with order by clauses that use a
+ * relative path to a property to sort on.
*/
- private void getIndexReaders(List readers, IndexReader reader) {
- if (reader instanceof MultiIndexReader) {
- IndexReader[] r = ((MultiIndexReader) reader).getIndexReaders();
- for (int i = 0; i < r.length; i++) {
- getIndexReaders(readers, r[i]);
+ private final class RelPathScoreDocComparator extends AbstractScoreDocComparator {
+
+ private final Path relPath;
+
+ public RelPathScoreDocComparator(IndexReader reader,
+ Path relPath)
+ throws IOException {
+ super(reader);
+ this.relPath = relPath;
+ }
+
+ /**
+ * Returns the sort value for the given {@link ScoreDoc}. The value is
+ * retrieved from the item state manager.
+ *
+ * @param i the score doc.
+ * @return the sort value for the score doc.
+ */
+ public Comparable sortValue(ScoreDoc i) {
+ try {
+ int idx = readerIndex(i.doc);
+ IndexReader reader = (IndexReader) readers.get(idx);
+ Document doc = reader.document(i.doc - starts[idx], FieldSelectors.UUID);
+ String uuid = doc.get(FieldNames.UUID);
+ Path path = hmgr.getPath(new NodeId(UUID.fromString(uuid)));
+ PathBuilder builder = new PathBuilder(path);
+ builder.addAll(relPath.getElements());
+ PropertyId id = hmgr.resolvePropertyPath(builder.getPath());
+ if (id == null) {
+ return null;
+ }
+ PropertyState state = (PropertyState) ism.getItemState(id);
+ if (state == null) {
+ return null;
+ }
+ InternalValue[] values = state.getValues();
+ if (values.length > 0) {
+ return getComparable(values[0]);
+ }
+ return null;
+ } catch (Exception e) {
+ return null;
}
- } else {
- readers.add(reader);
}
+
+ /**
+ * Returns a comparable for the value.
+ *
+ * @param value an internal value.
+ * @return a comparable for the given value.
+ */
+ private Comparable getComparable(InternalValue value) {
+ switch (value.getType()) {
+ case PropertyType.BINARY:
+ return null;
+ case PropertyType.BOOLEAN:
+ return ComparableBoolean.valueOf(value.getBoolean());
+ case PropertyType.DATE:
+ return new Long(value.getDate().getTimeInMillis());
+ case PropertyType.DOUBLE:
+ return new Double(value.getDouble());
+ case PropertyType.LONG:
+ return new Long(value.getLong());
+ case PropertyType.NAME:
+ return value.getQName().toString();
+ case PropertyType.PATH:
+ return value.getPath().toString();
+ case PropertyType.REFERENCE:
+ case PropertyType.STRING:
+ return value.getString();
+ default:
+ return null;
+ }
+ }
}
+
+ /**
+ * Represents a boolean that implement {@link Comparable}. This class can
+ * be removed when we move to Java 5.
+ */
+ private static final class ComparableBoolean implements Comparable {
+
+ private static final ComparableBoolean TRUE = new ComparableBoolean(true);
+
+ private static final ComparableBoolean FALSE = new ComparableBoolean(false);
+
+ private final boolean value;
+
+ private ComparableBoolean(boolean value) {
+ this.value = value;
+ }
+
+ public int compareTo(Object o) {
+ ComparableBoolean b = (ComparableBoolean) o;
+ return (b.value == value ? 0 : (value ? 1 : -1));
+ }
+
+ static ComparableBoolean valueOf(boolean value) {
+ return value ? TRUE : FALSE;
+ }
+ }
}
Index: src/main/java/org/apache/jackrabbit/core/query/QueryHandlerContext.java
===================================================================
--- src/main/java/org/apache/jackrabbit/core/query/QueryHandlerContext.java (revision 744177)
+++ src/main/java/org/apache/jackrabbit/core/query/QueryHandlerContext.java (working copy)
@@ -21,6 +21,8 @@
import org.apache.jackrabbit.core.state.ItemStateManager;
import org.apache.jackrabbit.core.NodeId;
import org.apache.jackrabbit.core.NamespaceRegistryImpl;
+import org.apache.jackrabbit.core.HierarchyManager;
+import org.apache.jackrabbit.core.HierarchyManagerImpl;
import org.apache.jackrabbit.core.persistence.PersistenceManager;
/**
@@ -41,6 +43,11 @@
private final ItemStateManager stateMgr;
/**
+ * The hierarchy manager on top of {@link #stateMgr}.
+ */
+ private final HierarchyManager hmgr;
+
+ /**
* The underlying persistence manager.
*/
private final PersistenceManager pm;
@@ -103,6 +110,7 @@
NodeId excludedNodeId) {
this.fs = fs;
this.stateMgr = stateMgr;
+ this.hmgr = new HierarchyManagerImpl(rootId, stateMgr);
this.pm = pm;
this.rootId = rootId;
this.ntRegistry = ntRegistry;
@@ -125,6 +133,16 @@
}
/**
+ * Returns the hierarchy manager on top of the item state manager of this
+ * query handler context.
+ *
+ * @return the hierarchy manager.
+ */
+ public HierarchyManager getHierarchyManager() {
+ return hmgr;
+ }
+
+ /**
* @return the underlying persistence manager.
*/
public PersistenceManager getPersistenceManager() {
Index: src/test/java/org/apache/jackrabbit/core/query/OrderByTest.java
===================================================================
--- src/test/java/org/apache/jackrabbit/core/query/OrderByTest.java (revision 744177)
+++ src/test/java/org/apache/jackrabbit/core/query/OrderByTest.java (working copy)
@@ -16,8 +16,17 @@
*/
package org.apache.jackrabbit.core.query;
+import java.util.List;
+import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Calendar;
+
import javax.jcr.Node;
import javax.jcr.RepositoryException;
+import javax.jcr.NodeIterator;
+import javax.jcr.Value;
+import javax.jcr.PropertyType;
import javax.jcr.query.Query;
import javax.jcr.query.QueryResult;
@@ -51,4 +60,208 @@
result = q.execute();
checkResult(result, 3);
}
+
+ public void testChildAxisString() throws RepositoryException {
+ checkChildAxis(new Value[]{getValue("a"), getValue("b"), getValue("c")});
+ }
+
+ public void testChildAxisLong() throws RepositoryException {
+ checkChildAxis(new Value[]{getValue(1), getValue(2), getValue(3)});
+ }
+
+ public void testChildAxisDouble() throws RepositoryException {
+ checkChildAxis(new Value[]{getValue(1.0), getValue(2.0), getValue(3.0)});
+ }
+
+ public void testChildAxisBoolean() throws RepositoryException {
+ checkChildAxis(new Value[]{getValue(false), getValue(true)});
+ }
+
+ public void testChildAxisCalendar() throws RepositoryException {
+ Calendar c1 = Calendar.getInstance();
+ Calendar c2 = Calendar.getInstance();
+ c2.add(Calendar.MINUTE, 1);
+ Calendar c3 = Calendar.getInstance();
+ c3.add(Calendar.MINUTE, 2);
+ checkChildAxis(new Value[]{getValue(c1), getValue(c2), getValue(c3)});
+ }
+
+ public void testChildAxisName() throws RepositoryException {
+ checkChildAxis(new Value[]{getNameValue("a"), getNameValue("b"), getNameValue("c")});
+ }
+
+ public void testChildAxisPath() throws RepositoryException {
+ checkChildAxis(new Value[]{getPathValue("a"), getPathValue("b"), getPathValue("c")});
+ }
+
+ public void testChildAxisDeep() throws RepositoryException {
+ Node n1 = testRootNode.addNode("node1");
+ n1.addNode("a").addNode("b"); // no property
+ Node n2 = testRootNode.addNode("node2");
+ n2.addNode("a").addNode("b").addNode("c").setProperty("prop", "a");
+ Node n3 = testRootNode.addNode("node2");
+ n3.addNode("a").addNode("b").addNode("c").setProperty("prop", "b");
+ testRootNode.save();
+
+ List expected = Arrays.asList(new String[]{n1.getPath(), n2.getPath(), n3.getPath()});
+ String xpath = testPath + "/* order by a/b/c/@prop";
+ assertEquals(expected, collectPaths(executeQuery(xpath)));
+
+ // descending
+ Collections.reverse(expected);
+ xpath = testPath + "/* order by a/b/c/@prop descending";
+ assertEquals(expected, collectPaths(executeQuery(xpath)));
+ }
+
+ public void testChildAxisNoValue() throws RepositoryException {
+ Node n1 = testRootNode.addNode("node1");
+ n1.addNode("child").setProperty("prop", "a");
+ Node n2 = testRootNode.addNode("node2");
+ n2.addNode("child");
+ testRootNode.save();
+
+ List expected = Arrays.asList(new String[]{n2.getPath(), n1.getPath()});
+ String xpath = testPath + "/* order by child/@prop";
+ assertEquals(expected, collectPaths(executeQuery(xpath)));
+
+ // descending
+ Collections.reverse(expected);
+ xpath = testPath + "/* order by child/@prop descending";
+ assertEquals(expected, collectPaths(executeQuery(xpath)));
+
+ // reverse order in content
+ n1.getNode("child").getProperty("prop").remove();
+ n2.getNode("child").setProperty("prop", "a");
+ testRootNode.save();
+
+ Collections.reverse(expected);
+ assertEquals(expected, collectPaths(executeQuery(xpath)));
+ }
+
+ public void testChildAxisMixedTypes() throws RepositoryException {
+ // when differing types are used then the class name of the type
+ // is used for comparison:
+ // java.lang.Double < java.lang.Integer
+ checkChildAxis(new Value[]{getValue(2.0), getValue(1)});
+ }
+
+ public void disabled_testPerformance() throws RepositoryException {
+ createNodes(testRootNode, 10, 4, 0, new NodeCreationCallback() {
+ public void nodeCreated(Node node, int count) throws
+ RepositoryException {
+ node.addNode("child").setProperty("property", "value" + count);
+ // save once in a while
+ if (count % 1000 == 0) {
+ superuser.save();
+ System.out.println("added " + count + " nodes so far.");
+ }
+ }
+ });
+ superuser.save();
+
+ String xpath = testPath + "//*[child/@property] order by child/@property";
+ for (int i = 0; i < 3; i++) {
+ long time = System.currentTimeMillis();
+ Query query = qm.createQuery(xpath, Query.XPATH);
+ ((QueryImpl) query).setLimit(20);
+ query.execute().getNodes().getSize();
+ time = System.currentTimeMillis() - time;
+ System.out.println("executed query in " + time + " ms.");
+ }
+ }
+
+ //------------------------------< helper >----------------------------------
+
+ private Value getValue(String value) throws RepositoryException {
+ return superuser.getValueFactory().createValue(value);
+ }
+
+ private Value getValue(long value) throws RepositoryException {
+ return superuser.getValueFactory().createValue(value);
+ }
+
+ private Value getValue(double value) throws RepositoryException {
+ return superuser.getValueFactory().createValue(value);
+ }
+
+ private Value getValue(boolean value) throws RepositoryException {
+ return superuser.getValueFactory().createValue(value);
+ }
+
+ private Value getValue(Calendar value) throws RepositoryException {
+ return superuser.getValueFactory().createValue(value);
+ }
+
+ private Value getNameValue(String value) throws RepositoryException {
+ return superuser.getValueFactory().createValue(value, PropertyType.NAME);
+ }
+
+ private Value getPathValue(String value) throws RepositoryException {
+ return superuser.getValueFactory().createValue(value, PropertyType.PATH);
+ }
+
+ /**
+ * Checks if order by with a relative path works on the the passed values.
+ * The values are expected to be in ascending order.
+ *
+ * @param values the values in ascending order.
+ * @throws RepositoryException if an error occurs.
+ */
+ private void checkChildAxis(Value[] values) throws RepositoryException {
+ List expected = new ArrayList();
+ for (int i = 0; i < values.length; i++) {
+ Node n = testRootNode.addNode("node" + i);
+ expected.add(n.getPath());
+ n.addNode("child").setProperty("prop", values[i]);
+ }
+ testRootNode.save();
+
+ String xpath = testPath + "/* order by child/@prop";
+ assertEquals(expected, collectPaths(executeQuery(xpath)));
+
+ // descending
+ Collections.reverse(expected);
+ xpath = testPath + "/* order by child/@prop descending";
+ assertEquals(expected, collectPaths(executeQuery(xpath)));
+
+ // reverse order in content
+ Collections.reverse(Arrays.asList(values));
+ for (int i = 0; i < values.length; i++) {
+ Node child = testRootNode.getNode("node" + i).getNode("child");
+ child.setProperty("prop", values[i]);
+ }
+ testRootNode.save();
+
+ Collections.reverse(expected);
+ assertEquals(expected, collectPaths(executeQuery(xpath)));
+ }
+
+ private static List collectPaths(QueryResult result)
+ throws RepositoryException {
+ List paths = new ArrayList();
+ for (NodeIterator it = result.getNodes(); it.hasNext(); ) {
+ paths.add(it.nextNode().getPath());
+ }
+ return paths;
+ }
+
+ private int createNodes(Node n, int nodesPerLevel, int levels,
+ int count, NodeCreationCallback callback)
+ throws RepositoryException {
+ levels--;
+ for (int i = 0; i < nodesPerLevel; i++) {
+ Node child = n.addNode("node" + i);
+ count++;
+ callback.nodeCreated(child, count);
+ if (levels > 0) {
+ count = createNodes(child, nodesPerLevel, levels, count, callback);
+ }
+ }
+ return count;
+ }
+
+ private static interface NodeCreationCallback {
+
+ public void nodeCreated(Node node, int count) throws RepositoryException;
+ }
}