You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jackrabbit.apache.org by mr...@apache.org on 2006/11/21 11:09:52 UTC

svn commit: r477599 - in /jackrabbit/trunk/jackrabbit/src: main/java/org/apache/jackrabbit/core/query/ main/java/org/apache/jackrabbit/core/query/lucene/ main/java/org/apache/jackrabbit/core/query/sql/ main/java/org/apache/jackrabbit/core/query/xpath/ ...

Author: mreutegg
Date: Tue Nov 21 02:09:51 2006
New Revision: 477599

URL: http://svn.apache.org/viewvc?view=rev&rev=477599
Log:
JCR-247: Child axis support for XPath predicates

Added:
    jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/lucene/ParentAxisQuery.java   (with props)
    jackrabbit/trunk/jackrabbit/src/test/java/org/apache/jackrabbit/core/query/ChildAxisQueryTest.java   (with props)
Modified:
    jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/QueryImpl.java
    jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/QueryTreeDump.java
    jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/RelationQueryNode.java
    jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/TextsearchQueryNode.java
    jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/lucene/LuceneQueryBuilder.java
    jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/sql/JCRSQLQueryBuilder.java
    jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/sql/QueryFormat.java
    jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/xpath/QueryFormat.java
    jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/xpath/XPathQueryBuilder.java
    jackrabbit/trunk/jackrabbit/src/test/java/org/apache/jackrabbit/core/query/TestAll.java

Modified: jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/QueryImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/QueryImpl.java?view=diff&rev=477599&r1=477598&r2=477599
==============================================================================
--- jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/QueryImpl.java (original)
+++ jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/QueryImpl.java Tue Nov 21 02:09:51 2006
@@ -25,6 +25,8 @@
 import org.apache.jackrabbit.name.QName;
 import org.apache.jackrabbit.name.NameFormat;
 import org.apache.jackrabbit.name.PathFormat;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import javax.jcr.ItemExistsException;
 import javax.jcr.ItemNotFoundException;
@@ -37,6 +39,7 @@
 import javax.jcr.query.InvalidQueryException;
 import javax.jcr.query.QueryResult;
 import javax.jcr.version.VersionException;
+import java.text.NumberFormat;
 
 /**
  * Provides the default implementation for a JCR query.
@@ -44,6 +47,11 @@
 public class QueryImpl extends AbstractQueryImpl {
 
     /**
+     * The logger instance for this class
+     */
+    private static final Logger log = LoggerFactory.getLogger(QueryImpl.class);
+
+    /**
      * The session of the user executing this query
      */
     protected SessionImpl session;
@@ -130,7 +138,17 @@
      */
     public QueryResult execute() throws RepositoryException {
         checkInitialized();
-        return query.execute();
+        long time = System.currentTimeMillis();
+        QueryResult result = query.execute();
+        if (log.isDebugEnabled()) {
+            time = System.currentTimeMillis() - time;
+            NumberFormat format = NumberFormat.getNumberInstance();
+            format.setMinimumFractionDigits(2);
+            format.setMaximumFractionDigits(2);
+            String seconds = format.format((double) time / 1000);
+            log.debug("executed in " + seconds + " s. (" + statement + ")");
+        }
+        return result;
     }
 
     /**

Modified: jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/QueryTreeDump.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/QueryTreeDump.java?view=diff&rev=477599&r1=477598&r2=477599
==============================================================================
--- jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/QueryTreeDump.java (original)
+++ jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/QueryTreeDump.java Tue Nov 21 02:09:51 2006
@@ -17,6 +17,7 @@
 package org.apache.jackrabbit.core.query;
 
 import org.apache.jackrabbit.name.QName;
+import org.apache.jackrabbit.name.Path;
 
 import java.util.Arrays;
 
@@ -141,7 +142,22 @@
         StringBuffer buffer = (StringBuffer) data;
         buffer.append(PADDING, 0, indent);
         buffer.append("+ TextsearchQueryNode: ");
-        buffer.append(" Prop=").append(node.getPropertyName());
+        buffer.append(" Path=");
+        Path relPath = node.getRelativePath();
+        if (relPath == null) {
+            buffer.append(".");
+        } else {
+            Path.PathElement[] elements = relPath.getElements();
+            String slash = "";
+            for (int i = 0; i < elements.length; i++) {
+                buffer.append(slash);
+                slash = "/";
+                if (node.getReferencesProperty() && i == elements.length - 1) {
+                    buffer.append("@");
+                }
+                buffer.append(elements[i]);
+            }
+        }
         buffer.append(" Query=").append(node.getQuery());
         buffer.append("\n");
         return buffer;
@@ -221,7 +237,22 @@
         } else {
             buffer.append("!!UNKNOWN OPERATION!!");
         }
-        buffer.append(" Prop=" + node.getProperty());
+        buffer.append(" Prop=");
+        Path relPath = node.getRelativePath();
+        if (relPath == null) {
+            buffer.append(relPath);
+        } else {
+            Path.PathElement[] elements = relPath.getElements();
+            String slash = "";
+            for (int i = 0; i < elements.length; i++) {
+                buffer.append(slash);
+                slash = "/";
+                if (i == elements.length - 1) {
+                    buffer.append("@");
+                }
+                buffer.append(elements[i]);
+            }
+        }
         if (node.getValueType() == QueryConstants.TYPE_DATE) {
             buffer.append(" Type=DATE Value=").append(node.getDateValue());
         } else if (node.getValueType() == QueryConstants.TYPE_DOUBLE) {

Modified: jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/RelationQueryNode.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/RelationQueryNode.java?view=diff&rev=477599&r1=477598&r2=477599
==============================================================================
--- jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/RelationQueryNode.java (original)
+++ jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/RelationQueryNode.java Tue Nov 21 02:09:51 2006
@@ -17,6 +17,8 @@
 package org.apache.jackrabbit.core.query;
 
 import org.apache.jackrabbit.name.QName;
+import org.apache.jackrabbit.name.Path;
+import org.apache.jackrabbit.name.MalformedPathException;
 
 import java.util.Date;
 
@@ -26,9 +28,16 @@
 public class RelationQueryNode extends NAryQueryNode implements QueryConstants {
 
     /**
-     * The name of the property
+     * Acts as an syntetic placeholder for a location step that matches any
+     * name. This is required becase a JCR path does not allow a QName with
+     * a single '*' (star) character.
      */
-    private QName property;
+    public static final QName STAR_NAME_TEST = new QName(QName.NS_REP_URI, "__star__");
+
+    /**
+     * The relative path to the property.
+     */
+    private Path relPath;
 
     /**
      * If <code>true</code> this relation query node contains a value preceded
@@ -93,13 +102,13 @@
      * <code>value</code> and an <code>operation</code> type.
      *
      * @param parent    the parent node for this query node.
-     * @param property  the name of a property.
+     * @param relPath   the relative path to a property.
      * @param value     a property value
      * @param operation the type of the relation.
      */
-    public RelationQueryNode(QueryNode parent, QName property, long value, int operation) {
+    public RelationQueryNode(QueryNode parent, Path relPath, long value, int operation) {
         super(parent);
-        this.property = property;
+        this.relPath = relPath;
         this.valueLong = value;
         this.operation = operation;
         this.type = TYPE_LONG;
@@ -110,13 +119,13 @@
      * <code>value</code> and an <code>operation</code> type.
      *
      * @param parent    the parent node for this query node.
-     * @param property  the name of a property.
+     * @param relPath   the relative path to a property.
      * @param value     a property value
      * @param operation the type of the relation.
      */
-    public RelationQueryNode(QueryNode parent, QName property, double value, int operation) {
+    public RelationQueryNode(QueryNode parent, Path relPath, double value, int operation) {
         super(parent);
-        this.property = property;
+        this.relPath = relPath;
         this.valueDouble = value;
         this.operation = operation;
         this.type = TYPE_DOUBLE;
@@ -127,13 +136,13 @@
      * <code>value</code> and an <code>operation</code> type.
      *
      * @param parent    the parent node for this query node.
-     * @param property  the name of a property.
+     * @param relPath   the relative path to a property.
      * @param value     a property value
      * @param operation the type of the relation.
      */
-    public RelationQueryNode(QueryNode parent, QName property, Date value, int operation) {
+    public RelationQueryNode(QueryNode parent, Path relPath, Date value, int operation) {
         super(parent);
-        this.property = property;
+        this.relPath = relPath;
         this.valueDate = value;
         this.operation = operation;
         this.type = TYPE_DATE;
@@ -144,13 +153,13 @@
      * <code>value</code> and an <code>operation</code> type.
      *
      * @param parent    the parent node for this query node.
-     * @param property  the name of a property.
+     * @param relPath   the relative path to a property.
      * @param value     a property value
      * @param operation the type of the relation.
      */
-    public RelationQueryNode(QueryNode parent, QName property, String value, int operation) {
+    public RelationQueryNode(QueryNode parent, Path relPath, String value, int operation) {
         super(parent);
-        this.property = property;
+        this.relPath = relPath;
         this.valueString = value;
         this.operation = operation;
         this.type = TYPE_STRING;
@@ -192,21 +201,77 @@
     }
 
     /**
-     * Returns the name of the property in this relation query node.
+     * 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 QName getProperty() {
-        return property;
+        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(QName name) {
-        property = name;
+        Path.PathBuilder builder = new Path.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() {
+        return relPath;
+    }
+
+    /**
+     * Sets the relative path to the property in this relation.
+     *
+     * @param relPath the relative path to a property.
+     * @throws IllegalArgumentException if <code>relPath</code> is absolute.
+     */
+    public void setRelativePath(Path relPath) {
+        if (relPath != null && relPath.isAbsolute()) {
+            throw new IllegalArgumentException("relPath must be relative");
+        }
+        this.relPath = relPath;
+    }
+
+    /**
+     * Adds a path element to the existing relative path. To add a path element
+     * which matches all node names use {@link #STAR_NAME_TEST}.
+     *
+     * @param element the path element to append.
+     */
+    public void addPathElement(Path.PathElement element) {
+        Path.PathBuilder builder = new Path.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 (MalformedPathException e) {
+            // just keep the original in that case
+        }
     }
 
     /**
@@ -329,7 +394,7 @@
                     && valueLong == other.valueLong
                     && valuePosition == other.valuePosition
                     && (valueString == null ? other.valueString == null : valueString.equals(other.valueString))
-                    && (property == null ? other.property == null : property.equals(other.property));
+                    && (relPath == null ? other.relPath== null : relPath.equals(other.relPath));
         }
         return false;
     }

Modified: jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/TextsearchQueryNode.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/TextsearchQueryNode.java?view=diff&rev=477599&r1=477598&r2=477599
==============================================================================
--- jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/TextsearchQueryNode.java (original)
+++ jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/TextsearchQueryNode.java Tue Nov 21 02:09:51 2006
@@ -17,6 +17,8 @@
 package org.apache.jackrabbit.core.query;
 
 import org.apache.jackrabbit.name.QName;
+import org.apache.jackrabbit.name.Path;
+import org.apache.jackrabbit.name.MalformedPathException;
 
 /**
  * Implements a query node that defines a textsearch clause.
@@ -29,11 +31,18 @@
     private final String query;
 
     /**
-     * Limits the scope of this textsearch clause to properties with this name.
+     * Limits the scope of this textsearch clause to a node or a property with
+     * the given relative path.
      * If <code>null</code> the scope of this textsearch clause is the fulltext
-     * index of all properties of a node.
+     * index of all properties of the context node.
      */
-    private QName propertyName;
+    private Path relPath;
+
+    /**
+     * If set to <code>true</code> {@link #relPath} references a property,
+     * otherwise references a node.
+     */
+    private boolean propertyRef;
 
     /**
      * Creates a new <code>TextsearchQueryNode</code> with a <code>parent</code>
@@ -44,22 +53,26 @@
      * @param query  the textsearch statement.
      */
     public TextsearchQueryNode(QueryNode parent, String query) {
-        this(parent, query, null);
+        this(parent, query, null, false);
     }
 
     /**
      * Creates a new <code>TextsearchQueryNode</code> with a <code>parent</code>
-     * and a textsearch <code>query</code> statement. The scope of the query
-     * is property with name <code>propertyName</code>.
+     * and a textsearch <code>query</code> statement. The scope of the query is
+     * the property or node referenced by <code>relPath</code>.
      *
-     * @param parent the parent node of this query node.
-     * @param query  the textsearch statement.
-     * @param propertyName scope of the fulltext search.
+     * @param parent     the parent node of this query node.
+     * @param query      the textsearch statement.
+     * @param relPath    scope of the fulltext search. If <code>null</code> the
+     *                   context node is searched.
+     * @param isProperty if <code>relPath</code> references a property or a
+     *                   node.
      */
-    public TextsearchQueryNode(QueryNode parent, String query, QName propertyName) {
+    public TextsearchQueryNode(QueryNode parent, String query, Path relPath, boolean isProperty) {
         super(parent);
         this.query = query;
-        this.propertyName = propertyName;
+        this.relPath = relPath;
+        this.propertyRef = isProperty;
     }
 
     /**
@@ -90,21 +103,98 @@
     /**
      * Returns a property name if the scope is limited to just a single property
      * or <code>null</code> if the scope is spawned across all properties of a
-     * node.
+     * node. Please note that this method does not return the full relative path
+     * that reference the item to match, but only the name of the final name
+     * element of the path returned by {@link #getRelativePath()}.
      *
      * @return property name or <code>null</code>.
+     * @deprecated Use {@link #getRelativePath()} instead.
      */
     public QName getPropertyName() {
-        return propertyName;
+        return relPath == null ? null : relPath.getNameElement().getName();
     }
 
     /**
      * Sets a new name as the search scope for this fulltext query.
      *
      * @param property the name of the property.
+     * @deprecated Use {@link #setRelativePath(Path)} instead.
      */
     public void setPropertyName(QName property) {
-        this.propertyName = property;
+        Path.PathBuilder builder = new Path.PathBuilder();
+        builder.addLast(property);
+        try {
+            this.relPath = builder.getPath();
+            this.propertyRef = true;
+        } catch (MalformedPathException e) {
+            // path is always valid
+        }
+    }
+
+    /**
+     * @return the relative path that references the item where the textsearch
+     *         is performed. Returns <code>null</code> if the textsearch is
+     *         performed on the context node.
+     */
+    public Path getRelativePath() {
+        return relPath;
+    }
+
+    /**
+     * Sets the relative path to the item where the textsearch is performed. If
+     * <code>relPath</code> is <code>null</code> the textsearch is performed on
+     * the context node.
+     *
+     * @param relPath the relative path to an item.
+     * @throws IllegalArgumentException if <code>relPath</code> is absolute.
+     */
+    public void setRelativePath(Path relPath) {
+        if (relPath != null && relPath.isAbsolute()) {
+            throw new IllegalArgumentException("relPath must be relative");
+        }
+        this.relPath = relPath;
+        if (relPath == null) {
+            // context node is never a property
+            propertyRef = false;
+        }
+    }
+
+    /**
+     * Adds a path element to the existing relative path. To add a path element
+     * which matches all node names use {@link RelationQueryNode#STAR_NAME_TEST}.
+     *
+     * @param element the path element to append.
+     */
+    public void addPathElement(Path.PathElement element) {
+        Path.PathBuilder builder = new Path.PathBuilder();
+        if (relPath != null) {
+            builder.addAll(relPath.getElements());
+        }
+        builder.addLast(element);
+        try {
+            relPath = builder.getPath();
+        } catch (MalformedPathException e) {
+            // path is always valid
+        }
+    }
+
+    /**
+     * @return <code>true</code> if {@link #getRelativePath()} references a
+     *         property, returns <code>false</code> if it references a node.
+     */
+    public boolean getReferencesProperty() {
+        return propertyRef;
+    }
+
+    /**
+     * Is set to <code>true</code>, indicates that {@link #getRelativePath()}
+     * references a property, if set to <code>false</code> indicates that it
+     * references a node.
+     *
+     * @param b flag whether a property is referenced.
+     */
+    public void setReferencesProperty(boolean b) {
+        propertyRef = b;
     }
 
     /**
@@ -114,7 +204,8 @@
         if (obj instanceof TextsearchQueryNode) {
             TextsearchQueryNode other = (TextsearchQueryNode) obj;
             return (query == null ? other.query == null : query.equals(other.query))
-                    && (propertyName == null ? other.propertyName == null : propertyName.equals(other.propertyName));
+                    && (relPath == null ? other.relPath == null : relPath.equals(other.relPath)
+                    && propertyRef == other.propertyRef);
         }
         return false;
     }

Modified: jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/lucene/LuceneQueryBuilder.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/lucene/LuceneQueryBuilder.java?view=diff&rev=477599&r1=477598&r2=477599
==============================================================================
--- jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/lucene/LuceneQueryBuilder.java (original)
+++ jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/lucene/LuceneQueryBuilder.java Tue Nov 21 02:09:51 2006
@@ -319,15 +319,18 @@
 
     public Object visit(TextsearchQueryNode node, Object data) {
         try {
+            Path relPath = node.getRelativePath();
             String fieldname;
-            if (node.getPropertyName() == null) {
+            if (relPath == null || !node.getReferencesProperty()) {
                 // fulltext on node
                 fieldname = FieldNames.FULLTEXT;
             } else {
+                // final path element is a property name
+                QName propName = relPath.getNameElement().getName();
                 StringBuffer tmp = new StringBuffer();
-                tmp.append(nsMappings.getPrefix(node.getPropertyName().getNamespaceURI()));
+                tmp.append(nsMappings.getPrefix(propName.getNamespaceURI()));
                 tmp.append(":").append(FieldNames.FULLTEXT_PREFIX);
-                tmp.append(node.getPropertyName().getLocalName());
+                tmp.append(propName.getLocalName());
                 fieldname = tmp.toString();
             }
             QueryParser parser = new QueryParser(fieldname, analyzer);
@@ -363,11 +366,42 @@
                     query.append(textsearch.charAt(i));
                 }
             }
-            return parser.parse(query.toString());
+            Query context = parser.parse(query.toString());
+            if (relPath != null && (!node.getReferencesProperty() || relPath.getLength() > 1)) {
+                // text search on some child axis
+                Path.PathElement[] elements = relPath.getElements();
+                for (int i = elements.length - 1; i >= 0; i--) {
+                    String name = null;
+                    if (!elements[i].getName().equals(RelationQueryNode.STAR_NAME_TEST)) {
+                        name = NameFormat.format(elements[i].getName(), nsMappings);;
+                    }
+                    // join text search with name test
+                    // if path references property that's elements.length - 2
+                    // if path references node that's elements.length - 1
+                    if (name != null
+                            && ((node.getReferencesProperty() && i == elements.length - 2)
+                                || (!node.getReferencesProperty() && i == elements.length - 1))) {
+                        Query q = new TermQuery(new Term(FieldNames.LABEL, name));
+                        BooleanQuery and = new BooleanQuery();
+                        and.add(q, true, false);
+                        and.add(context, true, false);
+                        context = and;
+                    } else if ((node.getReferencesProperty() && i < elements.length - 2)
+                            || (!node.getReferencesProperty() && i < elements.length - 1)) {
+                        // otherwise do a parent axis step
+                        context = new ParentAxisQuery(context, name);
+                    }
+                }
+                // finally select parent
+                context = new ParentAxisQuery(context, null);
+            }
+            return context;
         } catch (NamespaceException e) {
             exceptions.add(e);
         } catch (ParseException e) {
             exceptions.add(e);
+        } catch (NoPrefixDeclaredException e) {
+            exceptions.add(e);
         }
         return null;
     }
@@ -569,7 +603,8 @@
                         || node.getOperation() == QueryConstants.OPERATION_NE_GENERAL
                         || node.getOperation() == QueryConstants.OPERATION_NE_VALUE) {
                     // only use coercing on non-range operations
-                    stringValues = getStringValues(node.getProperty(), node.getStringValue());
+                    QName propertyName = node.getRelativePath().getNameElement().getName();
+                    stringValues = getStringValues(propertyName, node.getStringValue());
                 } else {
                     stringValues[0] = node.getStringValue();
                 }
@@ -582,7 +617,7 @@
                         + node.getValueType());
         }
 
-        if (node.getProperty() == null) {
+        if (node.getRelativePath() == null) {
             exceptions.add(new InvalidQueryException("@* not supported in predicate"));
             return data;
         }
@@ -600,9 +635,10 @@
             }
         }, null);
 
+        Path relPath = node.getRelativePath();
         String field = "";
         try {
-            field = NameFormat.format(node.getProperty(), nsMappings);
+            field = NameFormat.format(relPath.getNameElement().getName(), nsMappings);
         } catch (NoPrefixDeclaredException e) {
             // should never happen
             exceptions.add(e);
@@ -752,6 +788,41 @@
                 throw new IllegalArgumentException("Unknown relation operation: "
                         + node.getOperation());
         }
+
+        if (relPath.getLength() > 1) {
+            try {
+                // child axis in relation
+                Path.PathElement[] 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--) {
+                    String name = null;
+                    if (!elements[i].getName().equals(RelationQueryNode.STAR_NAME_TEST)) {
+                        name = NameFormat.format(elements[i].getName(), nsMappings);
+                    }
+                    if (i == elements.length - 2) {
+                        // join name test with property query if there is one
+                        if (name != null) {
+                            Query nameTest = new TermQuery(new Term(FieldNames.LABEL, name));
+                            BooleanQuery and = new BooleanQuery();
+                            and.add(query, true, false);
+                            and.add(nameTest, true, false);
+                            query = and;
+                        } else {
+                            // otherwise the query can be used as is
+                        }
+                    } else {
+                        query = new ParentAxisQuery(query, name);
+                    }
+                }
+            } catch (NoPrefixDeclaredException e) {
+                // should never happen
+                exceptions.add(e);
+            }
+            // finally select the parent of the selected nodes
+            query = new ParentAxisQuery(query, null);
+        }
+
         return query;
     }
 

Added: jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/lucene/ParentAxisQuery.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/lucene/ParentAxisQuery.java?view=auto&rev=477599
==============================================================================
--- jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/lucene/ParentAxisQuery.java (added)
+++ jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/lucene/ParentAxisQuery.java Tue Nov 21 02:09:51 2006
@@ -0,0 +1,301 @@
+/*
+ * 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 org.apache.lucene.search.Query;
+import org.apache.lucene.search.Scorer;
+import org.apache.lucene.search.Weight;
+import org.apache.lucene.search.Searcher;
+import org.apache.lucene.search.Explanation;
+import org.apache.lucene.search.Similarity;
+import org.apache.lucene.search.HitCollector;
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.Term;
+import org.apache.lucene.index.TermDocs;
+
+import java.io.IOException;
+import java.util.BitSet;
+import java.util.Map;
+import java.util.HashMap;
+
+/**
+ * <code>ParentAxisQuery</code> selects the parent nodes of a context query.
+ */
+class ParentAxisQuery extends Query {
+
+    /**
+     * Default score is 1.0f.
+     */
+    private static final Float DEFAULT_SCORE = new Float(1.0f);
+
+    /**
+     * The context query
+     */
+    private final Query contextQuery;
+
+    /**
+     * The nameTest to apply on the parent axis, or <code>null</code> if any
+     * parent node should be selected.
+     */
+    private final String nameTest;
+
+    /**
+     * The scorer of the context query
+     */
+    private Scorer contextScorer;
+
+    /**
+     * Creates a new <code>ParentAxisQuery</code> based on a
+     * <code>context</code> query.
+     *
+     * @param context  the context for this query.
+     * @param nameTest a name test or <code>null</code> if any parent node is
+     *                 selected.
+     */
+    ParentAxisQuery(Query context, String nameTest) {
+        this.contextQuery = context;
+        this.nameTest = nameTest;
+    }
+
+    /**
+     * Creates a <code>Weight</code> instance for this query.
+     *
+     * @param searcher the <code>Searcher</code> instance to use.
+     * @return a <code>ParentAxisWeight</code>.
+     */
+    protected Weight createWeight(Searcher searcher) {
+        return new ParentAxisWeight(searcher);
+    }
+
+    /**
+     * Always returns 'ParentAxisQuery'.
+     *
+     * @param field the name of a field.
+     * @return 'ParentAxisQuery'.
+     */
+    public String toString(String field) {
+        return "ParentAxisQuery";
+    }
+
+    //-----------------------< ParentAxisWeight >-------------------------------
+
+    /**
+     * The <code>Weight</code> implementation for this <code>ParentAxisQuery</code>.
+     */
+    private class ParentAxisWeight implements Weight {
+
+        /**
+         * The searcher in use
+         */
+        private final Searcher searcher;
+
+        /**
+         * Creates a new <code>ParentAxisWeight</code> instance using
+         * <code>searcher</code>.
+         *
+         * @param searcher a <code>Searcher</code> instance.
+         */
+        private ParentAxisWeight(Searcher searcher) {
+            this.searcher = searcher;
+        }
+
+        /**
+         * Returns this <code>ParentAxisQuery</code>.
+         *
+         * @return this <code>ParentAxisQuery</code>.
+         */
+        public Query getQuery() {
+            return ParentAxisQuery.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 <code>ParentAxisQuery</code>.
+         *
+         * @param reader a reader for accessing the index.
+         * @return a <code>ParentAxisScorer</code>.
+         * @throws IOException if an error occurs while reading from the index.
+         */
+        public Scorer scorer(IndexReader reader) throws IOException {
+            contextScorer = contextQuery.weight(searcher).scorer(reader);
+            HierarchyResolver resolver = (HierarchyResolver) reader;
+            return new ParentAxisScorer(searcher.getSimilarity(), reader, resolver);
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public Explanation explain(IndexReader reader, int doc) throws IOException {
+            return new Explanation();
+        }
+    }
+
+    //--------------------------< ParentAxisScorer >----------------------------
+
+    /**
+     * Implements a <code>Scorer</code> for this <code>ParentAxisQuery</code>.
+     */
+    private class ParentAxisScorer extends Scorer {
+
+        /**
+         * An <code>IndexReader</code> to access the index.
+         */
+        private final IndexReader reader;
+
+        /**
+         * The <code>HierarchyResolver</code> of the index.
+         */
+        private final HierarchyResolver hResolver;
+
+        /**
+         * BitSet storing the id's of selected documents
+         */
+        private BitSet hits;
+
+        /**
+         * Map that contains the scores from matching documents from the context
+         * query. To save memory only scores that are not equal to 1.0f are put
+         * to this map.
+         * <p/>
+         * key=[Integer] id of selected document from context query<br>
+         * value=[Float] score for that document
+         */
+        private final Map scores = new HashMap();
+
+        /**
+         * The next document id to return
+         */
+        private int nextDoc = -1;
+
+        /**
+         * Creates a new <code>ParentAxisScorer</code>.
+         *
+         * @param similarity the <code>Similarity</code> instance to use.
+         * @param reader     for index access.
+         */
+        protected ParentAxisScorer(Similarity similarity, IndexReader reader, HierarchyResolver resolver) {
+            super(similarity);
+            this.reader = reader;
+            this.hResolver = resolver;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public boolean next() throws IOException {
+            calculateParent();
+            nextDoc = hits.nextSetBit(nextDoc + 1);
+            return nextDoc > -1;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public int doc() {
+            return nextDoc;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public float score() throws IOException {
+            Float score = (Float) scores.get(new Integer(nextDoc));
+            if (score == null) {
+                score = DEFAULT_SCORE;
+            }
+            return score.floatValue();
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public boolean skipTo(int target) throws IOException {
+            nextDoc = hits.nextSetBit(target);
+            return nextDoc > -1;
+        }
+
+        /**
+         * {@inheritDoc}
+         *
+         * @throws UnsupportedOperationException this implementation always
+         *                                       throws an <code>UnsupportedOperationException</code>.
+         */
+        public Explanation explain(int doc) throws IOException {
+            throw new UnsupportedOperationException();
+        }
+
+        private void calculateParent() throws IOException {
+            if (hits == null) {
+                hits = new BitSet(reader.maxDoc());
+
+                final IOException[] ex = new IOException[1];
+                contextScorer.score(new HitCollector() {
+                    public void collect(int doc, float score) {
+                        try {
+                            doc = hResolver.getParent(doc);
+                            if (doc != -1) {
+                                hits.set(doc);
+                                if (score != DEFAULT_SCORE.floatValue()) {
+                                    scores.put(new Integer(doc), new Float(score));
+                                }
+                            }
+                        } catch (IOException e) {
+                            ex[0] = e;
+                        }
+                    }
+                });
+
+                if (ex[0] != null) {
+                    throw ex[0];
+                }
+
+                // filter out documents that do not match the name test
+                if (nameTest != null) {
+                    TermDocs tDocs = reader.termDocs(new Term(FieldNames.LABEL, nameTest));
+                    try {
+                        for (int i = hits.nextSetBit(0); i >= 0; i = hits.nextSetBit(i + 1)) {
+                            if (!tDocs.skipTo(i)) {
+                                hits.clear(i);
+                            }
+                        }
+                    } finally {
+                        tDocs.close();
+                    }
+                }
+            }
+        }
+    }
+}

Propchange: jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/lucene/ParentAxisQuery.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/sql/JCRSQLQueryBuilder.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/sql/JCRSQLQueryBuilder.java?view=diff&rev=477599&r1=477598&r2=477599
==============================================================================
--- jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/sql/JCRSQLQueryBuilder.java (original)
+++ jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/sql/JCRSQLQueryBuilder.java Tue Nov 21 02:09:51 2006
@@ -35,6 +35,8 @@
 import org.apache.jackrabbit.name.QName;
 import org.apache.jackrabbit.name.UnknownPrefixException;
 import org.apache.jackrabbit.name.NameFormat;
+import org.apache.jackrabbit.name.Path;
+import org.apache.jackrabbit.name.MalformedPathException;
 import org.apache.jackrabbit.util.ISO8601;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -494,7 +496,17 @@
 
     public Object visit(ASTContainsExpression node, Object data) {
         NAryQueryNode parent = (NAryQueryNode) data;
-        parent.addOperand(new TextsearchQueryNode(parent, node.getQuery(), node.getPropertyName()));
+        try {
+            Path relPath = null;
+            if (node.getPropertyName() != null) {
+                Path.PathBuilder builder = new Path.PathBuilder();
+                builder.addLast(node.getPropertyName());
+                relPath = builder.getPath();
+            }
+            parent.addOperand(new TextsearchQueryNode(parent, node.getQuery(), relPath, true));
+        } catch (MalformedPathException e) {
+            // path is always valid
+        }
         return parent;
     }
 
@@ -541,26 +553,32 @@
         RelationQueryNode node = null;
 
         try {
+            Path.PathBuilder builder = new Path.PathBuilder();
+            builder.addLast(propertyName);
+            Path relPath = builder.getPath();
             if (literal.getType() == QueryConstants.TYPE_DATE) {
                 SimpleDateFormat format = new SimpleDateFormat(DATE_PATTERN);
                 Date date = format.parse(stringValue);
-                node = new RelationQueryNode(parent, propertyName, date, operationType);
+                node = new RelationQueryNode(parent, relPath, date, operationType);
             } else if (literal.getType() == QueryConstants.TYPE_DOUBLE) {
                 double d = Double.parseDouble(stringValue);
-                node = new RelationQueryNode(parent, propertyName, d, operationType);
+                node = new RelationQueryNode(parent, relPath, d, operationType);
             } else if (literal.getType() == QueryConstants.TYPE_LONG) {
                 long l = Long.parseLong(stringValue);
-                node = new RelationQueryNode(parent, propertyName, l, operationType);
+                node = new RelationQueryNode(parent, relPath, l, operationType);
             } else if (literal.getType() == QueryConstants.TYPE_STRING) {
-                node = new RelationQueryNode(parent, propertyName, stringValue, operationType);
+                node = new RelationQueryNode(parent, relPath, stringValue, operationType);
             } else if (literal.getType() == QueryConstants.TYPE_TIMESTAMP) {
                 Calendar c = ISO8601.parse(stringValue);
-                node = new RelationQueryNode(parent, propertyName, c.getTime(), operationType);
+                node = new RelationQueryNode(parent, relPath, c.getTime(), operationType);
             }
         } catch (java.text.ParseException e) {
             throw new IllegalArgumentException(e.toString());
         } catch (NumberFormatException e) {
             throw new IllegalArgumentException(e.toString());
+        } catch (MalformedPathException e) {
+            // path is always valid, but throw anyway
+            throw new IllegalArgumentException(e.getMessage());
         }
 
         if (node == null) {

Modified: jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/sql/QueryFormat.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/sql/QueryFormat.java?view=diff&rev=477599&r1=477598&r2=477599
==============================================================================
--- jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/sql/QueryFormat.java (original)
+++ jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/sql/QueryFormat.java Tue Nov 21 02:09:51 2006
@@ -36,6 +36,7 @@
 import org.apache.jackrabbit.name.NoPrefixDeclaredException;
 import org.apache.jackrabbit.name.QName;
 import org.apache.jackrabbit.name.NameFormat;
+import org.apache.jackrabbit.name.Path;
 import org.apache.jackrabbit.util.ISO8601;
 
 import javax.jcr.query.InvalidQueryException;
@@ -266,13 +267,18 @@
         // escape quote
         String query = node.getQuery().replaceAll("'", "''");
         sb.append("CONTAINS(");
-        if (node.getPropertyName() == null) {
+        if (node.getRelativePath() == null) {
             sb.append("*");
         } else {
-            try {
-                appendName(node.getPropertyName(), resolver, sb);
-            } catch (NoPrefixDeclaredException e) {
-                exceptions.add(e);
+            if (node.getRelativePath().getLength() > 1
+                    || !node.getReferencesProperty()) {
+                exceptions.add(new InvalidQueryException("Child axis not supported in SQL"));
+            } else {
+                try {
+                    appendName(node.getRelativePath().getNameElement().getName(), resolver, sb);
+                } catch (NoPrefixDeclaredException e) {
+                    exceptions.add(e);
+                }
             }
         }
         sb.append(", '");
@@ -381,10 +387,15 @@
     }
 
     public Object visit(RelationQueryNode node, Object data) {
+        Path relPath = node.getRelativePath();
+        if (relPath.getLength() > 1) {
+            exceptions.add(new InvalidQueryException("Child axis not supported in SQL"));
+            return data;
+        }
         StringBuffer sb = (StringBuffer) data;
         try {
             StringBuffer propName = new StringBuffer();
-            appendName(node.getProperty(), resolver, propName);
+            appendName(relPath.getNameElement().getName(), resolver, propName);
             // surround name with property function
             node.acceptOperands(this, propName);
 

Modified: jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/xpath/QueryFormat.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/xpath/QueryFormat.java?view=diff&rev=477599&r1=477598&r2=477599
==============================================================================
--- jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/xpath/QueryFormat.java (original)
+++ jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/xpath/QueryFormat.java Tue Nov 21 02:09:51 2006
@@ -36,6 +36,7 @@
 import org.apache.jackrabbit.name.NoPrefixDeclaredException;
 import org.apache.jackrabbit.name.QName;
 import org.apache.jackrabbit.name.NameFormat;
+import org.apache.jackrabbit.name.Path;
 import org.apache.jackrabbit.util.ISO8601;
 import org.apache.jackrabbit.util.ISO9075;
 
@@ -119,7 +120,7 @@
                 try {
                     sb.append(pipe);
                     sb.append('@');
-                    sb.append(NameFormat.format(ISO9075.encode(selectProps[i]), resolver));
+                    NameFormat.format(ISO9075.encode(selectProps[i]), resolver, sb);
                     pipe = "|";
                 } catch (NoPrefixDeclaredException e) {
                     exceptions.add(e);
@@ -171,7 +172,7 @@
         QueryNode[] operands = node.getOperands();
         if (operands.length > 0) {
             try {
-                sb.append(NameFormat.format(XPathQueryBuilder.FN_NOT_10, resolver));
+                NameFormat.format(XPathQueryBuilder.FN_NOT_10, resolver, sb);
                 sb.append("(");
                 operands[0].accept(this, sb);
                 sb.append(")");
@@ -186,8 +187,9 @@
         StringBuffer sb = (StringBuffer) data;
         sb.append("@");
         try {
-            sb.append(NameFormat.format(ISO9075.encode(node.getPropertyName()), resolver));
-            sb.append("='").append(NameFormat.format(node.getValue(), resolver));
+            NameFormat.format(ISO9075.encode(node.getPropertyName()), resolver, sb);
+            sb.append("='");
+            NameFormat.format(node.getValue(), resolver, sb);
         } catch (NoPrefixDeclaredException e) {
             exceptions.add(e);
         }
@@ -199,8 +201,9 @@
         StringBuffer sb = (StringBuffer) data;
         try {
             sb.append("@");
-            sb.append(NameFormat.format(QName.JCR_PRIMARYTYPE, resolver));
-            sb.append("='").append(NameFormat.format(node.getValue(), resolver));
+            NameFormat.format(QName.JCR_PRIMARYTYPE, resolver, sb);
+            sb.append("='");
+            NameFormat.format(node.getValue(), resolver, sb);
             sb.append("'");
         } catch (NoPrefixDeclaredException e) {
             exceptions.add(e);
@@ -211,13 +214,29 @@
     public Object visit(TextsearchQueryNode node, Object data) {
         StringBuffer sb = (StringBuffer) data;
         try {
-            sb.append(NameFormat.format(XPathQueryBuilder.JCR_CONTAINS, resolver));
+            NameFormat.format(XPathQueryBuilder.JCR_CONTAINS, resolver, sb);
             sb.append("(");
-            if (node.getPropertyName() == null) {
+            Path relPath = node.getRelativePath();
+            if (relPath == null) {
                 sb.append(".");
             } else {
-                sb.append("@");
-                sb.append(NameFormat.format(ISO9075.encode(node.getPropertyName()), resolver));
+                Path.PathElement[] elements = relPath.getElements();
+                String slash = "";
+                for (int i = 0; i < elements.length; i++) {
+                    sb.append(slash);
+                    slash = "/";
+                    if (node.getReferencesProperty() && i == elements.length - 1) {
+                        sb.append("@");
+                    }
+                    if (elements[i].getName().equals(RelationQueryNode.STAR_NAME_TEST)) {
+                        sb.append("*");
+                    } else {
+                        NameFormat.format(ISO9075.encode(elements[i].getName()), resolver, sb);
+                    }
+                    if (elements[i].getIndex() != 0) {
+                        sb.append("[").append(elements[i].getIndex()).append("]");
+                    }
+                }
             }
             sb.append(", '");
             sb.append(node.getQuery().replaceAll("'", "''"));
@@ -253,9 +272,9 @@
         } else {
             try {
                 if (node.getNameTest().getLocalName().length() == 0) {
-                    sb.append(NameFormat.format(XPathQueryBuilder.JCR_ROOT, resolver));
+                    NameFormat.format(XPathQueryBuilder.JCR_ROOT, resolver, sb);
                 } else {
-                    sb.append(NameFormat.format(ISO9075.encode(node.getNameTest()), resolver));
+                    NameFormat.format(ISO9075.encode(node.getNameTest()), resolver, sb);
                 }
             } catch (NoPrefixDeclaredException e) {
                 exceptions.add(e);
@@ -276,14 +295,14 @@
     public Object visit(DerefQueryNode node, Object data) {
         StringBuffer sb = (StringBuffer) data;
         try {
-            sb.append(NameFormat.format(XPathQueryBuilder.JCR_DEREF, resolver));
+            NameFormat.format(XPathQueryBuilder.JCR_DEREF, resolver, sb);
             sb.append("(@");
-            sb.append(NameFormat.format(ISO9075.encode(node.getRefProperty()), resolver));
+            NameFormat.format(ISO9075.encode(node.getRefProperty()), resolver, sb);
             sb.append(", '");
             if (node.getNameTest() == null) {
                 sb.append("*");
             } else {
-                sb.append(NameFormat.format(ISO9075.encode(node.getNameTest()), resolver));
+                NameFormat.format(ISO9075.encode(node.getNameTest()), resolver, sb);
             }
             sb.append("')");
         } catch (NoPrefixDeclaredException e) {
@@ -296,64 +315,80 @@
         StringBuffer sb = (StringBuffer) data;
         try {
 
-            StringBuffer propName = new StringBuffer();
+            StringBuffer propPath = new StringBuffer();
             // only encode if not position function
-            if (node.getProperty().equals(XPathQueryBuilder.FN_POSITION_FULL)) {
-                NameFormat.format(node.getProperty(), resolver, propName);
+            Path relPath = node.getRelativePath();
+            if (relPath.getNameElement().getName().equals(XPathQueryBuilder.FN_POSITION_FULL)) {
+                NameFormat.format(XPathQueryBuilder.FN_POSITION_FULL, resolver, propPath);
             } else {
-                propName.append("@");
-                NameFormat.format(ISO9075.encode(node.getProperty()), resolver, propName);
+                Path.PathElement[] elements = relPath.getElements();
+                String slash = "";
+                for (int i = 0; i < elements.length; i++) {
+                    propPath.append(slash);
+                    slash = "/";
+                    if (i == elements.length - 1) {
+                        propPath.append("@");
+                    }
+                    if (elements[i].getName().equals(RelationQueryNode.STAR_NAME_TEST)) {
+                        propPath.append("*");
+                    } else {
+                        NameFormat.format(ISO9075.encode(elements[i].getName()), resolver, propPath);
+                    }
+                    if (elements[i].getIndex() != 0) {
+                        propPath.append("[").append(elements[i].getIndex()).append("]");
+                    }
+                }
             }
 
             // surround name with property function
-            node.acceptOperands(this, propName);
+            node.acceptOperands(this, propPath);
 
             if (node.getOperation() == OPERATION_EQ_VALUE) {
-                sb.append(propName).append(" eq ");
+                sb.append(propPath).append(" eq ");
                 appendValue(node, sb);
             } else if (node.getOperation() == OPERATION_EQ_GENERAL) {
-                sb.append(propName).append(" = ");
+                sb.append(propPath).append(" = ");
                 appendValue(node, sb);
             } else if (node.getOperation() == OPERATION_GE_GENERAL) {
-                sb.append(propName).append(" >= ");
+                sb.append(propPath).append(" >= ");
                 appendValue(node, sb);
             } else if (node.getOperation() == OPERATION_GE_VALUE) {
-                sb.append(propName).append(" ge ");
+                sb.append(propPath).append(" ge ");
                 appendValue(node, sb);
             } else if (node.getOperation() == OPERATION_GT_GENERAL) {
-                sb.append(propName).append(" > ");
+                sb.append(propPath).append(" > ");
                 appendValue(node, sb);
             } else if (node.getOperation() == OPERATION_GT_VALUE) {
-                sb.append(propName).append(" gt ");
+                sb.append(propPath).append(" gt ");
                 appendValue(node, sb);
             } else if (node.getOperation() == OPERATION_LE_GENERAL) {
-                sb.append(propName).append(" <= ");
+                sb.append(propPath).append(" <= ");
                 appendValue(node, sb);
             } else if (node.getOperation() == OPERATION_LE_VALUE) {
-                sb.append(propName).append(" le ");
+                sb.append(propPath).append(" le ");
                 appendValue(node, sb);
             } else if (node.getOperation() == OPERATION_LIKE) {
-                sb.append(NameFormat.format(XPathQueryBuilder.JCR_LIKE, resolver));
-                sb.append("(").append(propName).append(", ");
+                NameFormat.format(XPathQueryBuilder.JCR_LIKE, resolver, sb);
+                sb.append("(").append(propPath).append(", ");
                 appendValue(node, sb);
                 sb.append(")");
             } else if (node.getOperation() == OPERATION_LT_GENERAL) {
-                sb.append(propName).append(" < ");
+                sb.append(propPath).append(" < ");
                 appendValue(node, sb);
             } else if (node.getOperation() == OPERATION_LT_VALUE) {
-                sb.append(propName).append(" lt ");
+                sb.append(propPath).append(" lt ");
                 appendValue(node, sb);
             } else if (node.getOperation() == OPERATION_NE_GENERAL) {
-                sb.append(propName).append(" != ");
+                sb.append(propPath).append(" != ");
                 appendValue(node, sb);
             } else if (node.getOperation() == OPERATION_NE_VALUE) {
-                sb.append(propName).append(" ne ");
+                sb.append(propPath).append(" ne ");
                 appendValue(node, sb);
             } else if (node.getOperation() == OPERATION_NULL) {
-                sb.append(NameFormat.format(XPathQueryBuilder.FN_NOT, resolver));
-                sb.append("(").append(propName).append(")");
+                NameFormat.format(XPathQueryBuilder.FN_NOT, resolver, sb);
+                sb.append("(").append(propPath).append(")");
             } else if (node.getOperation() == OPERATION_NOT_NULL) {
-                sb.append(propName);
+                sb.append(propPath);
             } else {
                 exceptions.add(new InvalidQueryException("Invalid operation: " + node.getOperation()));
             }
@@ -372,7 +407,8 @@
             for (int i = 0; i < specs.length; i++) {
                 sb.append(comma);
                 QName prop = ISO9075.encode(specs[i].getProperty());
-                sb.append(" @").append(NameFormat.format(prop, resolver));
+                sb.append(" @");
+                NameFormat.format(prop, resolver, sb);
                 if (!specs[i].isAscending()) {
                     sb.append(" descending");
                 }
@@ -425,7 +461,7 @@
         } else if (node.getValueType() == TYPE_DATE || node.getValueType() == TYPE_TIMESTAMP) {
             Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
             cal.setTime(node.getDateValue());
-            b.append(NameFormat.format(XPathQueryBuilder.XS_DATETIME, resolver));
+            NameFormat.format(XPathQueryBuilder.XS_DATETIME, resolver, b);
             b.append("('").append(ISO8601.format(cal)).append("')");
         } else if (node.getValueType() == TYPE_POSITION) {
             if (node.getPositionValue() == LocationStepQueryNode.LAST) {

Modified: jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/xpath/XPathQueryBuilder.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/xpath/XPathQueryBuilder.java?view=diff&rev=477599&r1=477598&r2=477599
==============================================================================
--- jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/xpath/XPathQueryBuilder.java (original)
+++ jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/xpath/XPathQueryBuilder.java Tue Nov 21 02:09:51 2006
@@ -38,6 +38,7 @@
 import org.apache.jackrabbit.name.QName;
 import org.apache.jackrabbit.name.UnknownPrefixException;
 import org.apache.jackrabbit.name.NameFormat;
+import org.apache.jackrabbit.name.Path;
 import org.apache.jackrabbit.util.ISO8601;
 import org.apache.jackrabbit.util.ISO9075;
 import org.apache.commons.collections.map.ReferenceMap;
@@ -367,10 +368,9 @@
                 } else {
                     if (queryNode.getType() == QueryNode.TYPE_PATH) {
                         createLocationStep(node, (NAryQueryNode) queryNode);
-                    } else if (queryNode.getType() == QueryNode.TYPE_TEXTSEARCH) {
-                        // ignore
-                    } else {
-                        exceptions.add(new InvalidQueryException("Only attribute axis is allowed in predicate"));
+                    } else if (queryNode.getType() == QueryNode.TYPE_TEXTSEARCH
+                            || queryNode.getType() == QueryNode.TYPE_RELATION) {
+                        node.childrenAccept(this, queryNode);
                     }
                 }
                 break;
@@ -531,27 +531,27 @@
             SimpleNode child = (SimpleNode) node.jjtGetChild(0);
             if (child.getId() == JJTQNAME || child.getId() == JJTQNAMEFORITEMTYPE) {
                 try {
+                    QName name = ISO9075.decode(NameFormat.parse(child.getValue(), resolver));
                     if (queryNode.getType() == QueryNode.TYPE_LOCATION) {
-                        QName name = ISO9075.decode(NameFormat.parse(child.getValue(), resolver));
                         if (name.equals(JCR_ROOT)) {
                             name = LocationStepQueryNode.EMPTY_NAME;
                         }
                         ((LocationStepQueryNode) queryNode).setNameTest(name);
                     } else if (queryNode.getType() == QueryNode.TYPE_DEREF) {
-                        QName name = ISO9075.decode(NameFormat.parse(child.getValue(), resolver));
                         ((DerefQueryNode) queryNode).setRefProperty(name);
                     } else if (queryNode.getType() == QueryNode.TYPE_RELATION) {
-                        QName name = ISO9075.decode(NameFormat.parse(child.getValue(), resolver));
-                        ((RelationQueryNode) queryNode).setProperty(name);
+                        Path.PathElement element = Path.PathElement.create(name);
+                        ((RelationQueryNode) queryNode).addPathElement(element);
                     } else if (queryNode.getType() == QueryNode.TYPE_PATH) {
-                        QName name = ISO9075.decode(NameFormat.parse(child.getValue(), resolver));
                         root.addSelectProperty(name);
                     } else if (queryNode.getType() == QueryNode.TYPE_ORDER) {
-                        QName name = ISO9075.decode(NameFormat.parse(child.getValue(), resolver));
                         root.getOrderNode().addOrderSpec(name, true);
                     } else if (queryNode.getType() == QueryNode.TYPE_TEXTSEARCH) {
-                        QName name = ISO9075.decode(NameFormat.parse(child.getValue(), resolver));
-                        ((TextsearchQueryNode) queryNode).setPropertyName(name);
+                        TextsearchQueryNode ts = (TextsearchQueryNode) queryNode;
+                        ts.addPathElement(Path.PathElement.create(name));
+                        if (isAttributeNameTest(node)) {
+                            ts.setReferencesProperty(true);
+                        }
                     }
                 } catch (IllegalNameException e) {
                     exceptions.add(new InvalidQueryException("Illegal name: " + child.getValue()));
@@ -561,6 +561,12 @@
             } else if (child.getId() == JJTSTAR) {
                 if (queryNode.getType() == QueryNode.TYPE_LOCATION) {
                     ((LocationStepQueryNode) queryNode).setNameTest(null);
+                } else if (queryNode.getType() == QueryNode.TYPE_RELATION) {
+                    ((RelationQueryNode) queryNode).addPathElement(
+                            Path.PathElement.create(RelationQueryNode.STAR_NAME_TEST));
+                } else if (queryNode.getType() == QueryNode.TYPE_TEXTSEARCH) {
+                    ((TextsearchQueryNode) queryNode).addPathElement(
+                            Path.PathElement.create(RelationQueryNode.STAR_NAME_TEST));
                 }
             } else {
                 exceptions.add(new InvalidQueryException("Unsupported location for name test: " + child));
@@ -755,7 +761,7 @@
                         // assign property name
                         node.jjtGetChild(1).jjtAccept(this, like);
                         // check property name
-                        if (like.getProperty() == null) {
+                        if (like.getRelativePath() == null) {
                             exceptions.add(new InvalidQueryException("Wrong first argument type for jcr:like"));
                         }
 
@@ -793,7 +799,7 @@
                         // set dummy value to set type of relation query node
                         // will be overwritten when the tree is furhter parsed.
                         rel.setPositionValue(1);
-                        rel.setProperty(FN_POSITION_FULL);
+                        rel.addPathElement(Path.PathElement.create(FN_POSITION_FULL));
                     } else {
                         exceptions.add(new InvalidQueryException("Unsupported expression with position(). Only = is supported."));
                     }
@@ -943,7 +949,7 @@
      * Returns true if <code>node</code> has a child node which is the attribute
      * axis.
      *
-     * @param node a node with type {@link org.apache.jackrabbit.core.query.xpath.XPathTreeConstants#JJTSTEPEXPR}.
+     * @param node a node with type {@link #JJTSTEPEXPR}.
      * @return <code>true</code> if this step expression uses the attribute axis.
      */
     private boolean isAttributeAxis(SimpleNode node) {
@@ -951,6 +957,29 @@
             if (((SimpleNode) node.jjtGetChild(i)).getId() == JJTAT) {
                 return true;
             }
+        }
+        return false;
+    }
+
+    /**
+     * Returns <code>true</code> if the NodeTest <code>node</code> is an
+     * attribute name test.
+     * Example:
+     * <pre>
+     * StepExpr
+     *     At @
+     *     NodeTest
+     *         NameTest
+     *             QName foo
+     * </pre>
+     * @param node a node with type {@link #JJTNAMETEST}.
+     * @return <code>true</code> if the name test <code>node</code> is on the
+     * attribute axis.
+     */
+    private boolean isAttributeNameTest(SimpleNode node) {
+        SimpleNode stepExpr = (SimpleNode) node.jjtGetParent().jjtGetParent();
+        if (stepExpr.getId() == JJTSTEPEXPR) {
+            return ((SimpleNode) stepExpr.jjtGetChild(0)).getId() == JJTAT;
         }
         return false;
     }

Added: jackrabbit/trunk/jackrabbit/src/test/java/org/apache/jackrabbit/core/query/ChildAxisQueryTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit/src/test/java/org/apache/jackrabbit/core/query/ChildAxisQueryTest.java?view=auto&rev=477599
==============================================================================
--- jackrabbit/trunk/jackrabbit/src/test/java/org/apache/jackrabbit/core/query/ChildAxisQueryTest.java (added)
+++ jackrabbit/trunk/jackrabbit/src/test/java/org/apache/jackrabbit/core/query/ChildAxisQueryTest.java Tue Nov 21 02:09:51 2006
@@ -0,0 +1,234 @@
+/*
+ * 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.RepositoryException;
+import javax.jcr.Node;
+import java.io.ByteArrayOutputStream;
+import java.io.OutputStreamWriter;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.util.Calendar;
+
+/**
+ * <code>ChildAxisQueryTest</code> tests queries with a child axis in their
+ * predicates.
+ */
+public class ChildAxisQueryTest extends AbstractQueryTest {
+
+    /**
+     * Predicate with child node axis in a relation
+     */
+    public void testRelationQuery() throws RepositoryException {
+        Node n1 = testRootNode.addNode(nodeName1);
+        n1.setProperty(propertyName1, 1);
+        Node n2 = testRootNode.addNode(nodeName1);
+        n2.setProperty(propertyName1, 2);
+        Node n3 = testRootNode.addNode(nodeName1);
+        n3.setProperty(propertyName1, 3);
+
+        testRootNode.save();
+
+        String base = testPath + "[" + nodeName1 + "/@" + propertyName1;
+        executeXPathQuery(base + " = 1]", new Node[]{testRootNode});
+        executeXPathQuery(base + " = 2]", new Node[]{testRootNode});
+        executeXPathQuery(base + " = 3]", new Node[]{testRootNode});
+        executeXPathQuery(base + " = 4]", new Node[]{});
+        executeXPathQuery(base + " > 0]", new Node[]{testRootNode});
+        executeXPathQuery(base + " > 1]", new Node[]{testRootNode});
+        executeXPathQuery(base + " > 2]", new Node[]{testRootNode});
+        executeXPathQuery(base + " > 3]", new Node[]{});
+        executeXPathQuery(base + " >= 1]", new Node[]{testRootNode});
+        executeXPathQuery(base + " >= 2]", new Node[]{testRootNode});
+        executeXPathQuery(base + " >= 3]", new Node[]{testRootNode});
+        executeXPathQuery(base + " >= 4]", new Node[]{});
+        executeXPathQuery(base + " < 1]", new Node[]{});
+        executeXPathQuery(base + " < 2]", new Node[]{testRootNode});
+        executeXPathQuery(base + " < 3]", new Node[]{testRootNode});
+        executeXPathQuery(base + " < 4]", new Node[]{testRootNode});
+        executeXPathQuery(base + " <= 0]", new Node[]{});
+        executeXPathQuery(base + " <= 1]", new Node[]{testRootNode});
+        executeXPathQuery(base + " <= 2]", new Node[]{testRootNode});
+        executeXPathQuery(base + " <= 3]", new Node[]{testRootNode});
+        executeXPathQuery(base + " != 0]", new Node[]{testRootNode});
+        executeXPathQuery(base + " != 1]", new Node[]{testRootNode});
+        executeXPathQuery(base + " != 2]", new Node[]{testRootNode});
+        executeXPathQuery(base + " != 3]", new Node[]{testRootNode});
+    }
+
+    public void testRelationQueryDeep() throws RepositoryException {
+        Node n = testRootNode.addNode(nodeName1).addNode(nodeName2);
+        Node n1 = n.addNode(nodeName3);
+        n1.setProperty(propertyName1, 1);
+        Node n2 = n.addNode(nodeName3);
+        n2.setProperty(propertyName1, 2);
+        Node n3 = n.addNode(nodeName3);
+        n3.setProperty(propertyName1, 3);
+
+        testRootNode.save();
+
+        String base = testPath + "[" + nodeName1 + "/" + nodeName2 + "/" +
+                nodeName3 + "/@" + propertyName1;
+        executeXPathQuery(base + " = 1]", new Node[]{testRootNode});
+        executeXPathQuery(base + " = 2]", new Node[]{testRootNode});
+        executeXPathQuery(base + " = 3]", new Node[]{testRootNode});
+        executeXPathQuery(base + " = 4]", new Node[]{});
+        executeXPathQuery(base + " > 0]", new Node[]{testRootNode});
+        executeXPathQuery(base + " > 1]", new Node[]{testRootNode});
+        executeXPathQuery(base + " > 2]", new Node[]{testRootNode});
+        executeXPathQuery(base + " > 3]", new Node[]{});
+        executeXPathQuery(base + " >= 1]", new Node[]{testRootNode});
+        executeXPathQuery(base + " >= 2]", new Node[]{testRootNode});
+        executeXPathQuery(base + " >= 3]", new Node[]{testRootNode});
+        executeXPathQuery(base + " >= 4]", new Node[]{});
+        executeXPathQuery(base + " < 1]", new Node[]{});
+        executeXPathQuery(base + " < 2]", new Node[]{testRootNode});
+        executeXPathQuery(base + " < 3]", new Node[]{testRootNode});
+        executeXPathQuery(base + " < 4]", new Node[]{testRootNode});
+        executeXPathQuery(base + " <= 0]", new Node[]{});
+        executeXPathQuery(base + " <= 1]", new Node[]{testRootNode});
+        executeXPathQuery(base + " <= 2]", new Node[]{testRootNode});
+        executeXPathQuery(base + " <= 3]", new Node[]{testRootNode});
+        executeXPathQuery(base + " != 0]", new Node[]{testRootNode});
+        executeXPathQuery(base + " != 1]", new Node[]{testRootNode});
+        executeXPathQuery(base + " != 2]", new Node[]{testRootNode});
+        executeXPathQuery(base + " != 3]", new Node[]{testRootNode});
+    }
+
+    public void testMultiRelation() throws RepositoryException {
+        Node level1 = testRootNode.addNode(nodeName1);
+        level1.setProperty(propertyName1, "foo");
+        Node level2 = level1.addNode(nodeName2);
+        level2.setProperty(propertyName1, "bar");
+        Node n1 = level2.addNode(nodeName3);
+        n1.setProperty(propertyName2, 1);
+        Node n2 = level2.addNode(nodeName3);
+        n2.setProperty(propertyName2, 2);
+        Node n3 = level2.addNode(nodeName3);
+        n3.setProperty(propertyName2, 3);
+
+        testRootNode.save();
+
+        String base = testPath + "[" + nodeName1 + "/" + nodeName2 + "/" +
+                nodeName3 + "/@" + propertyName2;
+        executeXPathQuery(base + " = 1]", new Node[]{testRootNode});
+        executeXPathQuery(base + " = 1 and " + nodeName1 + "/@" +
+                propertyName1 + " = 'foo' and " + nodeName1 + "/" + nodeName2 +
+                "/@" + propertyName1 + " = 'bar']", new Node[]{testRootNode});
+        executeXPathQuery(base + " = 1 and " + nodeName1 + "/@" +
+                propertyName1 + " = 'foo' and " + nodeName1 + "/" + nodeName2 +
+                "/@" + propertyName1 + " = 'bar']", new Node[]{testRootNode});
+        executeXPathQuery(base + " = 1 and " + nodeName1 + "/@" +
+                propertyName1 + " = 'foo' and " + nodeName2 +
+                "/@" + propertyName1 + " = 'bar']", new Node[]{});
+    }
+
+    public void testLike() throws RepositoryException {
+        Node n1 = testRootNode.addNode(nodeName1);
+        n1.setProperty(propertyName1, "foo");
+        Node n2 = testRootNode.addNode(nodeName1);
+        n2.setProperty(propertyName1, "foobar");
+        Node n3 = testRootNode.addNode(nodeName1);
+        n3.setProperty(propertyName1, "foo bar");
+
+        testRootNode.save();
+
+        String base = testPath + "[jcr:like(" + nodeName1 + "/@" + propertyName1;
+        executeXPathQuery(base + ", 'fo_')]", new Node[]{testRootNode});
+        executeXPathQuery(base + ", 'foo_ar')]", new Node[]{testRootNode});
+        executeXPathQuery(base + ", 'foo %')]", new Node[]{testRootNode});
+        executeXPathQuery(base + ", 'f_oba')]", new Node[]{});
+    }
+
+    public void testContains() throws RepositoryException {
+        Node level1 = testRootNode.addNode(nodeName1);
+        level1.setProperty(propertyName1, "The quick brown fox jumps over the lazy dog.");
+        Node level2 = level1.addNode(nodeName2);
+        level2.setProperty(propertyName1, "Franz jagt im total verwahrlosten Taxi quer durch Bayern.");
+        Node n1 = level2.addNode(nodeName3);
+        n1.setProperty(propertyName2, 1);
+        Node n2 = level2.addNode(nodeName3);
+        n2.setProperty(propertyName2, 2);
+        Node n3 = level2.addNode(nodeName3);
+        n3.setProperty(propertyName2, 3);
+
+        testRootNode.save();
+
+        String base = testPath + "[jcr:contains(";
+        executeXPathQuery(base + nodeName1 + "/@" + propertyName1 + ", 'lazy')" +
+                " and " + nodeName1 + "/" + nodeName2 + "/" + nodeName3 + "/@" + propertyName2 + " = 2]",
+                new Node[]{testRootNode});
+        executeXPathQuery(base + nodeName1 + "/" + nodeName2 + "/@" + propertyName1 + ", 'franz')" +
+                " and " + nodeName1 + "/" + nodeName2 + "/" + nodeName3 + "/@" + propertyName2 + " = 3]",
+                new Node[]{testRootNode});
+        executeXPathQuery(base + nodeName1 + ", 'lazy')" +
+                " and " + nodeName1 + "/" + nodeName2 + "/" + nodeName3 + "/@" + propertyName2 + " = 1]",
+                new Node[]{testRootNode});
+        executeXPathQuery(base + nodeName1 + "/" + nodeName2 + ", 'franz')" +
+                " and " + nodeName1 + "/" + nodeName2 + "/" + nodeName3 + "/@" + propertyName2 + " = 1]",
+                new Node[]{testRootNode});
+    }
+
+    public void testNtFile() throws RepositoryException, IOException {
+        Node file = testRootNode.addNode(nodeName1, "nt:file");
+        Node resource = file.addNode("jcr:content", "nt:resource");
+        resource.setProperty("jcr:encoding", "UTF-8");
+        resource.setProperty("jcr:mimeType", "text/plain");
+        ByteArrayOutputStream data = new ByteArrayOutputStream();
+        OutputStreamWriter writer = new OutputStreamWriter(data, "UTF-8");
+        writer.write("The quick brown fox jumps over the lazy dog.");
+        writer.close();
+        resource.setProperty("jcr:data", new ByteArrayInputStream(data.toByteArray()));
+        resource.setProperty("jcr:lastModified", Calendar.getInstance());
+
+        testRootNode.save();
+        String xpath = testPath + "/*[jcr:contains(jcr:content, 'lazy')]";
+        executeXPathQuery(xpath, new Node[]{file});
+    }
+
+    public void testStarNameTest() throws RepositoryException {
+        Node level1 = testRootNode.addNode(nodeName1);
+        level1.setProperty(propertyName1, "The quick brown fox jumps over the lazy dog.");
+        Node level2 = level1.addNode(nodeName2);
+        level2.setProperty(propertyName1, "Franz jagt im total verwahrlosten Taxi quer durch Bayern.");
+        Node n1 = level2.addNode(nodeName3);
+        n1.setProperty(propertyName2, 1);
+        Node n2 = level2.addNode(nodeName3);
+        n2.setProperty(propertyName2, 2);
+        Node n3 = level2.addNode(nodeName4);
+        n3.setProperty(propertyName2, 3);
+
+        testRootNode.save();
+
+        String base = testPath + "[jcr:contains(";
+        executeXPathQuery(base + nodeName1 + "/@" + propertyName1 + ", 'lazy')" +
+                " and " + nodeName1 + "/" + nodeName2 + "/" + nodeName3 + "/@" + propertyName2 + " = 3]",
+                new Node[]{});
+        executeXPathQuery(base + nodeName1 + "/@" + propertyName1 + ", 'lazy')" +
+                " and " + nodeName1 + "/" + nodeName2 + "/*/@" + propertyName2 + " = 3]",
+                new Node[]{testRootNode});
+
+        executeXPathQuery(base + "*/@" + propertyName1 + ", 'lazy')]",
+                new Node[]{testRootNode});
+        executeXPathQuery(base + nodeName1 + "/*, 'franz')]",
+                new Node[]{testRootNode});
+        executeXPathQuery(base + "*/*, 'franz')]",
+                new Node[]{testRootNode});
+        executeXPathQuery(base + "*/*, 'lazy')]",
+                new Node[]{});
+    }
+}

Propchange: jackrabbit/trunk/jackrabbit/src/test/java/org/apache/jackrabbit/core/query/ChildAxisQueryTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: jackrabbit/trunk/jackrabbit/src/test/java/org/apache/jackrabbit/core/query/TestAll.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit/src/test/java/org/apache/jackrabbit/core/query/TestAll.java?view=diff&rev=477599&r1=477598&r2=477599
==============================================================================
--- jackrabbit/trunk/jackrabbit/src/test/java/org/apache/jackrabbit/core/query/TestAll.java (original)
+++ jackrabbit/trunk/jackrabbit/src/test/java/org/apache/jackrabbit/core/query/TestAll.java Tue Nov 21 02:09:51 2006
@@ -46,6 +46,7 @@
         suite.addTestSuite(DerefTest.class);
         suite.addTestSuite(VersionStoreQueryTest.class);
         suite.addTestSuite(UpperLowerCaseQueryTest.class);
+        suite.addTestSuite(ChildAxisQueryTest.class);
 
         // exclude long running tests per default
         //suite.addTestSuite(MassiveRangeTest.class);