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);