Return-Path: Delivered-To: apmail-jackrabbit-commits-archive@www.apache.org Received: (qmail 35958 invoked from network); 16 Feb 2009 10:52:23 -0000 Received: from hermes.apache.org (HELO mail.apache.org) (140.211.11.2) by minotaur.apache.org with SMTP; 16 Feb 2009 10:52:23 -0000 Received: (qmail 49152 invoked by uid 500); 16 Feb 2009 10:52:23 -0000 Delivered-To: apmail-jackrabbit-commits-archive@jackrabbit.apache.org Received: (qmail 49117 invoked by uid 500); 16 Feb 2009 10:52:23 -0000 Mailing-List: contact commits-help@jackrabbit.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@jackrabbit.apache.org Delivered-To: mailing list commits@jackrabbit.apache.org Received: (qmail 49108 invoked by uid 99); 16 Feb 2009 10:52:23 -0000 Received: from athena.apache.org (HELO athena.apache.org) (140.211.11.136) by apache.org (qpsmtpd/0.29) with ESMTP; Mon, 16 Feb 2009 02:52:23 -0800 X-ASF-Spam-Status: No, hits=-2000.0 required=10.0 tests=ALL_TRUSTED X-Spam-Check-By: apache.org Received: from [140.211.11.4] (HELO eris.apache.org) (140.211.11.4) by apache.org (qpsmtpd/0.29) with ESMTP; Mon, 16 Feb 2009 10:52:16 +0000 Received: by eris.apache.org (Postfix, from userid 65534) id 429D323888D5; Mon, 16 Feb 2009 10:51:56 +0000 (UTC) Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: svn commit: r744889 - in /jackrabbit/trunk/jackrabbit-core/src: main/java/org/apache/jackrabbit/core/query/ main/java/org/apache/jackrabbit/core/query/lucene/ test/java/org/apache/jackrabbit/core/query/ Date: Mon, 16 Feb 2009 10:51:44 -0000 To: commits@jackrabbit.apache.org From: mreutegg@apache.org X-Mailer: svnmailer-1.0.8 Message-Id: <20090216105156.429D323888D5@eris.apache.org> X-Virus-Checked: Checked by ClamAV on apache.org Author: mreutegg Date: Mon Feb 16 10:51:37 2009 New Revision: 744889 URL: http://svn.apache.org/viewvc?rev=744889&view=rev Log: JCR-800: Child Axis support in order by clause Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/QueryHandlerContext.java jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/QueryImpl.java jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/QueryObjectModelImpl.java jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/QueryResultImpl.java jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/SearchIndex.java jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/SharedFieldCache.java jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/SharedFieldSortComparator.java jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/OrderByTest.java Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/QueryHandlerContext.java URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/QueryHandlerContext.java?rev=744889&r1=744888&r2=744889&view=diff ============================================================================== --- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/QueryHandlerContext.java (original) +++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/QueryHandlerContext.java Mon Feb 16 10:51:37 2009 @@ -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() { Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/QueryImpl.java URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/QueryImpl.java?rev=744889&r1=744888&r2=744889&view=diff ============================================================================== --- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/QueryImpl.java (original) +++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/QueryImpl.java Mon Feb 16 10:51:37 2009 @@ -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(); } Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/QueryObjectModelImpl.java URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/QueryObjectModelImpl.java?rev=744889&r1=744888&r2=744889&view=diff ============================================================================== --- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/QueryObjectModelImpl.java (original) +++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/QueryObjectModelImpl.java Mon Feb 16 10:51:37 2009 @@ -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(), Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/QueryResultImpl.java URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/QueryResultImpl.java?rev=744889&r1=744888&r2=744889&view=diff ============================================================================== --- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/QueryResultImpl.java (original) +++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/QueryResultImpl.java Mon Feb 16 10:51:37 2009 @@ -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, Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/SearchIndex.java URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/SearchIndex.java?rev=744889&r1=744888&r2=744889&view=diff ============================================================================== --- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/SearchIndex.java (original) +++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/SearchIndex.java Mon Feb 16 10:51:37 2009 @@ -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. *

* Note: This is a mandatory parameter! @@ -427,6 +421,11 @@ private int termInfosIndexDivisor = DEFAULT_TERM_INFOS_INDEX_DIVISOR; /** + * The sort comparator source for indexed properties. + */ + private SortComparatorSource scs; + + /** * Indicates if this 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()]); Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/SharedFieldCache.java URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/SharedFieldCache.java?rev=744889&r1=744888&r2=744889&view=diff ============================================================================== --- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/SharedFieldCache.java (original) +++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/SharedFieldCache.java Mon Feb 16 10:51:37 2009 @@ -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; } Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/SharedFieldSortComparator.java URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/SharedFieldSortComparator.java?rev=744889&r1=744888&r2=744889&view=diff ============================================================================== --- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/SharedFieldSortComparator.java (original) +++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/SharedFieldSortComparator.java Mon Feb 16 10:51:37 2009 @@ -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,146 +55,69 @@ 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 { - - final List readers = new ArrayList(); - getIndexReaders(readers, reader); - - final SharedFieldCache.StringIndex[] indexes = new SharedFieldCache.StringIndex[readers.size()]; - - int maxDoc = 0; - final int[] starts = new int[readers.size() + 1]; - - 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; - - return new ScoreDocComparator() { - - public final int compare(final ScoreDoc i, final ScoreDoc j) { - int idx1 = readerIndex(i.doc); - int idx2 = readerIndex(j.doc); - - String iTerm = indexes[idx1].getTerm(i.doc - starts[idx1]); - String jTerm = indexes[idx2].getTerm(j.doc - starts[idx2]); - - if (iTerm == jTerm) { - return 0; - } else if (iTerm == null) { - return -1; - } else if (jTerm == null) { - return 1; - } else { - return iTerm.compareTo(jTerm); - } + 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); } - - /** - * 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 ""; - } - } - - public int sortType() { - return SortField.CUSTOM; - } - - /** - * 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; - - 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; - } - - }; + } else { + return new RelPathScoreDocComparator(reader, p); + } } /** @@ -198,7 +135,7 @@ * @param readers the list of index readers. * @param reader the reader to check. */ - private void getIndexReaders(List readers, IndexReader reader) { + 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++) { @@ -208,4 +145,242 @@ readers.add(reader); } } + + /** + * Abstract base class of {@link ScoreDocComparator} implementations. + */ + abstract class AbstractScoreDocComparator implements ScoreDocComparator { + + /** + * The index readers. + */ + protected final List readers = new ArrayList(); + + /** + * The document number starts for the {@link #readers}. + */ + protected final int[] starts; + + public AbstractScoreDocComparator(IndexReader reader) + throws IOException { + getIndexReaders(readers, reader); + + int maxDoc = 0; + this.starts = new int[readers.size() + 1]; + + for (int i = 0; i < readers.size(); i++) { + IndexReader r = (IndexReader) readers.get(i); + starts[i] = maxDoc; + maxDoc += r.maxDoc(); + } + starts[readers.size()] = maxDoc; + } + + /** + * 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 + * j
a positive integer if i + * should come after j
0 if they + * are equal + */ + public int compare(ScoreDoc i, ScoreDoc j) { + Comparable iTerm = sortValue(i); + Comparable jTerm = sortValue(j); + + 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); + } + } + + public int sortType() { + return SortField.CUSTOM; + } + + /** + * 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; + } + } + + /** + * A score doc comparator that works for order by clauses with properties + * directly on the result nodes. + */ + 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]); + } + } + + /** + * A score doc comparator that works with order by clauses that use a + * relative path to a property to sort on. + */ + 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; + } + } + + /** + * 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; + } + } } Modified: jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/OrderByTest.java URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/OrderByTest.java?rev=744889&r1=744888&r2=744889&view=diff ============================================================================== --- jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/OrderByTest.java (original) +++ jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/OrderByTest.java Mon Feb 16 10:51:37 2009 @@ -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; + } }