Index: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/DerefQuery.java =================================================================== --- jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/DerefQuery.java (revision 790821) +++ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/DerefQuery.java (working copy) @@ -114,7 +114,15 @@ * @return 'DerefQuery'. */ public String toString(String field) { - return "DerefQuery"; + StringBuffer sb = new StringBuffer(); + sb.append("DerefQuery("); + sb.append(refProperty); + sb.append(", "); + sb.append(contextQuery); + sb.append(", "); + sb.append(nameTest); + sb.append(")"); + return sb.toString(); } /** @@ -291,6 +299,14 @@ throw new UnsupportedOperationException(); } + /** + * 1. do context query + * 2. go through each document from the query + * 3. find reference property UUIDs + * 4. Use UUIDs to find document number + * 5. Use the name test to filter the documents + * @throws IOException + */ private void calculateChildren() throws IOException { if (uuids == null) { uuids = new ArrayList(); Index: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/LuceneQueryBuilder.java =================================================================== --- jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/LuceneQueryBuilder.java (revision 790821) +++ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/LuceneQueryBuilder.java (working copy) @@ -41,11 +41,11 @@ import org.apache.jackrabbit.core.state.ItemStateManager; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.Path; -import org.apache.jackrabbit.spi.commons.conversion.MalformedPathException; +import org.apache.jackrabbit.spi.PathFactory; import org.apache.jackrabbit.spi.commons.conversion.NameException; import org.apache.jackrabbit.spi.commons.conversion.NamePathResolver; import org.apache.jackrabbit.spi.commons.name.NameConstants; -import org.apache.jackrabbit.spi.commons.name.PathBuilder; +import org.apache.jackrabbit.spi.commons.name.PathFactoryImpl; import org.apache.jackrabbit.spi.commons.query.AndQueryNode; import org.apache.jackrabbit.spi.commons.query.DefaultQueryNodeVisitor; import org.apache.jackrabbit.spi.commons.query.DerefQueryNode; @@ -68,11 +68,11 @@ import org.apache.jackrabbit.util.XMLChar; import org.apache.lucene.analysis.Analyzer; import org.apache.lucene.index.Term; +import org.apache.lucene.queryParser.ParseException; +import org.apache.lucene.queryParser.QueryParser; import org.apache.lucene.search.BooleanQuery; import org.apache.lucene.search.Query; import org.apache.lucene.search.BooleanClause.Occur; -import org.apache.lucene.queryParser.QueryParser; -import org.apache.lucene.queryParser.ParseException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -90,6 +90,16 @@ private static final Logger log = LoggerFactory.getLogger(LuceneQueryBuilder.class); /** + * The path factory instance. + */ + private static final PathFactory PATH_FACTORY = PathFactoryImpl.getInstance(); + + /** + * The name of a parent path element. + */ + private static final Name PARENT_ELEMENT_NAME = PATH_FACTORY.getParentElement().getName(); + + /** * Root node of the abstract query tree */ private final QueryRootNode root; @@ -494,6 +504,10 @@ NameQuery nameTest = null; if (node.getNameTest() != null) { + if (node.getNameTest().equals(PARENT_ELEMENT_NAME)) { + andQuery.add(new ParentAxisQuery(context, null, indexFormatVersion, nsMappings), Occur.MUST); + return andQuery; + } nameTest = new NameQuery(node.getNameTest(), indexFormatVersion, nsMappings); } @@ -587,6 +601,16 @@ } public Object visit(RelationQueryNode node, Object data) throws RepositoryException { + PathQueryNode relPath = node.getRelativePath(); + if (relPath == null + && node.getOperation() != QueryConstants.OPERATION_SIMILAR + && node.getOperation() != QueryConstants.OPERATION_SPELLCHECK) { + exceptions.add(new InvalidQueryException("@* not supported in predicate")); + return data; + } + LocationStepQueryNode[] steps = relPath.getPathSteps(); + Name propertyName = steps[steps.length - 1].getNameTest(); + Query query; String[] stringValues = new String[1]; switch (node.getValueType()) { @@ -608,7 +632,6 @@ || node.getOperation() == QueryConstants.OPERATION_NE_GENERAL || node.getOperation() == QueryConstants.OPERATION_NE_VALUE) { // only use coercing on non-range operations - Name propertyName = node.getRelativePath().getNameElement().getName(); stringValues = getStringValues(propertyName, node.getStringValue()); } else { stringValues[0] = node.getStringValue(); @@ -622,13 +645,6 @@ + node.getValueType()); } - if (node.getRelativePath() == null - && node.getOperation() != QueryConstants.OPERATION_SIMILAR - && node.getOperation() != QueryConstants.OPERATION_SPELLCHECK) { - exceptions.add(new InvalidQueryException("@* not supported in predicate")); - return data; - } - // get property transformation final int[] transform = new int[]{TransformConstants.TRANSFORM_NONE}; node.acceptOperands(new DefaultQueryNodeVisitor() { @@ -642,37 +658,24 @@ } }, null); - Path relPath = node.getRelativePath(); if (node.getOperation() == QueryConstants.OPERATION_SIMILAR) { // this is a bit ugly: - // add the name of a dummy property because relPath actually + // use the name of a dummy property because relPath actually // references a property. whereas the relPath of the similar // operation references a node - PathBuilder builder; - if (relPath == null) { - builder = new PathBuilder(); - } else { - builder = new PathBuilder(relPath); - } - builder.addLast(NameConstants.JCR_PRIMARYTYPE); - try { - relPath = builder.getPath(); - } catch (MalformedPathException e) { - // will never happen - } + propertyName = NameConstants.JCR_PRIMARYTYPE; } String field = ""; try { - field = resolver.getJCRName(relPath.getNameElement().getName()); + field = resolver.getJCRName(propertyName); } catch (NamespaceException e) { // should never happen exceptions.add(e); } // support for fn:name() - Name propName = relPath.getNameElement().getName(); - if (propName.getNamespaceURI().equals(SearchManager.NS_FN_URI) - && propName.getLocalName().equals("name()")) { + if (propertyName.getNamespaceURI().equals(SearchManager.NS_FN_URI) + && propertyName.getLocalName().equals("name()")) { if (node.getValueType() != QueryConstants.TYPE_STRING) { exceptions.add(new InvalidQueryException("Name function can " + "only be used in conjunction with a string literal")); @@ -864,41 +867,115 @@ } } - if (relPath.getLength() > 1) { + if (steps.length > 1) { // child axis in relation - Path.Element[] elements = relPath.getElements(); // elements.length - 1 = property name // elements.length - 2 = last child axis name test - for (int i = elements.length - 2; i >= 0; i--) { + boolean selectParent = true; + for (int i = steps.length - 2; i >= 0; i--) { + LocationStepQueryNode step = steps[i]; Name name = null; - if (!elements[i].getName().equals(RelationQueryNode.STAR_NAME_TEST)) { - name = elements[i].getName(); + if (!RelationQueryNode.STAR_NAME_TEST.equals(steps[i].getNameTest())) { + name = steps[i].getNameTest(); } - if (i == elements.length - 2) { - // join name test with property query if there is one - if (name != null) { - Query nameTest = new NameQuery(name, - indexFormatVersion, nsMappings); - BooleanQuery and = new BooleanQuery(); - and.add(query, Occur.MUST); - and.add(nameTest, Occur.MUST); - query = and; + if (i == steps.length - 2) { + if (step instanceof DerefQueryNode) { + query = createPredicateDeref(query, (DerefQueryNode) step, data); + if (steps.length == 2) { + selectParent = false; + } + } else if (step instanceof LocationStepQueryNode) { + // join name test with property query if there is one + if (name != null) { + if (!name.equals(PARENT_ELEMENT_NAME)) { + Query nameTest = new NameQuery(name, + indexFormatVersion, nsMappings); + BooleanQuery and = new BooleanQuery(); + and.add(query, Occur.MUST); + and.add(nameTest, Occur.MUST); + + query = and; + } else { + // If we're searching the parent, we want to return the child axis, + // not the parent because this is part of the predicate. For instance, + // if the query is //child[../base], this part of the code is operating + // on the "../base" portion. So we want to return all the child nodes + // of "base", which will then be matched against the non predicate part. + query = new ChildAxisQuery(sharedItemMgr, + query, + null, + indexFormatVersion, + nsMappings); + selectParent = false; + } + } else { + // otherwise the query can be used as is + } + } + } else if (name != null && name.equals(PARENT_ELEMENT_NAME)) { + // We need to select one of the properties if we haven't already. + if (selectParent) { + query = new ParentAxisQuery(query, null, + indexFormatVersion, nsMappings); + + selectParent = false; + } + + // See the note above on searching parents + query = new ChildAxisQuery(sharedItemMgr, + query, + null, + indexFormatVersion, + nsMappings); + } else { + if (step instanceof LocationStepQueryNode) { + query = new ParentAxisQuery(query, name, indexFormatVersion, nsMappings); } else { - // otherwise the query can be used as is + throw new UnsupportedOperationException(); } - } else { - query = new ParentAxisQuery(query, name, - indexFormatVersion, nsMappings); } } // finally select the parent of the selected nodes - query = new ParentAxisQuery(query, null, - indexFormatVersion, nsMappings); + if (selectParent) { + query = new ParentAxisQuery(query, null, indexFormatVersion, nsMappings); + } } return query; } + + public Query createPredicateDeref(Query subQuery, DerefQueryNode node, Object data) throws RepositoryException { + Query context = (Query) data; + + if (context == null) { + exceptions.add(new IllegalArgumentException("Unsupported query")); + } + try { + String refProperty = resolver.getJCRName(node.getRefProperty()); + + context = new PredicateDerefQuery(subQuery, refProperty, node.getNameTest(), + indexFormatVersion, nsMappings); + + // attach predicates + Object[] predicates = node.acceptOperands(this, data); + if (predicates.length > 0) { + BooleanQuery andQuery = new BooleanQuery(); + for (int i = 0; i < predicates.length; i++) { + andQuery.add((Query) predicates[i], Occur.MUST); + } + andQuery.add(context, Occur.MUST); + context = andQuery; + } + + } catch (NamespaceException e) { + // should never happen + exceptions.add(e); + } + + return context; + } + public Object visit(OrderQueryNode node, Object data) { return data; } Index: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/ParentAxisQuery.java =================================================================== --- jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/ParentAxisQuery.java (revision 790821) +++ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/ParentAxisQuery.java (working copy) @@ -124,7 +124,13 @@ * @return 'ParentAxisQuery'. */ public String toString(String field) { - return "ParentAxisQuery"; + StringBuffer sb = new StringBuffer(); + sb.append("ParentAxisQuery("); + sb.append(contextQuery); + sb.append(", "); + sb.append(nameTest); + sb.append(")"); + return sb.toString(); } //-----------------------< ParentAxisWeight >------------------------------- Index: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/PredicateDerefQuery.java =================================================================== --- jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/PredicateDerefQuery.java (revision 0) +++ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/PredicateDerefQuery.java (revision 0) @@ -0,0 +1,375 @@ +/* + * 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; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.BitSet; +import java.util.List; +import java.util.Set; + +import org.apache.jackrabbit.spi.Name; +import org.apache.lucene.index.IndexReader; +import org.apache.lucene.index.Term; +import org.apache.lucene.index.TermDocs; +import org.apache.lucene.search.Explanation; +import org.apache.lucene.search.HitCollector; +import org.apache.lucene.search.Query; +import org.apache.lucene.search.Scorer; +import org.apache.lucene.search.Searcher; +import org.apache.lucene.search.Similarity; +import org.apache.lucene.search.Weight; + +/** + * Implements a Lucene Query which returns the nodes which have a + * reference property which matches the nodes of the subquery. + */ +public class PredicateDerefQuery extends Query { + + /** + * The context query + */ + private final Query subQuery; + + /** + * The name of the reference property. + */ + private final String refProperty; + + /** + * The nameTest to apply on target node, or null if all + * target nodes should be selected. + */ + private final Name nameTest; + + /** + * The index format version. + */ + private final IndexFormatVersion version; + + /** + * The internal namespace mappings. + */ + private final NamespaceMappings nsMappings; + + /** + * The scorer of the context query + */ + private Scorer subQueryScorer; + + /** + * The scorer of the name test query + */ + private Scorer nameTestScorer; + /** + * Creates a new DerefQuery based on a context + * query. + * + * @param context the context for this query. + * @param subQuery TODO + * @param refProperty the name of the reference property. + * @param nameTest a name test or null if any node is + * selected. + * @param version the index format version. + * @param nsMappings the namespace mappings. + */ + PredicateDerefQuery(Query subQuery, String refProperty, + Name nameTest, IndexFormatVersion version, NamespaceMappings nsMappings) { + this.subQuery = subQuery; + this.refProperty = refProperty; + this.nameTest = nameTest; + this.version = version; + this.nsMappings = nsMappings; + } + + /** + * Creates a Weight instance for this query. + * + * @param searcher the Searcher instance to use. + * @return a DerefWeight. + */ + protected Weight createWeight(Searcher searcher) { + return new DerefWeight(searcher); + } + + /** + * Returns PredicateDerefQuery(subQuery, referenceNodeProperty, nameTest) + * + * @param field the name of a field. + * @return 'DerefQuery'. + */ + public String toString(String field) { + StringBuffer sb = new StringBuffer(); + sb.append("PredicateDerefQuery("); + sb.append(subQuery); + sb.append(", "); + sb.append(nameTest); + sb.append(", "); + sb.append(refProperty); + sb.append(")"); + return sb.toString(); + } + + + /** + * {@inheritDoc} + */ + public void extractTerms(Set terms) { + // no terms to extract + } + + /** + * {@inheritDoc} + */ + public Query rewrite(IndexReader reader) throws IOException { + Query cQuery = subQuery.rewrite(reader); + if (cQuery == subQuery) { + return this; + } else { + return new PredicateDerefQuery(subQuery, refProperty, nameTest, version, nsMappings); + } + } + + //-------------------< DerefWeight >------------------------------------ + + /** + * The Weight implementation for this DerefQuery. + */ + private class DerefWeight implements Weight { + + /** + * The searcher in use + */ + private final Searcher searcher; + + /** + * Creates a new DerefWeight instance using + * searcher. + * + * @param searcher a Searcher instance. + */ + private DerefWeight(Searcher searcher) { + this.searcher = searcher; + } + + /** + * Returns this DerefQuery. + * + * @return this DerefQuery. + */ + public Query getQuery() { + return PredicateDerefQuery.this; + } + + /** + * {@inheritDoc} + */ + public float getValue() { + return 1.0f; + } + + /** + * {@inheritDoc} + */ + public float sumOfSquaredWeights() throws IOException { + return 1.0f; + } + + /** + * {@inheritDoc} + */ + public void normalize(float norm) { + } + + /** + * Creates a scorer for this DerefQuery. + * + * @param reader a reader for accessing the index. + * @return a DerefScorer. + * @throws IOException if an error occurs while reading from the index. + */ + public Scorer scorer(IndexReader reader) throws IOException { + subQueryScorer = subQuery.weight(searcher).scorer(reader); + if (nameTest != null) { + nameTestScorer = new NameQuery(nameTest, version, nsMappings).weight(searcher).scorer(reader); + } + return new DerefScorer(searcher.getSimilarity(), reader); + } + + /** + * {@inheritDoc} + */ + public Explanation explain(IndexReader reader, int doc) throws IOException { + return new Explanation(); + } + } + + //----------------------< DerefScorer >--------------------------------- + + /** + * Implements a Scorer for this DerefQuery. + */ + private class DerefScorer extends Scorer { + + /** + * An IndexReader to access the index. + */ + private final IndexReader reader; + + /** + * BitSet storing the id's of selected documents + */ + private final BitSet subQueryHits; + + /** + * BitSet storing the id's of selected documents + */ + private final BitSet hits; + + /** + * List of UUIDs of selected nodes + */ + private List uuids = null; + + + /** + * The next document id to return + */ + private int nextDoc = -1; + + /** + * Creates a new DerefScorer. + * + * @param similarity the Similarity instance to use. + * @param reader for index access. + */ + protected DerefScorer(Similarity similarity, IndexReader reader) { + super(similarity); + this.reader = reader; + this.hits = new BitSet(reader.maxDoc()); + this.subQueryHits = new BitSet(reader.maxDoc()); + } + + /** + * {@inheritDoc} + */ + public boolean next() throws IOException { + calculateChildren(); + nextDoc = hits.nextSetBit(nextDoc + 1); + return nextDoc > -1; + } + + /** + * {@inheritDoc} + */ + public int doc() { + return nextDoc; + } + + /** + * {@inheritDoc} + */ + public float score() throws IOException { + return 1.0f; + } + + /** + * {@inheritDoc} + */ + public boolean skipTo(int target) throws IOException { + calculateChildren(); + nextDoc = hits.nextSetBit(target); + return nextDoc > -1; + } + + /** + * {@inheritDoc} + * + * @throws UnsupportedOperationException this implementation always + * throws an UnsupportedOperationException. + */ + public Explanation explain(int doc) throws IOException { + throw new UnsupportedOperationException(); + } + + + /** + * Perform the sub query + * For each reference property UUID + * - find document number + * - if document # is in subquery bitset add to bit set + * Use the name test to filter the documents + * @throws IOException + */ + private void calculateChildren() throws IOException { + if (uuids == null) { + uuids = new ArrayList(); +// subQueryHits.clear(); +// hits.clear(); + subQueryScorer.score(new HitCollector() { + public void collect(int doc, float score) { + subQueryHits.set(doc); + } + }); + + TermDocs termDocs = reader.termDocs(new Term(FieldNames.PROPERTIES_SET, refProperty)); + String prefix = FieldNames.createNamedValue(refProperty, ""); + while (termDocs.next()) { + int doc = termDocs.doc(); + + String[] values = reader.document(doc).getValues(FieldNames.PROPERTIES); + if (values == null) { + // no reference properties at all on this node + continue; + } + for (int v = 0; v < values.length; v++) { + if (values[v].startsWith(prefix)) { + String uuid = values[v].substring(prefix.length()); + + TermDocs node = reader.termDocs(new Term(FieldNames.UUID, uuid)); + try { + while (node.next()) { + if (subQueryHits.get(node.doc())) { + hits.set(doc); + } + } + } finally { + node.close(); + } + } + } + } + + // collect nameTest hits + final BitSet nameTestHits = new BitSet(); + if (nameTestScorer != null) { + nameTestScorer.score(new HitCollector() { + public void collect(int doc, float score) { + nameTestHits.set(doc); + } + }); + } + + // filter out the target nodes that do not match the name test + // if there is any name test at all. + if (nameTestScorer != null) { + hits.and(nameTestHits); + } + } + } + } +} Property changes on: jackrabbit-core\src\main\java\org\apache\jackrabbit\core\query\lucene\PredicateDerefQuery.java ___________________________________________________________________ Added: svn:eol-style + native Index: jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/DerefTest.java =================================================================== --- jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/DerefTest.java (revision 790821) +++ jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/DerefTest.java (working copy) @@ -137,6 +137,17 @@ new Node[]{microsoft}); } + public void testDerefInPredicate() throws RepositoryException { + executeXPathQuery(testPath + "/people//*[jcr:deref(@worksfor, '*')/@ceo='McNealy']", + new Node[]{andrew, eric}); + + executeXPathQuery("//*[people/jcr:deref(@worksfor, '*')/@ceo='McNealy']", + new Node[]{testRootNode}); + +// executeXPathQuery("//*[jcr:contains(people/jcr:deref(@worksfor, '*'),'ballmer')]", +// new Node[]{testRootNode}); + } + /** * Checks if jcr:deref works when dereferencing into the version storage. */ Index: jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/ParentNodeTest.java =================================================================== --- jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/ParentNodeTest.java (revision 0) +++ jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/ParentNodeTest.java (revision 0) @@ -0,0 +1,102 @@ +/* + * 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.RepositoryException; +import javax.jcr.query.Query; +import javax.jcr.query.QueryResult; + +/** + * QueryResultTest tests various methods on the + * NodeIterator returned by a QueryResult. + */ +public class ParentNodeTest extends AbstractQueryTest { + + protected void setUp() throws Exception { + super.setUp(); + + // creates the following test structure: + // + base (foo1=bar1) + // + child (foo2=bar2) + // + child2 (foo2=bar2) + // + child3 (foo2=bar2) + Node base = testRootNode.addNode("base"); + base.setProperty("foo1", "bar1"); + + base.addNode("child").setProperty("foo2", "bar2"); + base.addNode("child2").setProperty("foo2", "bar2"); + base.addNode("child3").setProperty("foo2", "bar2"); + + superuser.save(); + } + + public void testParentInPath() throws RepositoryException { + String stmt = testPath + "//child/..[@foo1]"; + QueryResult result = qm.createQuery(stmt, Query.XPATH).execute(); + assertEquals("Wrong size of NodeIterator in result", + 1, result.getNodes().getSize()); + + assertEquals("base", result.getNodes().nextNode().getName()); + } + + public void testParentInAttribute1() throws RepositoryException { + String stmt = testPath + "//child[../@foo1]"; + QueryResult result = qm.createQuery(stmt, Query.XPATH).execute(); + assertTrue("Wrong size of NodeIterator in result", result.getNodes().getSize() > 0); + + assertEquals("child", result.getNodes().nextNode().getName()); + } + + public void testParentInAttribute2() throws RepositoryException { + String stmt = testPath + "//child[../child/@foo2]"; + QueryResult result = qm.createQuery(stmt, Query.XPATH).execute(); + assertTrue("Wrong size of NodeIterator in result", result.getNodes().getSize() > 0); + + assertEquals("child", result.getNodes().nextNode().getName()); + } + + public void testParentInAttribute3() throws RepositoryException { + String stmt = testPath + "//child[../../base/@foo1]"; + QueryResult result = qm.createQuery(stmt, Query.XPATH).execute(); + assertTrue("Wrong size of NodeIterator in result", result.getNodes().getSize() > 0); + + assertEquals("child", result.getNodes().nextNode().getName()); + } + + public void testParentInAttribute4() throws RepositoryException { + String stmt = testPath + "//child[../@foo1 = 'bar1']"; + QueryResult result = qm.createQuery(stmt, Query.XPATH).execute(); + assertTrue("Wrong size of NodeIterator in result", result.getNodes().getSize() > 0); + + assertEquals("child", result.getNodes().nextNode().getName()); + } + + public void testParentAttributeWithDescendant() throws RepositoryException { + String stmt = testPath + "//base[../base/@foo1 = 'bar1']/child"; + QueryResult result = qm.createQuery(stmt, Query.XPATH).execute(); + assertTrue("Wrong size of NodeIterator in result", result.getNodes().getSize() > 0); + + assertEquals("child", result.getNodes().nextNode().getName()); + } + + public void testParentWithAnd() throws RepositoryException { + String stmt = testPath + "//child[../@foo1 = 'bar1 and @foo2']"; + QueryResult result = qm.createQuery(stmt, Query.XPATH).execute(); + assertEquals("Wrong size of NodeIterator in result", 0, result.getNodes().getSize()); + } +} Property changes on: jackrabbit-core\src\test\java\org\apache\jackrabbit\core\query\ParentNodeTest.java ___________________________________________________________________ Added: svn:eol-style + native Index: jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/TestAll.java =================================================================== --- jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/TestAll.java (revision 790821) +++ jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/TestAll.java (working copy) @@ -60,6 +60,7 @@ suite.addTestSuite(IndexFormatVersionTest.class); suite.addTestSuite(IndexingRuleTest.class); suite.addTestSuite(ShareableNodeTest.class); + suite.addTestSuite(ParentNodeTest.class); return suite; } Index: jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/query/AndQueryNode.java =================================================================== --- jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/query/AndQueryNode.java (revision 790821) +++ jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/query/AndQueryNode.java (working copy) @@ -22,7 +22,7 @@ * Implements a query node that defines an AND operation between arbitrary * other {@link QueryNode}s. */ -public class AndQueryNode extends NAryQueryNode { +public class AndQueryNode extends NAryQueryNode { /** * Creates a new AndQueryNode with a parent Index: jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/query/DefaultQueryNodeFactory.java =================================================================== --- jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/query/DefaultQueryNodeFactory.java (revision 790821) +++ jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/query/DefaultQueryNodeFactory.java (working copy) @@ -89,7 +89,7 @@ */ public RelationQueryNode createRelationQueryNode(QueryNode parent, int operation) { - return new RelationQueryNode(parent, operation); + return new RelationQueryNode(parent, operation, this); } /** Index: jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/query/LocationStepQueryNode.java =================================================================== --- jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/query/LocationStepQueryNode.java (revision 790821) +++ jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/query/LocationStepQueryNode.java (working copy) @@ -32,7 +32,7 @@ * / -> descendants = false, nameTest = "" * */ -public class LocationStepQueryNode extends NAryQueryNode { +public class LocationStepQueryNode extends NAryQueryNode { /** Constant value for position index = last() */ public static final int LAST = Integer.MIN_VALUE; @@ -46,7 +46,7 @@ * TODO: The root location step should be refactored somehow */ public static final Name EMPTY_NAME = NameFactoryImpl.getInstance().create("", ""); - + /** Empty QueryNode array for us as return value */ private static final QueryNode[] EMPTY = new QueryNode[0]; @@ -68,9 +68,11 @@ private int index = NONE; /** - * Creates a new LocationStepQueryNode that matches only - * the empty name (the repository root). The created location step - * uses only the child axis. + * Creates a new LocationStepQueryNode that matches only the + * empty name (the repository root). The created location step uses only the + * child axis. + * + * @param parent the parent of this query node. */ protected LocationStepQueryNode(QueryNode parent) { super(parent); @@ -132,7 +134,7 @@ if (operands == null) { return EMPTY; } else { - return (QueryNode[]) operands.toArray(new QueryNode[operands.size()]); + return operands.toArray(new QueryNode[operands.size()]); } } Index: jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/query/NAryQueryNode.java =================================================================== --- jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/query/NAryQueryNode.java (revision 790821) +++ jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/query/NAryQueryNode.java (working copy) @@ -26,7 +26,7 @@ /** * Defines an abstract query node for nodes that have child nodes. */ -public abstract class NAryQueryNode extends QueryNode { +public abstract class NAryQueryNode extends QueryNode { /** * Empty result. @@ -36,7 +36,7 @@ /** * The list of operands / children */ - protected List operands = null; + protected List operands = null; /** * Creates a new NAryQueryNode with a reference to a parent @@ -55,10 +55,10 @@ * @param parent the parent node. * @param operands child nodes of this NAryQueryNode. */ - public NAryQueryNode(QueryNode parent, QueryNode[] operands) { + public NAryQueryNode(QueryNode parent, T[] operands) { super(parent); if (operands.length > 0) { - this.operands = new ArrayList(); + this.operands = new ArrayList(); this.operands.addAll(Arrays.asList(operands)); } } @@ -68,9 +68,9 @@ * * @param operand the child {@link QueryNode} to add. */ - public void addOperand(QueryNode operand) { + public void addOperand(T operand) { if (operands == null) { - operands = new ArrayList(); + operands = new ArrayList(); } operands.add(operand); } @@ -83,7 +83,7 @@ * and has been removed; false if this node does not contain * operand as a child node. */ - public boolean removeOperand(QueryNode operand) { + public boolean removeOperand(T operand) { if (operands == null) { return false; } @@ -108,7 +108,7 @@ if (operands == null) { return new QueryNode[0]; } else { - return (QueryNode[]) operands.toArray(new QueryNode[operands.size()]); + return operands.toArray(new QueryNode[operands.size()]); } } @@ -131,16 +131,16 @@ * @param visitor the visitor to call back. * @param data arbitrary data for the visitor. * @return the return values of the visitor.visit() calls. - * @throws RepositoryException + * @throws RepositoryException if an error occurs. */ public Object[] acceptOperands(QueryNodeVisitor visitor, Object data) throws RepositoryException { if (operands == null) { return EMPTY; } - List result = new ArrayList(operands.size()); - for (int i = 0; i < operands.size(); i++) { - Object r = ((QueryNode) operands.get(i)).accept(visitor, data); + List result = new ArrayList(operands.size()); + for (T operand : operands) { + Object r = operand.accept(visitor, data); if (r != null) { result.add(r); } @@ -166,9 +166,8 @@ if (operands == null) { return false; } - for (Iterator iter = operands.iterator(); iter.hasNext();) { - QueryNode queryNode = (QueryNode) iter.next(); - if (queryNode.needsSystemTree()) { + for (T operand : operands) { + if (operand.needsSystemTree()) { return true; } } Index: jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/query/NotQueryNode.java =================================================================== --- jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/query/NotQueryNode.java (revision 790821) +++ jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/query/NotQueryNode.java (working copy) @@ -21,7 +21,7 @@ /** * Implements a query node that defines a not operation on the child query. */ -public class NotQueryNode extends NAryQueryNode { +public class NotQueryNode extends NAryQueryNode { /** * Creates a new NotQueryNode instance. Index: jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/query/OrQueryNode.java =================================================================== --- jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/query/OrQueryNode.java (revision 790821) +++ jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/query/OrQueryNode.java (working copy) @@ -22,7 +22,7 @@ * Implements a query node that defines an OR operation between arbitrary * other {@link QueryNode}s. */ -public class OrQueryNode extends NAryQueryNode { +public class OrQueryNode extends NAryQueryNode { /** * Creates a new OrQueryNode with a parent Index: jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/query/PathQueryNode.java =================================================================== --- jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/query/PathQueryNode.java (revision 790821) +++ jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/query/PathQueryNode.java (working copy) @@ -26,7 +26,7 @@ /** * Implements a query node that defines a path restriction. */ -public class PathQueryNode extends NAryQueryNode { +public class PathQueryNode extends NAryQueryNode { /** * Flag indicating whether this path is absolute. @@ -101,7 +101,7 @@ if (operands == null) { return EMPTY; } else { - return (LocationStepQueryNode[]) operands.toArray(new LocationStepQueryNode[operands.size()]); + return operands.toArray(new LocationStepQueryNode[operands.size()]); } } Index: jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/query/QueryTreeDump.java =================================================================== --- jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/query/QueryTreeDump.java (revision 790821) +++ jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/query/QueryTreeDump.java (working copy) @@ -205,14 +205,14 @@ buffer.append(PADDING, 0, indent); buffer.append("+ RelationQueryNode: Op: "); buffer.append(QueryConstants.OPERATION_NAMES.getName(node.getOperation())); - buffer.append(" Prop="); - Path relPath = node.getRelativePath(); + buffer.append(" Prop=["); + PathQueryNode relPath = node.getRelativePath(); if (relPath == null) { buffer.append(relPath); } else { - appendPath(relPath, buffer); + visit(relPath, buffer); } - buffer.append(" Type=").append(QueryConstants.TYPE_NAMES.getName(node.getValueType())); + buffer.append("] Type=").append(QueryConstants.TYPE_NAMES.getName(node.getValueType())); if (node.getValueType() == QueryConstants.TYPE_DATE) { buffer.append(" Value=").append(node.getDateValue()); } else if (node.getValueType() == QueryConstants.TYPE_DOUBLE) { Index: jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/query/RelationQueryNode.java =================================================================== --- jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/query/RelationQueryNode.java (revision 790821) +++ jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/query/RelationQueryNode.java (working copy) @@ -22,14 +22,12 @@ import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.Path; -import org.apache.jackrabbit.spi.commons.conversion.MalformedPathException; +import org.apache.jackrabbit.spi.Path.Element; import org.apache.jackrabbit.spi.commons.name.NameFactoryImpl; -import org.apache.jackrabbit.spi.commons.name.PathBuilder; - /** * Implements a query node that defines property value relation. */ -public class RelationQueryNode extends NAryQueryNode implements QueryConstants { +public class RelationQueryNode extends NAryQueryNode implements QueryConstants { /** * Acts as an syntetic placeholder for a location step that matches any @@ -41,7 +39,7 @@ /** * The relative path to the property. */ - private Path relPath; + private PathQueryNode relPath; /** * If true this relation query node contains a value preceded @@ -90,15 +88,23 @@ */ private int type; + private final QueryNodeFactory factory; + /** * Creates a new RelationQueryNode without a type nor value * assigned. * - * @param parent the parent node for this query node. + * @param parent the parent node for this query node. + * @param operation the operation. + * @param factory the query node factory. */ - protected RelationQueryNode(QueryNode parent, int operation) { + protected RelationQueryNode(QueryNode parent, + int operation, + QueryNodeFactory factory) { super(parent); this.operation = operation; + this.factory = factory; + this.relPath = factory.createPathQueryNode(this); } /** @@ -138,38 +144,9 @@ } /** - * Returns the name of the property in this relation query node. Please - * note that this method does not return the full relative path that - * reference the property to match, but only the name of the final name - * element of the path returned by {@link #getRelativePath()}. - * - * @return the name of the property in this relation query node. - * @deprecated Use {@link #getRelativePath()} instead. - */ - public Name getProperty() { - return relPath == null ? null : relPath.getNameElement().getName(); - } - - /** - * Sets a new property name for this relation query node. - * - * @param name the new property name. - * @deprecated Use {@link #setRelativePath(Path)} instead. - */ - public void setProperty(Name name) { - PathBuilder builder = new PathBuilder(); - builder.addLast(name); - try { - this.relPath = builder.getPath(); - } catch (MalformedPathException e) { - // path is always valid - } - } - - /** * @return the relative path that references the property in this relation. */ - public Path getRelativePath() { + public PathQueryNode getRelativePath() { return relPath; } @@ -180,10 +157,14 @@ * @throws IllegalArgumentException if relPath is absolute. */ public void setRelativePath(Path relPath) { - if (relPath != null && relPath.isAbsolute()) { + if (relPath.isAbsolute()) { throw new IllegalArgumentException("relPath must be relative"); } - this.relPath = relPath; + + Element[] elements = relPath.getElements(); + for (Element element : elements) { + addPathElement(element); + } } /** @@ -193,23 +174,9 @@ * @param element the path element to append. */ public void addPathElement(Path.Element element) { - PathBuilder builder = new PathBuilder(); - if (relPath != null) { - builder.addAll(relPath.getElements()); - } - builder.addLast(element); - try { - relPath = builder.getPath(); - } - catch (MalformedPathException e) { - // path is always valid - } - // try to normalize the path - try { - relPath = relPath.getNormalizedPath(); - } catch (RepositoryException e) { - // just keep the original in that case - } + LocationStepQueryNode step = factory.createLocationStepQueryNode(relPath); + step.setNameTest(element.getName()); + relPath.addPathStep(step); } /** Index: jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/query/sql/QueryFormat.java =================================================================== --- jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/query/sql/QueryFormat.java (revision 790821) +++ jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/query/sql/QueryFormat.java (working copy) @@ -406,14 +406,14 @@ StringBuffer sb = (StringBuffer) data; try { StringBuffer propName = new StringBuffer(); - Path relPath = node.getRelativePath(); + PathQueryNode relPath = node.getRelativePath(); if (relPath == null) { propName.append("."); - } else if (relPath.getLength() > 1) { + } else if (relPath.getPathSteps().length > 1) { exceptions.add(new InvalidQueryException("Child axis not supported in SQL")); return data; } else { - appendName(relPath.getNameElement().getName(), resolver, propName); + visit(relPath, data); } // surround name with property function node.acceptOperands(this, propName); Index: jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/query/xpath/QueryFormat.java =================================================================== --- jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/query/xpath/QueryFormat.java (revision 790821) +++ jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/query/xpath/QueryFormat.java (working copy) @@ -345,29 +345,13 @@ StringBuffer propPath = new StringBuffer(); // only encode if not position function - Path relPath = node.getRelativePath(); + PathQueryNode relPath = node.getRelativePath(); if (relPath == null) { propPath.append("."); - } else if (relPath.getNameElement().getName().equals(XPathQueryBuilder.FN_POSITION_FULL)) { + } else if (relPath.getNumOperands() > 0 && relPath.getPathSteps()[0].getNameTest().equals(XPathQueryBuilder.FN_POSITION_FULL)) { propPath.append(resolver.getJCRName(XPathQueryBuilder.FN_POSITION_FULL)); } else { - Path.Element[] elements = relPath.getElements(); - String slash = ""; - for (int i = 0; i < elements.length; i++) { - propPath.append(slash); - slash = "/"; - if (i == elements.length - 1 && node.getOperation() != OPERATION_SIMILAR) { - propPath.append("@"); - } - if (elements[i].getName().equals(RelationQueryNode.STAR_NAME_TEST)) { - propPath.append("*"); - } else { - propPath.append(resolver.getJCRName(encode(elements[i].getName()))); - } - if (elements[i].getIndex() != 0) { - propPath.append("[").append(elements[i].getIndex()).append("]"); - } - } + visit(relPath, data); } // surround name with property function Index: jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/query/xpath/XPathQueryBuilder.java =================================================================== --- jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/query/xpath/XPathQueryBuilder.java (revision 790821) +++ jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/query/xpath/XPathQueryBuilder.java (working copy) @@ -424,7 +424,10 @@ if (tmpRelPath == null) { tmpRelPath = new PathBuilder(); } - tmpRelPath.addLast(tmp.getRelativePath().getNameElement()); + PathQueryNode relPath = tmp.getRelativePath(); + LocationStepQueryNode[] steps = relPath.getPathSteps(); + + tmpRelPath.addLast(steps[steps.length-1].getNameTest()); } } break; @@ -545,7 +548,11 @@ } break; case JJTDOTDOT: - exceptions.add(new InvalidQueryException("Parent axis is not supported")); + if (queryNode instanceof LocationStepQueryNode) { + ((LocationStepQueryNode) queryNode).setNameTest(PATH_FACTORY.getParentElement().getName()); + } else { + ((RelationQueryNode) queryNode).addPathElement(PATH_FACTORY.getParentElement()); + } break; default: // per default traverse @@ -943,47 +950,12 @@ } if (queryNode.getType() == QueryNode.TYPE_PATH) { PathQueryNode pathNode = (PathQueryNode) queryNode; - DerefQueryNode derefNode = factory.createDerefQueryNode(pathNode, null, false); - - // assign property name - node.jjtGetChild(1).jjtAccept(this, derefNode); - // check property name - if (derefNode.getRefProperty() == null) { - exceptions.add(new InvalidQueryException("Wrong first argument type for jcr:deref")); - } - - SimpleNode literal = (SimpleNode) node.jjtGetChild(2).jjtGetChild(0); - if (literal.getId() == JJTSTRINGLITERAL) { - String value = literal.getValue(); - // strip quotes - value = value.substring(1, value.length() - 1); - if (!value.equals("*")) { - Name name = null; - try { - name = decode(resolver.getQName(value)); - } catch (NameException e) { - exceptions.add(new InvalidQueryException("Illegal name: " + value)); - } - derefNode.setNameTest(name); - } - } else { - exceptions.add(new InvalidQueryException("Second argument for jcr:deref must be a String")); - } - - // check if descendant - if (!descendant) { - Node p = node.jjtGetParent(); - for (int i = 0; i < p.jjtGetNumChildren(); i++) { - SimpleNode c = (SimpleNode) p.jjtGetChild(i); - if (c == node) { - break; - } - descendant = (c.getId() == JJTSLASHSLASH - || c.getId() == JJTROOTDESCENDANTS); - } - } - derefNode.setIncludeDescendants(descendant); - pathNode.addPathStep(derefNode); + + pathNode.addPathStep(createDerefQueryNode(node, descendant, pathNode)); + } else if (queryNode.getType() == QueryNode.TYPE_RELATION) { + RelationQueryNode relNode = (RelationQueryNode) queryNode; + DerefQueryNode deref = createDerefQueryNode(node, descendant, relNode.getRelativePath()); + relNode.getRelativePath().addPathStep(deref); } else { exceptions.add(new InvalidQueryException("Unsupported location for jcr:deref()")); } @@ -1103,6 +1075,51 @@ return queryNode; } + private DerefQueryNode createDerefQueryNode(SimpleNode node, boolean descendant, QueryNode pathNode) + throws NamespaceException { + DerefQueryNode derefNode = factory.createDerefQueryNode(pathNode, null, false); + + // assign property name + node.jjtGetChild(1).jjtAccept(this, derefNode); + // check property name + if (derefNode.getRefProperty() == null) { + exceptions.add(new InvalidQueryException("Wrong first argument type for jcr:deref")); + } + + SimpleNode literal = (SimpleNode) node.jjtGetChild(2).jjtGetChild(0); + if (literal.getId() == JJTSTRINGLITERAL) { + String value = literal.getValue(); + // strip quotes + value = value.substring(1, value.length() - 1); + if (!value.equals("*")) { + Name name = null; + try { + name = decode(resolver.getQName(value)); + } catch (NameException e) { + exceptions.add(new InvalidQueryException("Illegal name: " + value)); + } + derefNode.setNameTest(name); + } + } else { + exceptions.add(new InvalidQueryException("Second argument for jcr:deref must be a String")); + } + + // check if descendant + if (!descendant) { + Node p = node.jjtGetParent(); + for (int i = 0; i < p.jjtGetNumChildren(); i++) { + SimpleNode c = (SimpleNode) p.jjtGetChild(i); + if (c == node) { + break; + } + descendant = (c.getId() == JJTSLASHSLASH + || c.getId() == JJTROOTDESCENDANTS); + } + } + derefNode.setIncludeDescendants(descendant); + return derefNode; + } + private OrderQueryNode.OrderSpec createOrderSpec(SimpleNode node, OrderQueryNode queryNode) { SimpleNode child = (SimpleNode) node.jjtGetChild(0);