Author: rhauch
Date: 2009-10-03 00:09:29 -0400 (Sat, 03 Oct 2009)
New Revision: 1286
Added:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/NodeDepth.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/NodePath.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/parse/InvalidQueryException.java
trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/xpath/XPathQueryParser.java
trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/xpath/XPathToQueryTranslator.java
trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/xpath/XPathToQueryTranslatorTest.java
Removed:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/InvalidQueryException.java
Modified:
trunk/.gitignore
trunk/dna-graph/src/main/java/org/jboss/dna/graph/GraphI18n.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/property/DateTime.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/property/basic/JodaDateTime.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/QueryBuilder.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/QueryEngine.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/AllNodes.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Literal.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/NamedSelector.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Operator.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Visitor.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Visitors.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/RewriteIdentityJoins.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/parse/QueryParser.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/parse/SqlQueryParser.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/ProcessingComponent.java
trunk/dna-graph/src/main/resources/org/jboss/dna/graph/GraphI18n.properties
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/QueryBuilderTest.java
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/parse/SqlQueryParserTest.java
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/process/AbstractQueryResultsTest.java
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/process/SortValuesComponentTest.java
trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/xpath/XPath.java
trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/xpath/XPathParser.java
trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/xpath/XPathParserTest.java
trunk/dna-search/src/main/java/org/jboss/dna/search/LuceneQueryEngine.java
Log:
DNA-467 Add search/query support to the graph API
DNA-468 Add XPath query language support
Added a lot more implementation and unit tests behind the XPath parser, the XPath AST
objects, and the translator that converts an XPath AST into a SQL query model. Found and
fixed a number of issues in several of the 'dna-graph' classes, including some
tweaks to the SQL query odel, the SQL parser, minor improvements to the JodaDateTime
class, and some changes to the exception handling in the 'dna-graph' query model
and builder.
At this point, the XPath support is pretty good, though still not complete. It may be
good enough to use for a while - until we have more examples. The unit tests are
verifying not only that the XPath expressions can be parsed, but that they're also
converted correctly to an expected SQL representation.
The XPath functionality has not yet been integrated into the JCR implementation, since
that requires hooking up the LuceneQueryEngine (which still needs some work). However, I
plan to start putting more of the pieces together and focusing on wrapping up the
LuceneQueryEngine.
Modified: trunk/.gitignore
===================================================================
--- trunk/.gitignore 2009-10-03 04:07:41 UTC (rev 1285)
+++ trunk/.gitignore 2009-10-03 04:09:29 UTC (rev 1286)
@@ -39,6 +39,7 @@
/web/dna-web-jcr-rest/target/
/web/dna-web-jcr-rest-war/target/
+/web/dna-web-jcr-rest-client/target/
/extensions/dna-classloader-maven/target
/extensions/dna-common-jdbc/target
Modified: trunk/dna-graph/src/main/java/org/jboss/dna/graph/GraphI18n.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/GraphI18n.java 2009-10-03 04:07:41
UTC (rev 1285)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/GraphI18n.java 2009-10-03 04:09:29
UTC (rev 1286)
@@ -128,6 +128,7 @@
public static I18n childNotFound;
/* Query */
+ public static I18n unknownQueryLanguage;
public static I18n tableDoesNotExist;
public static I18n columnDoesNotExistOnTable;
public static I18n columnDoesNotExistInQuery;
Modified: trunk/dna-graph/src/main/java/org/jboss/dna/graph/property/DateTime.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/property/DateTime.java 2009-10-03
04:07:41 UTC (rev 1285)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/property/DateTime.java 2009-10-03
04:09:29 UTC (rev 1286)
@@ -71,6 +71,14 @@
long getMilliseconds();
/**
+ * Get the number of milliseconds from 1970-01-01T00:00Z with this time converted to
UTC. This value is consistent with the
+ * JDK {@link java.util.Date Date} and {@link java.util.Calendar Calendar} classes.
+ *
+ * @return the number of milliseconds from 1970-01-01T00:00Z in the UTC time zone
+ */
+ long getMillisecondsInUtc();
+
+ /**
* Get this instance represented as a standard JDK {@link java.util.Date} instance.
Note that this conversion loses the time
* zone information, as the standard JDK {@link java.util.Date} does not represent
time zones.
*
Modified:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/property/basic/JodaDateTime.java
===================================================================
---
trunk/dna-graph/src/main/java/org/jboss/dna/graph/property/basic/JodaDateTime.java 2009-10-03
04:07:41 UTC (rev 1285)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/property/basic/JodaDateTime.java 2009-10-03
04:09:29 UTC (rev 1286)
@@ -40,6 +40,8 @@
@Immutable
public class JodaDateTime implements org.jboss.dna.graph.property.DateTime {
+ private static final DateTimeZone UTC_ZONE = DateTimeZone.forID("UTC");
+
/**
*/
private static final long serialVersionUID = -730188225988292422L;
@@ -47,36 +49,44 @@
private static final int MILLIS_IN_HOUR = 1000 * 60 * 60;
private final DateTime instance;
+ private final long millisInUtc;
public JodaDateTime() {
this.instance = new DateTime();
+ this.millisInUtc = instance.withZone(UTC_ZONE).getMillis();
}
public JodaDateTime( String iso8601 ) {
this.instance = new DateTime(iso8601);
+ this.millisInUtc = instance.withZone(UTC_ZONE).getMillis();
}
public JodaDateTime( String iso8601,
String timeZoneId ) {
this.instance = new DateTime(iso8601, DateTimeZone.forID(timeZoneId));
+ this.millisInUtc = instance.withZone(UTC_ZONE).getMillis();
}
public JodaDateTime( long milliseconds ) {
this.instance = new DateTime(milliseconds);
+ this.millisInUtc = instance.withZone(UTC_ZONE).getMillis();
}
public JodaDateTime( long milliseconds,
Chronology chronology ) {
this.instance = new DateTime(milliseconds, chronology);
+ this.millisInUtc = instance.withZone(UTC_ZONE).getMillis();
}
public JodaDateTime( long milliseconds,
String timeZoneId ) {
this.instance = new DateTime(milliseconds, DateTimeZone.forID(timeZoneId));
+ this.millisInUtc = instance.withZone(UTC_ZONE).getMillis();
}
public JodaDateTime( DateTimeZone dateTimeZone ) {
this.instance = new DateTime(dateTimeZone);
+ this.millisInUtc = instance.withZone(UTC_ZONE).getMillis();
}
public JodaDateTime( int year,
@@ -87,6 +97,7 @@
int secondOfMinute,
int millisecondsOfSecond ) {
this.instance = new DateTime(year, monthOfYear, dayOfMonth, hourOfDay,
minuteOfHour, secondOfMinute, millisecondsOfSecond);
+ this.millisInUtc = instance.withZone(UTC_ZONE).getMillis();
}
public JodaDateTime( int year,
@@ -99,6 +110,7 @@
Chronology chronology ) {
this.instance = new DateTime(year, monthOfYear, dayOfMonth, hourOfDay,
minuteOfHour, secondOfMinute,
millisecondsOfSecond, chronology);
+ this.millisInUtc = instance.withZone(UTC_ZONE).getMillis();
}
public JodaDateTime( int year,
@@ -111,6 +123,7 @@
DateTimeZone dateTimeZone ) {
this.instance = new DateTime(year, monthOfYear, dayOfMonth, hourOfDay,
minuteOfHour, secondOfMinute,
millisecondsOfSecond, dateTimeZone);
+ this.millisInUtc = instance.withZone(UTC_ZONE).getMillis();
}
public JodaDateTime( int year,
@@ -123,6 +136,7 @@
int timeZoneOffsetHours ) {
this.instance = new DateTime(year, monthOfYear, dayOfMonth, hourOfDay,
minuteOfHour, secondOfMinute,
millisecondsOfSecond,
DateTimeZone.forOffsetHours(timeZoneOffsetHours));
+ this.millisInUtc = instance.withZone(UTC_ZONE).getMillis();
}
public JodaDateTime( int year,
@@ -135,18 +149,22 @@
String timeZoneId ) {
this.instance = new DateTime(year, monthOfYear, dayOfMonth, hourOfDay,
minuteOfHour, secondOfMinute,
millisecondsOfSecond,
DateTimeZone.forID(timeZoneId));
+ this.millisInUtc = instance.withZone(UTC_ZONE).getMillis();
}
public JodaDateTime( java.util.Date jdkDate ) {
this.instance = new DateTime(jdkDate);
+ this.millisInUtc = instance.withZone(UTC_ZONE).getMillis();
}
public JodaDateTime( java.util.Calendar jdkCalendar ) {
this.instance = new DateTime(jdkCalendar);
+ this.millisInUtc = instance.withZone(UTC_ZONE).getMillis();
}
public JodaDateTime( DateTime dateTime ) {
this.instance = dateTime; // it's immutable, so just hold onto the supplied
instance
+ this.millisInUtc = instance.withZone(UTC_ZONE).getMillis();
}
/**
@@ -207,7 +225,16 @@
/**
* {@inheritDoc}
+ *
+ * @see org.jboss.dna.graph.property.DateTime#getMillisecondsInUtc()
*/
+ public long getMillisecondsInUtc() {
+ return millisInUtc;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
public int getMinuteOfHour() {
return this.instance.getMinuteOfHour();
}
@@ -315,12 +342,8 @@
* {@inheritDoc}
*/
public int compareTo( org.jboss.dna.graph.property.DateTime that ) {
- if (that instanceof JodaDateTime) {
- return this.instance.compareTo(((JodaDateTime)that).instance);
- }
- if (that == null) return 1;
- long diff = this.toUtcTimeZone().getMilliseconds() -
that.toUtcTimeZone().getMilliseconds();
- return (int)diff;
+ long diff = this.getMillisecondsInUtc() - that.getMillisecondsInUtc();
+ return diff == 0 ? 0 : diff > 0 ? 1 : -1;
}
/**
@@ -328,7 +351,7 @@
*/
@Override
public int hashCode() {
- return this.instance.hashCode();
+ return (int)this.millisInUtc;
}
/**
@@ -337,18 +360,13 @@
@Override
public boolean equals( Object obj ) {
if (obj == this) return true;
- if (obj instanceof JodaDateTime) {
- JodaDateTime that = (JodaDateTime)obj;
-
- /*
- * The equals semantics for JodaDateTimes are very strict, implying that not
only are the two instants represented
- * by the JodaDateTimes logically equivalent, but also that the Chronology
and DateTimeZone are the same.
- * Instead, use isEqual, which only checks that the two JodaDateTimes are
logically equivalent.
- */
- return this.instance.isEqual(that.instance);
+ if (obj instanceof org.jboss.dna.graph.property.DateTime) {
+ org.jboss.dna.graph.property.DateTime that =
(org.jboss.dna.graph.property.DateTime)obj;
+ return this.getMillisecondsInUtc() == that.getMillisecondsInUtc();
}
if (obj instanceof DateTime) {
- return this.instance.equals(obj);
+ DateTime that = (DateTime)obj;
+ return this.getMillisecondsInUtc() == that.withZone(UTC_ZONE).getMillis();
}
return false;
}
@@ -365,7 +383,9 @@
* {@inheritDoc}
*/
public org.jboss.dna.graph.property.DateTime toUtcTimeZone() {
- DateTime jodaTime = this.instance.withZone(DateTimeZone.forID("UTC"));
+ DateTimeZone utc = DateTimeZone.forID("UTC");
+ if (this.instance.getZone().equals(utc)) return this;
+ DateTime jodaTime = this.instance.withZone(utc);
return new JodaDateTime(jodaTime);
}
Deleted:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/InvalidQueryException.java
===================================================================
---
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/InvalidQueryException.java 2009-10-03
04:07:41 UTC (rev 1285)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/InvalidQueryException.java 2009-10-03
04:09:29 UTC (rev 1286)
@@ -1,65 +0,0 @@
-/*
- * JBoss DNA (
http://www.jboss.org/dna)
- * See the COPYRIGHT.txt file distributed with this work for information
- * regarding copyright ownership. Some portions may be licensed
- * to Red Hat, Inc. under one or more contributor license agreements.
- * See the AUTHORS.txt file in the distribution for a full listing of
- * individual contributors.
- *
- * JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA
- * is licensed to you under the terms of the GNU Lesser General Public License as
- * published by the Free Software Foundation; either version 2.1 of
- * the License, or (at your option) any later version.
- *
- * JBoss DNA is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this software; if not, write to the Free
- * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
- * 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
- */
-package org.jboss.dna.graph.query;
-
-/**
- *
- */
-public class InvalidQueryException extends RuntimeException {
-
- private static final long serialVersionUID = 1L;
-
- /**
- *
- */
- public InvalidQueryException() {
- }
-
- /**
- * @param message
- */
- public InvalidQueryException( String message ) {
- super(message);
-
- }
-
- /**
- * @param cause
- */
- public InvalidQueryException( Throwable cause ) {
- super(cause);
-
- }
-
- /**
- * @param message
- * @param cause
- */
- public InvalidQueryException( String message,
- Throwable cause ) {
- super(message, cause);
-
- }
-
-}
Modified: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/QueryBuilder.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/QueryBuilder.java 2009-10-03
04:07:41 UTC (rev 1285)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/QueryBuilder.java 2009-10-03
04:09:29 UTC (rev 1286)
@@ -23,20 +23,28 @@
*/
package org.jboss.dna.graph.query;
+import java.math.BigDecimal;
+import java.net.URI;
import java.util.LinkedList;
import java.util.List;
+import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean;
import net.jcip.annotations.NotThreadSafe;
import org.jboss.dna.common.util.CheckArg;
import org.jboss.dna.graph.ExecutionContext;
import org.jboss.dna.graph.GraphI18n;
+import org.jboss.dna.graph.property.Binary;
+import org.jboss.dna.graph.property.DateTime;
import org.jboss.dna.graph.property.Name;
import org.jboss.dna.graph.property.Path;
+import org.jboss.dna.graph.property.PropertyType;
+import org.jboss.dna.graph.property.ValueFactories;
import org.jboss.dna.graph.property.ValueFormatException;
import org.jboss.dna.graph.query.model.AllNodes;
import org.jboss.dna.graph.query.model.And;
import org.jboss.dna.graph.query.model.BindVariableName;
import org.jboss.dna.graph.query.model.ChildNode;
+import org.jboss.dna.graph.query.model.ChildNodeJoinCondition;
import org.jboss.dna.graph.query.model.Column;
import org.jboss.dna.graph.query.model.Comparison;
import org.jboss.dna.graph.query.model.Constraint;
@@ -54,8 +62,10 @@
import org.jboss.dna.graph.query.model.Literal;
import org.jboss.dna.graph.query.model.LowerCase;
import org.jboss.dna.graph.query.model.NamedSelector;
+import org.jboss.dna.graph.query.model.NodeDepth;
import org.jboss.dna.graph.query.model.NodeLocalName;
import org.jboss.dna.graph.query.model.NodeName;
+import org.jboss.dna.graph.query.model.NodePath;
import org.jboss.dna.graph.query.model.Not;
import org.jboss.dna.graph.query.model.Operator;
import org.jboss.dna.graph.query.model.Or;
@@ -302,13 +312,13 @@
*
* @param name the name; may not be null
* @return the name; never null
- * @throws InvalidQueryException if the supplied name is not a valid {@link Name}
object
+ * @throws IllegalArgumentException if the supplied name is not a valid {@link Name}
object
*/
protected Name name( String name ) {
try {
return context.getValueFactories().getNameFactory().create(name.trim());
} catch (ValueFormatException e) {
- throw new InvalidQueryException(GraphI18n.expectingValidName.text(name));
+ throw new IllegalArgumentException(GraphI18n.expectingValidName.text(name));
}
}
@@ -318,13 +328,13 @@
*
* @param path the path; may not be null
* @return the path; never null
- * @throws InvalidQueryException if the supplied string is not a valid {@link Path}
object
+ * @throws IllegalArgumentException if the supplied string is not a valid {@link
Path} object
*/
protected Path path( String path ) {
try {
return context.getValueFactories().getPathFactory().create(path.trim());
} catch (ValueFormatException e) {
- throw new InvalidQueryException(GraphI18n.expectingValidPath.text(path));
+ throw new IllegalArgumentException(GraphI18n.expectingValidPath.text(path));
}
}
@@ -335,7 +345,7 @@
*
* @param nameExpression the expression specifying the columm name and (optionally)
the table's name or alias; may not be null
* @return the column; never null
- * @throws InvalidQueryException if the table's name/alias is not specified, but
the query has more than one named source
+ * @throws IllegalArgumentException if the table's name/alias is not specified,
but the query has more than one named source
*/
protected Column column( String nameExpression ) {
String[] parts = nameExpression.split("(?<!\\\\)\\."); // a . not
preceded by an escaping slash
@@ -356,7 +366,7 @@
propertyName = name(parts[0]);
columnName = parts[0];
} else {
- throw new
InvalidQueryException(GraphI18n.columnMustBeScoped.text(parts[0]));
+ throw new
IllegalArgumentException(GraphI18n.columnMustBeScoped.text(parts[0]));
}
}
return new Column(name, propertyName, columnName);
@@ -373,16 +383,15 @@
}
/**
- * Select the columns with the supplied names. Each column name has the form
"<code>[tableName.]columnName</code>", where "
- * <code>tableName</code>" must be a valid table name or alias. If
the table name/alias is not specified, then there is
- * expected to be a single FROM clause with a single named selector.
+ * Add to the select clause the columns with the supplied names. Each column name has
the form "
+ * <code>[tableName.]columnName</code>", where "
<code>tableName</code>" must be a valid table name or alias. If the
table
+ * name/alias is not specified, then there is expected to be a single FROM clause
with a single named selector.
*
* @param columnNames the column expressions; may not be null
* @return this builder object, for convenience in method chaining
- * @throws InvalidQueryException if the table's name/alias is not specified, but
the query has more than one named source
+ * @throws IllegalArgumentException if the table's name/alias is not specified,
but the query has more than one named source
*/
public QueryBuilder select( String... columnNames ) {
- columns.clear();
for (String expression : columnNames) {
columns.add(column(expression));
}
@@ -406,7 +415,7 @@
*
* @param columnNames the column expressions; may not be null
* @return this builder object, for convenience in method chaining
- * @throws InvalidQueryException if the table's name/alias is not specified, but
the query has more than one named source
+ * @throws IllegalArgumentException if the table's name/alias is not specified,
but the query has more than one named source
*/
public QueryBuilder selectDistinct( String... columnNames ) {
distinct = true;
@@ -549,6 +558,73 @@
}
/**
+ * Perform an inner join between the already defined source with the
"__ALLNODES__" table using the supplied alias.
+ *
+ * @param alias the alias for the "__ALL_NODES" table; may not be null
+ * @return the component that must be used to complete the join specification; never
null
+ */
+ public JoinClause joinAllNodesAs( String alias ) {
+ return innerJoinAllNodesAs(alias);
+ }
+
+ /**
+ * Perform an inner join between the already defined source with the
"__ALL_NODES" table using the supplied alias.
+ *
+ * @param alias the alias for the "__ALL_NODES" table; may not be null
+ * @return the component that must be used to complete the join specification; never
null
+ */
+ public JoinClause innerJoinAllNodesAs( String alias ) {
+ // Expect there to be a source already ...
+ return new JoinClause(namedSelector(AllNodes.ALL_NODES_NAME + " AS " +
alias), JoinType.INNER);
+ }
+
+ /**
+ * Perform a cross join between the already defined source with the
"__ALL_NODES" table using the supplied alias. Cross joins
+ * have a higher precedent than other join types, so if this is called after another
join was defined, the resulting cross
+ * join will be between the previous join's right-hand side and the supplied
table.
+ *
+ * @param alias the alias for the "__ALL_NODES" table; may not be null
+ * @return the component that must be used to complete the join specification; never
null
+ */
+ public JoinClause crossJoinAllNodesAs( String alias ) {
+ // Expect there to be a source already ...
+ return new JoinClause(namedSelector(AllNodes.ALL_NODES_NAME + " AS " +
alias), JoinType.CROSS);
+ }
+
+ /**
+ * Perform a full outer join between the already defined source with the
"__ALL_NODES" table using the supplied alias.
+ *
+ * @param alias the alias for the "__ALL_NODES" table; may not be null
+ * @return the component that must be used to complete the join specification; never
null
+ */
+ public JoinClause fullOuterJoinAllNodesAs( String alias ) {
+ // Expect there to be a source already ...
+ return new JoinClause(namedSelector(AllNodes.ALL_NODES_NAME + " AS " +
alias), JoinType.FULL_OUTER);
+ }
+
+ /**
+ * Perform a left outer join between the already defined source with the
"__ALL_NODES" table using the supplied alias.
+ *
+ * @param alias the alias for the "__ALL_NODES" table; may not be null
+ * @return the component that must be used to complete the join specification; never
null
+ */
+ public JoinClause leftOuterJoinAllNodesAs( String alias ) {
+ // Expect there to be a source already ...
+ return new JoinClause(namedSelector(AllNodes.ALL_NODES_NAME + " AS " +
alias), JoinType.LEFT_OUTER);
+ }
+
+ /**
+ * Perform a right outer join between the already defined source with the
"__ALL_NODES" table using the supplied alias.
+ *
+ * @param alias the alias for the "__ALL_NODES" table; may not be null
+ * @return the component that must be used to complete the join specification; never
null
+ */
+ public JoinClause rightOuterJoinAllNodesAs( String alias ) {
+ // Expect there to be a source already ...
+ return new JoinClause(namedSelector(AllNodes.ALL_NODES_NAME + " AS " +
alias), JoinType.RIGHT_OUTER);
+ }
+
+ /**
* Specify the maximum number of rows that are to be returned in the results. By
default there is no limit.
*
* @param rowLimit the maximum number of rows
@@ -703,7 +779,7 @@
*
* @param tableName the table name
* @return the selector name matching the supplied table name; never null
- * @throws InvalidQueryException if the table name could not be resolved
+ * @throws IllegalArgumentException if the table name could not be resolved
*/
protected SelectorName nameOf( String tableName ) {
final SelectorName name = new SelectorName(tableName);
@@ -723,7 +799,7 @@
}
});
if (notFound.get()) {
- throw new InvalidQueryException("Expected \"" + tableName
+ "\" to be a valid table name or alias");
+ throw new IllegalArgumentException("Expected \"" +
tableName + "\" to be a valid table name or alias");
}
return name;
}
@@ -734,12 +810,13 @@
*
* @param columnEqualExpression the equality expression between the two tables;
may not be null
* @return the query builder instance, for method chaining purposes
+ * @throws IllegalArgumentException if the supplied expression is not an equality
expression
*/
public QueryBuilder on( String columnEqualExpression ) {
String[] parts = columnEqualExpression.split("=");
if (parts.length != 2) {
- throw new InvalidQueryException("Expected equality expression for
columns, but found \"" + columnEqualExpression
- + "\"");
+ throw new IllegalArgumentException("Expected equality expression for
columns, but found \""
+ + columnEqualExpression +
"\"");
}
return createJoin(new EquiJoinCondition(column(parts[0]),
column(parts[1])));
}
@@ -780,7 +857,7 @@
*/
public QueryBuilder onChildNode( String parentTable,
String childTable ) {
- return createJoin(new DescendantNodeJoinCondition(nameOf(parentTable),
nameOf(childTable)));
+ return createJoin(new ChildNodeJoinCondition(nameOf(parentTable),
nameOf(childTable)));
}
protected QueryBuilder createJoin( JoinCondition condition ) {
@@ -836,16 +913,34 @@
public ComparisonBuilder fullTextSearchScore( String table );
/**
- * Constrains the nodes in the the supplied table such that they must have a
matching node local name.
+ * Constrains the nodes in the the supplied table based upon criteria on the
node's depth.
*
* @param table the name of the table; may not be null and must refer to a valid
name or alias of a table appearing in the
* FROM clause
* @return the interface for completing the value portion of the criteria
specification; never null
*/
+ public ComparisonBuilder depth( String table );
+
+ /**
+ * Constrains the nodes in the the supplied table based upon criteria on the
node's path.
+ *
+ * @param table the name of the table; may not be null and must refer to a valid
name or alias of a table appearing in the
+ * FROM clause
+ * @return the interface for completing the value portion of the criteria
specification; never null
+ */
+ public ComparisonBuilder path( String table );
+
+ /**
+ * Constrains the nodes in the the supplied table based upon criteria on the
node's local name.
+ *
+ * @param table the name of the table; may not be null and must refer to a valid
name or alias of a table appearing in the
+ * FROM clause
+ * @return the interface for completing the value portion of the criteria
specification; never null
+ */
public ComparisonBuilder nodeLocalName( String table );
/**
- * Constrains the nodes in the the supplied table such that they must have a
matching node name.
+ * Constrains the nodes in the the supplied table based upon criteria on the
node's name.
*
* @param table the name of the table; may not be null and must refer to a valid
name or alias of a table appearing in the
* FROM clause
@@ -987,8 +1082,7 @@
*/
public ConstraintBuilder isSameNode( String table,
String asNodeAtPath ) {
- if (constraint != null) throw new InvalidQueryException("Existing
constraint; call in proper order");
- return setConstraint(new SameNode(selector(table), path(asNodeAtPath)));
+ return setConstraint(new SameNode(selector(table),
QueryBuilder.this.path(asNodeAtPath)));
}
/**
@@ -1001,8 +1095,7 @@
*/
public ConstraintBuilder isChild( String childTable,
String parentPath ) {
- if (constraint != null) throw new InvalidQueryException("Existing
constraint; call in proper order");
- return setConstraint(new ChildNode(selector(childTable), path(parentPath)));
+ return setConstraint(new ChildNode(selector(childTable),
QueryBuilder.this.path(parentPath)));
}
/**
@@ -1015,12 +1108,11 @@
*/
public ConstraintBuilder isBelowPath( String descendantTable,
String ancestorPath ) {
- if (constraint != null) throw new InvalidQueryException("Existing
constraint; call in proper order");
- return setConstraint(new DescendantNode(selector(descendantTable),
path(ancestorPath)));
+ return setConstraint(new DescendantNode(selector(descendantTable),
QueryBuilder.this.path(ancestorPath)));
}
/**
- * Define a constraint clause that the node within the named table has at least
one value for the named property. path.
+ * Define a constraint clause that the node within the named table has at least
one value for the named property.
*
* @param table the name of the table; may not be null and must refer to a valid
name or alias of a table appearing in the
* FROM clause
@@ -1029,7 +1121,6 @@
*/
public ConstraintBuilder hasProperty( String table,
String propertyName ) {
- if (constraint != null) throw new InvalidQueryException("Existing
constraint; call in proper order");
return setConstraint(new PropertyExistence(selector(table),
name(propertyName)));
}
@@ -1044,7 +1135,6 @@
*/
public ConstraintBuilder search( String table,
String searchExpression ) {
- if (constraint != null) throw new InvalidQueryException("Existing
constraint; call in proper order");
return setConstraint(new FullTextSearch(selector(table), searchExpression));
}
@@ -1061,7 +1151,6 @@
public ConstraintBuilder search( String table,
String propertyName,
String searchExpression ) {
- if (constraint != null) throw new InvalidQueryException("Existing
constraint; call in proper order");
return setConstraint(new FullTextSearch(selector(table), name(propertyName),
searchExpression));
}
@@ -1097,6 +1186,24 @@
/**
* {@inheritDoc}
*
+ * @see
org.jboss.dna.graph.query.QueryBuilder.DynamicOperandBuilder#depth(java.lang.String)
+ */
+ public ComparisonBuilder depth( String table ) {
+ return new ComparisonBuilder(this, new NodeDepth(selector(table)));
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.QueryBuilder.DynamicOperandBuilder#path(java.lang.String)
+ */
+ public ComparisonBuilder path( String table ) {
+ return new ComparisonBuilder(this, new NodePath(selector(table)));
+ }
+
+ /**
+ * {@inheritDoc}
+ *
* @see
org.jboss.dna.graph.query.QueryBuilder.DynamicOperandBuilder#nodeLocalName(String)
*/
public ComparisonBuilder nodeLocalName( String table ) {
@@ -1179,6 +1286,400 @@
}
}
+ public class CastAs {
+ private final RightHandSide rhs;
+ private final Object value;
+
+ protected CastAs( RightHandSide rhs,
+ Object value ) {
+ this.rhs = rhs;
+ this.value = value;
+ }
+
+ private ValueFactories factories() {
+ return QueryBuilder.this.context.getValueFactories();
+ }
+
+ /**
+ * Define the right-hand side literal value cast as the specified type.
+ *
+ * @param type the property type; may not be null
+ * @return the constraint builder; never null
+ */
+ public ConstraintBuilder as( PropertyType type ) {
+ return rhs.comparisonBuilder.is(rhs.operator,
factories().getValueFactory(type).create(value));
+ }
+
+ /**
+ * Define the right-hand side literal value cast as a {@link
PropertyType#STRING}.
+ *
+ * @return the constraint builder; never null
+ */
+ public ConstraintBuilder asString() {
+ return as(PropertyType.STRING);
+ }
+
+ /**
+ * Define the right-hand side literal value cast as a {@link
PropertyType#BOOLEAN}.
+ *
+ * @return the constraint builder; never null
+ */
+ public ConstraintBuilder asBoolean() {
+ return as(PropertyType.BOOLEAN);
+ }
+
+ /**
+ * Define the right-hand side literal value cast as a {@link PropertyType#LONG}.
+ *
+ * @return the constraint builder; never null
+ */
+ public ConstraintBuilder asLong() {
+ return as(PropertyType.LONG);
+ }
+
+ /**
+ * Define the right-hand side literal value cast as a {@link
PropertyType#DOUBLE}.
+ *
+ * @return the constraint builder; never null
+ */
+ public ConstraintBuilder asDouble() {
+ return as(PropertyType.DOUBLE);
+ }
+
+ /**
+ * Define the right-hand side literal value cast as a {@link
PropertyType#DECIMAL}.
+ *
+ * @return the constraint builder; never null
+ */
+ public ConstraintBuilder asDecimal() {
+ return as(PropertyType.DECIMAL);
+ }
+
+ /**
+ * Define the right-hand side literal value cast as a {@link PropertyType#DATE}.
+ *
+ * @return the constraint builder; never null
+ */
+ public ConstraintBuilder asDate() {
+ return as(PropertyType.DATE);
+ }
+
+ /**
+ * Define the right-hand side literal value cast as a {@link PropertyType#NAME}.
+ *
+ * @return the constraint builder; never null
+ */
+ public ConstraintBuilder asName() {
+ return as(PropertyType.NAME);
+ }
+
+ /**
+ * Define the right-hand side literal value cast as a {@link PropertyType#PATH}.
+ *
+ * @return the constraint builder; never null
+ */
+ public ConstraintBuilder asPath() {
+ return as(PropertyType.PATH);
+ }
+
+ /**
+ * Define the right-hand side literal value cast as a {@link
PropertyType#BINARY}.
+ *
+ * @return the constraint builder; never null
+ */
+ public ConstraintBuilder asBinary() {
+ return as(PropertyType.BINARY);
+ }
+
+ /**
+ * Define the right-hand side literal value cast as a {@link
PropertyType#REFERENCE}.
+ *
+ * @return the constraint builder; never null
+ */
+ public ConstraintBuilder asReference() {
+ return as(PropertyType.REFERENCE);
+ }
+
+ /**
+ * Define the right-hand side literal value cast as a {@link PropertyType#URI}.
+ *
+ * @return the constraint builder; never null
+ */
+ public ConstraintBuilder asUri() {
+ return as(PropertyType.URI);
+ }
+
+ /**
+ * Define the right-hand side literal value cast as a {@link PropertyType#UUID}.
+ *
+ * @return the constraint builder; never null
+ */
+ public ConstraintBuilder asUuid() {
+ return as(PropertyType.UUID);
+ }
+ }
+
+ public class RightHandSide {
+ protected final Operator operator;
+ protected final ComparisonBuilder comparisonBuilder;
+
+ protected RightHandSide( ComparisonBuilder comparisonBuilder,
+ Operator operator ) {
+ this.operator = operator;
+ this.comparisonBuilder = comparisonBuilder;
+ }
+
+ /**
+ * Define the right-hand side of a comparison.
+ *
+ * @param literal the literal value;
+ * @return the constraint builder; never null
+ */
+ public ConstraintBuilder literal( String literal ) {
+ return comparisonBuilder.is(operator, literal);
+ }
+
+ /**
+ * Define the right-hand side of a comparison.
+ *
+ * @param literal the literal value;
+ * @return the constraint builder; never null
+ */
+ public ConstraintBuilder literal( int literal ) {
+ return comparisonBuilder.is(operator, literal);
+ }
+
+ /**
+ * Define the right-hand side of a comparison.
+ *
+ * @param literal the literal value;
+ * @return the constraint builder; never null
+ */
+ public ConstraintBuilder literal( long literal ) {
+ return comparisonBuilder.is(operator, literal);
+ }
+
+ /**
+ * Define the right-hand side of a comparison.
+ *
+ * @param literal the literal value;
+ * @return the constraint builder; never null
+ */
+ public ConstraintBuilder literal( float literal ) {
+ return comparisonBuilder.is(operator, literal);
+ }
+
+ /**
+ * Define the right-hand side of a comparison.
+ *
+ * @param literal the literal value;
+ * @return the constraint builder; never null
+ */
+ public ConstraintBuilder literal( double literal ) {
+ return comparisonBuilder.is(operator, literal);
+ }
+
+ /**
+ * Define the right-hand side of a comparison.
+ *
+ * @param literal the literal value;
+ * @return the constraint builder; never null
+ */
+ public ConstraintBuilder literal( DateTime literal ) {
+ return comparisonBuilder.is(operator, literal.toUtcTimeZone());
+ }
+
+ /**
+ * Define the right-hand side of a comparison.
+ *
+ * @param literal the literal value;
+ * @return the constraint builder; never null
+ */
+ public ConstraintBuilder literal( Path literal ) {
+ return comparisonBuilder.is(operator, literal);
+ }
+
+ /**
+ * Define the right-hand side of a comparison.
+ *
+ * @param literal the literal value;
+ * @return the constraint builder; never null
+ */
+ public ConstraintBuilder literal( Name literal ) {
+ return comparisonBuilder.is(operator, literal);
+ }
+
+ /**
+ * Define the right-hand side of a comparison.
+ *
+ * @param literal the literal value;
+ * @return the constraint builder; never null
+ */
+ public ConstraintBuilder literal( URI literal ) {
+ return comparisonBuilder.is(operator, literal);
+ }
+
+ /**
+ * Define the right-hand side of a comparison.
+ *
+ * @param literal the literal value;
+ * @return the constraint builder; never null
+ */
+ public ConstraintBuilder literal( UUID literal ) {
+ return comparisonBuilder.is(operator, literal);
+ }
+
+ /**
+ * Define the right-hand side of a comparison.
+ *
+ * @param literal the literal value;
+ * @return the constraint builder; never null
+ */
+ public ConstraintBuilder literal( Binary literal ) {
+ return comparisonBuilder.is(operator, literal);
+ }
+
+ /**
+ * Define the right-hand side of a comparison.
+ *
+ * @param literal the literal value;
+ * @return the constraint builder; never null
+ */
+ public ConstraintBuilder literal( BigDecimal literal ) {
+ return comparisonBuilder.is(operator, literal);
+ }
+
+ /**
+ * Define the right-hand side of a comparison.
+ *
+ * @param literal the literal value;
+ * @return the constraint builder; never null
+ */
+ public ConstraintBuilder literal( boolean literal ) {
+ return comparisonBuilder.is(operator, literal);
+ }
+
+ /**
+ * Define the right-hand side of a comparison.
+ *
+ * @param variableName the name of the variable
+ * @return the constraint builder; never null
+ */
+ public ConstraintBuilder variable( String variableName ) {
+ return comparisonBuilder.is(operator, variableName);
+ }
+
+ /**
+ * Define the right-hand side of a comparison.
+ *
+ * @param literal the literal value that is to be cast
+ * @return the constraint builder; never null
+ */
+ public CastAs cast( int literal ) {
+ return new CastAs(this, literal);
+ }
+
+ /**
+ * Define the right-hand side of a comparison.
+ *
+ * @param literal the literal value that is to be cast
+ * @return the constraint builder; never null
+ */
+ public CastAs cast( String literal ) {
+ return new CastAs(this, literal);
+ }
+
+ /**
+ * Define the right-hand side of a comparison.
+ *
+ * @param literal the literal value that is to be cast
+ * @return the constraint builder; never null
+ */
+ public CastAs cast( boolean literal ) {
+ return new CastAs(this, literal);
+ }
+
+ /**
+ * Define the right-hand side of a comparison.
+ *
+ * @param literal the literal value that is to be cast
+ * @return the constraint builder; never null
+ */
+ public CastAs cast( long literal ) {
+ return new CastAs(this, literal);
+ }
+
+ /**
+ * Define the right-hand side of a comparison.
+ *
+ * @param literal the literal value that is to be cast
+ * @return the constraint builder; never null
+ */
+ public CastAs cast( double literal ) {
+ return new CastAs(this, literal);
+ }
+
+ /**
+ * Define the right-hand side of a comparison.
+ *
+ * @param literal the literal value that is to be cast
+ * @return the constraint builder; never null
+ */
+ public CastAs cast( BigDecimal literal ) {
+ return new CastAs(this, literal);
+ }
+
+ /**
+ * Define the right-hand side of a comparison.
+ *
+ * @param literal the literal value that is to be cast
+ * @return the constraint builder; never null
+ */
+ public CastAs cast( DateTime literal ) {
+ return new CastAs(this, literal.toUtcTimeZone());
+ }
+
+ /**
+ * Define the right-hand side of a comparison.
+ *
+ * @param literal the literal value that is to be cast
+ * @return the constraint builder; never null
+ */
+ public CastAs cast( Name literal ) {
+ return new CastAs(this, literal);
+ }
+
+ /**
+ * Define the right-hand side of a comparison.
+ *
+ * @param literal the literal value that is to be cast
+ * @return the constraint builder; never null
+ */
+ public CastAs cast( Path literal ) {
+ return new CastAs(this, literal);
+ }
+
+ /**
+ * Define the right-hand side of a comparison.
+ *
+ * @param literal the literal value that is to be cast
+ * @return the constraint builder; never null
+ */
+ public CastAs cast( UUID literal ) {
+ return new CastAs(this, literal);
+ }
+
+ /**
+ * Define the right-hand side of a comparison.
+ *
+ * @param literal the literal value that is to be cast
+ * @return the constraint builder; never null
+ */
+ public CastAs cast( URI literal ) {
+ return new CastAs(this, literal);
+ }
+ }
+
/**
* An interface used to set the right-hand side of a constraint.
*/
@@ -1192,14 +1693,112 @@
this.constraintBuilder = constraintBuilder;
}
- protected ConstraintBuilder isVariable( Operator operator,
- String variableName ) {
- assert operator != null;
+ /**
+ * Define the operator that will be used in the comparison, returning an
interface that can be used to define the
+ * right-hand-side of the comparison.
+ *
+ * @param operator the operator; may not be null
+ * @return the interface used to define the right-hand-side of the comparison
+ */
+ public RightHandSide is( Operator operator ) {
+ CheckArg.isNotNull(operator, "operator");
+ return new RightHandSide(this, operator);
+ }
+
+ /**
+ * Use the 'equal to' operator in the comparison, returning an interface
that can be used to define the right-hand-side of
+ * the comparison.
+ *
+ * @return the interface used to define the right-hand-side of the comparison
+ */
+ public RightHandSide isEqualTo() {
+ return is(Operator.EQUAL_TO);
+ }
+
+ /**
+ * Use the 'equal to' operator in the comparison, returning an interface
that can be used to define the right-hand-side of
+ * the comparison.
+ *
+ * @return the interface used to define the right-hand-side of the comparison
+ */
+ public RightHandSide isNotEqualTo() {
+ return is(Operator.NOT_EQUAL_TO);
+ }
+
+ /**
+ * Use the 'equal to' operator in the comparison, returning an interface
that can be used to define the right-hand-side of
+ * the comparison.
+ *
+ * @return the interface used to define the right-hand-side of the comparison
+ */
+ public RightHandSide isGreaterThan() {
+ return is(Operator.GREATER_THAN);
+ }
+
+ /**
+ * Use the 'equal to' operator in the comparison, returning an interface
that can be used to define the right-hand-side of
+ * the comparison.
+ *
+ * @return the interface used to define the right-hand-side of the comparison
+ */
+ public RightHandSide isGreaterThanOrEqualTo() {
+ return is(Operator.GREATER_THAN_OR_EQUAL_TO);
+ }
+
+ /**
+ * Use the 'equal to' operator in the comparison, returning an interface
that can be used to define the right-hand-side of
+ * the comparison.
+ *
+ * @return the interface used to define the right-hand-side of the comparison
+ */
+ public RightHandSide isLessThan() {
+ return is(Operator.LESS_THAN);
+ }
+
+ /**
+ * Use the 'equal to' operator in the comparison, returning an interface
that can be used to define the right-hand-side of
+ * the comparison.
+ *
+ * @return the interface used to define the right-hand-side of the comparison
+ */
+ public RightHandSide isLessThanOrEqualTo() {
+ return is(Operator.LESS_THAN_OR_EQUAL_TO);
+ }
+
+ /**
+ * Use the 'equal to' operator in the comparison, returning an interface
that can be used to define the right-hand-side of
+ * the comparison.
+ *
+ * @return the interface used to define the right-hand-side of the comparison
+ */
+ public RightHandSide isLike() {
+ return is(Operator.LIKE);
+ }
+
+ /**
+ * Define the right-hand-side of the constraint using the supplied operator.
+ *
+ * @param operator the operator; may not be null
+ * @param variableName the name of the variable
+ * @return the builder used to create the constraint clause, ready to be used to
create other constraints clauses or
+ * complete already-started clauses; never null
+ */
+ public ConstraintBuilder isVariable( Operator operator,
+ String variableName ) {
+ CheckArg.isNotNull(operator, "operator");
return this.constraintBuilder.setConstraint(new Comparison(left, operator,
new BindVariableName(variableName)));
}
- protected ConstraintBuilder is( Operator operator,
- Object literal ) {
+ /**
+ * Define the right-hand-side of the constraint using the supplied operator.
+ *
+ * @param operator the operator; may not be null
+ * @param literal the literal value
+ * @return the builder used to create the constraint clause, ready to be used to
create other constraints clauses or
+ * complete already-started clauses; never null
+ */
+ public ConstraintBuilder is( Operator operator,
+ Object literal ) {
assert operator != null;
return this.constraintBuilder.setConstraint(new Comparison(left, operator,
new Literal(literal)));
}
Modified: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/QueryEngine.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/QueryEngine.java 2009-10-03
04:07:41 UTC (rev 1285)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/QueryEngine.java 2009-10-03
04:09:29 UTC (rev 1286)
@@ -23,11 +23,18 @@
*/
package org.jboss.dna.graph.query;
+import java.util.Collections;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicBoolean;
import net.jcip.annotations.ThreadSafe;
+import org.jboss.dna.common.text.ParsingException;
import org.jboss.dna.common.util.CheckArg;
import org.jboss.dna.graph.ExecutionContext;
+import org.jboss.dna.graph.GraphI18n;
import org.jboss.dna.graph.query.QueryResults.Statistics;
import org.jboss.dna.graph.query.model.Column;
import org.jboss.dna.graph.query.model.Constraint;
@@ -37,6 +44,8 @@
import org.jboss.dna.graph.query.model.Visitors;
import org.jboss.dna.graph.query.optimize.Optimizer;
import org.jboss.dna.graph.query.optimize.RuleBasedOptimizer;
+import org.jboss.dna.graph.query.parse.InvalidQueryException;
+import org.jboss.dna.graph.query.parse.QueryParser;
import org.jboss.dna.graph.query.plan.CanonicalPlanner;
import org.jboss.dna.graph.query.plan.PlanHints;
import org.jboss.dna.graph.query.plan.PlanNode;
@@ -58,11 +67,13 @@
private final Optimizer optimizer;
private final Processor processor;
private final Schemata schemata;
+ private final ConcurrentMap<String, QueryParser> parsers = new
ConcurrentHashMap<String, QueryParser>();
public QueryEngine( Planner planner,
Optimizer optimizer,
Processor processor,
- Schemata schemata ) {
+ Schemata schemata,
+ QueryParser... parsers ) {
CheckArg.isNotNull(processor, "processor");
this.planner = planner != null ? planner : new CanonicalPlanner();
this.optimizer = optimizer != null ? optimizer : new RuleBasedOptimizer();
@@ -74,14 +85,78 @@
return null;
}
};
+ for (QueryParser parser : parsers) {
+ if (parser != null) addLanguage(parser);
+ }
}
/**
+ * Add a language to this engine by supplying its parser.
+ *
+ * @param languageParser the query parser for the language
+ * @throws IllegalArgumentException if the language parser is null
+ */
+ public void addLanguage( QueryParser languageParser ) {
+ CheckArg.isNotNull(languageParser, "languageParser");
+ this.parsers.put(languageParser.getLanguage().toLowerCase(), languageParser);
+ }
+
+ /**
+ * Remove from this engine the language with the given name.
+ *
+ * @param language the name of the language, which is to match the {@link
QueryParser#getLanguage() language} of the parser
+ * @return the parser for the language, or null if the engine had no support for the
named language
+ * @throws IllegalArgumentException if the language is null
+ */
+ public QueryParser removeLanguage( String language ) {
+ CheckArg.isNotNull(language, "language");
+ return this.parsers.remove(language.toLowerCase());
+ }
+
+ /**
+ * Get the set of languages that this engine is capable of parsing.
+ *
+ * @return the unmodifiable copy of the set of languages; never null but possibly
empty
+ */
+ public Set<String> getLanguages() {
+ Set<String> result = new HashSet<String>();
+ for (QueryParser parser : parsers.values()) {
+ result.add(parser.getLanguage());
+ }
+ return Collections.unmodifiableSet(result);
+ }
+
+ /**
* Execute the supplied query by planning, optimizing, and then processing it.
*
* @param context the context in which the query should be executed
+ * @param language the language in which the query is expressed; must be one of the
supported {@link #getLanguages()
+ * languages}
* @param query the query that is to be executed
* @return the query results; never null
+ * @throws IllegalArgumentException if the language, context or query references are
null, or if the language is not know
+ * @throws ParsingException if there is an error parsing the supplied query
+ * @throws InvalidQueryException if the supplied query can be parsed but is invalid
+ */
+ public QueryResults execute( ExecutionContext context,
+ String language,
+ String query ) {
+ CheckArg.isNotNull(language, "language");
+ CheckArg.isNotNull(context, "context");
+ CheckArg.isNotNull(query, "query");
+ QueryParser parser = parsers.get(language.toLowerCase());
+ if (parser == null) {
+ throw new
IllegalArgumentException(GraphI18n.unknownQueryLanguage.text(language));
+ }
+ return execute(context, parser.parseQuery(query, context));
+ }
+
+ /**
+ * Execute the supplied query by planning, optimizing, and then processing it.
+ *
+ * @param context the context in which the query should be executed
+ * @param query the query that is to be executed
+ * @return the query results; never null
* @throws IllegalArgumentException if the context or query references are null
*/
public QueryResults execute( ExecutionContext context,
@@ -101,6 +176,8 @@
public QueryResults execute( ExecutionContext context,
QueryCommand query,
PlanHints hints ) {
+ CheckArg.isNotNull(context, "context");
+ CheckArg.isNotNull(query, "query");
QueryContext queryContext = new QueryContext(context, hints, schemata);
// Create the canonical plan ...
Modified: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/AllNodes.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/AllNodes.java 2009-10-03
04:07:41 UTC (rev 1285)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/AllNodes.java 2009-10-03
04:09:29 UTC (rev 1286)
@@ -65,9 +65,10 @@
@Override
public boolean equals( Object obj ) {
if (obj == this) return true;
- if (obj instanceof AllNodes) {
- AllNodes that = (AllNodes)obj;
- return ObjectUtil.isEqualWithNulls(this.getAlias(), that.getAlias());
+ if (obj instanceof Selector) {
+ Selector that = (Selector)obj;
+ return ObjectUtil.isEqualWithNulls(this.getName(), that.getName())
+ && ObjectUtil.isEqualWithNulls(this.getAlias(),
that.getAlias());
}
return false;
}
Modified: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Literal.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Literal.java 2009-10-03
04:07:41 UTC (rev 1285)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Literal.java 2009-10-03
04:09:29 UTC (rev 1286)
@@ -66,7 +66,7 @@
if (obj == this) return true;
if (obj instanceof Literal) {
Literal that = (Literal)obj;
- return this.value.equals(that.value);
+ return this.value.equals(that.value) ||
this.value.toString().equals(that.value.toString());
}
return false;
}
Modified:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/NamedSelector.java
===================================================================
---
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/NamedSelector.java 2009-10-03
04:07:41 UTC (rev 1285)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/NamedSelector.java 2009-10-03
04:09:29 UTC (rev 1286)
@@ -72,8 +72,8 @@
@Override
public boolean equals( Object obj ) {
if (obj == this) return true;
- if (obj instanceof NamedSelector) {
- NamedSelector that = (NamedSelector)obj;
+ if (obj instanceof Selector) {
+ Selector that = (Selector)obj;
if (!this.getName().equals(that.getName())) return false;
if (!ObjectUtil.isEqualWithNulls(this.getAlias(), that.getAlias())) return
false;
return true;
Copied: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/NodeDepth.java (from
rev 1285, trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Literal.java)
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/NodeDepth.java
(rev 0)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/NodeDepth.java 2009-10-03
04:09:29 UTC (rev 1286)
@@ -0,0 +1,90 @@
+/*
+ * JBoss DNA (
http://www.jboss.org/dna)
+ * See the COPYRIGHT.txt file distributed with this work for information
+ * regarding copyright ownership. Some portions may be licensed
+ * to Red Hat, Inc. under one or more contributor license agreements.
+ * See the AUTHORS.txt file in the distribution for a full listing of
+ * individual contributors.
+ *
+ * JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA
+ * is licensed to you under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * JBoss DNA is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
+ */
+package org.jboss.dna.graph.query.model;
+
+import net.jcip.annotations.Immutable;
+import org.jboss.dna.common.util.CheckArg;
+
+/**
+ * A dynamic operand that evaluates to the depth of a node given by a selector, used in a
{@link Comparison} constraint.
+ */
+@Immutable
+public class NodeDepth extends DynamicOperand {
+ private final SelectorName selectorName;
+
+ /**
+ * Create a dynamic operand that evaluates to the depth of the node identified by the
selector.
+ *
+ * @param selectorName the name of the selector
+ * @throws IllegalArgumentException if the selector name or property name are null
+ */
+ public NodeDepth( SelectorName selectorName ) {
+ CheckArg.isNotNull(selectorName, "selectorName");
+ this.selectorName = selectorName;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.graph.query.model.DynamicOperand#getSelectorName()
+ */
+ @Override
+ public final SelectorName getSelectorName() {
+ return selectorName;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return Visitors.readable(this);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals( Object obj ) {
+ if (obj == this) return true;
+ if (obj instanceof NodeDepth) {
+ NodeDepth that = (NodeDepth)obj;
+ return this.selectorName.equals(that.selectorName);
+ }
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitable#accept(org.jboss.dna.graph.query.model.Visitor)
+ */
+ public void accept( Visitor visitor ) {
+ visitor.visit(this);
+ }
+}
Copied: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/NodePath.java (from
rev 1285, trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Literal.java)
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/NodePath.java
(rev 0)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/NodePath.java 2009-10-03
04:09:29 UTC (rev 1286)
@@ -0,0 +1,90 @@
+/*
+ * JBoss DNA (
http://www.jboss.org/dna)
+ * See the COPYRIGHT.txt file distributed with this work for information
+ * regarding copyright ownership. Some portions may be licensed
+ * to Red Hat, Inc. under one or more contributor license agreements.
+ * See the AUTHORS.txt file in the distribution for a full listing of
+ * individual contributors.
+ *
+ * JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA
+ * is licensed to you under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * JBoss DNA is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
+ */
+package org.jboss.dna.graph.query.model;
+
+import net.jcip.annotations.Immutable;
+import org.jboss.dna.common.util.CheckArg;
+
+/**
+ * A dynamic operand that evaluates to the path of a node given by a selector, used in a
{@link Comparison} constraint.
+ */
+@Immutable
+public class NodePath extends DynamicOperand {
+ private final SelectorName selectorName;
+
+ /**
+ * Create a dynamic operand that evaluates to the path of the node identified by the
selector.
+ *
+ * @param selectorName the name of the selector
+ * @throws IllegalArgumentException if the selector name or property name are null
+ */
+ public NodePath( SelectorName selectorName ) {
+ CheckArg.isNotNull(selectorName, "selectorName");
+ this.selectorName = selectorName;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.graph.query.model.DynamicOperand#getSelectorName()
+ */
+ @Override
+ public final SelectorName getSelectorName() {
+ return selectorName;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return Visitors.readable(this);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals( Object obj ) {
+ if (obj == this) return true;
+ if (obj instanceof NodePath) {
+ NodePath that = (NodePath)obj;
+ return this.selectorName.equals(that.selectorName);
+ }
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitable#accept(org.jboss.dna.graph.query.model.Visitor)
+ */
+ public void accept( Visitor visitor ) {
+ visitor.visit(this);
+ }
+}
Modified: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Operator.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Operator.java 2009-10-03
04:07:41 UTC (rev 1285)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Operator.java 2009-10-03
04:09:29 UTC (rev 1286)
@@ -64,6 +64,29 @@
}
/**
+ * Get the equivalent operator if the operands are to be reversed.
+ *
+ * @return the reverse operator; never null
+ */
+ public Operator getReverse() {
+ switch (this) {
+ case GREATER_THAN:
+ return LESS_THAN;
+ case GREATER_THAN_OR_EQUAL_TO:
+ return LESS_THAN_OR_EQUAL_TO;
+ case LESS_THAN:
+ return GREATER_THAN;
+ case LESS_THAN_OR_EQUAL_TO:
+ return GREATER_THAN_OR_EQUAL_TO;
+ case EQUAL_TO:
+ case LIKE:
+ case NOT_EQUAL_TO:
+ default:
+ return this;
+ }
+ }
+
+ /**
* {@inheritDoc}
*
* @see java.lang.Enum#toString()
Modified: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Visitor.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Visitor.java 2009-10-03
04:07:41 UTC (rev 1285)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Visitor.java 2009-10-03
04:09:29 UTC (rev 1286)
@@ -66,6 +66,10 @@
void visit( NodeName obj );
+ void visit( NodePath obj );
+
+ void visit( NodeDepth obj );
+
void visit( NamedSelector obj );
void visit( Not obj );
Modified: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Visitors.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Visitors.java 2009-10-03
04:07:41 UTC (rev 1285)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Visitors.java 2009-10-03
04:09:29 UTC (rev 1286)
@@ -154,6 +154,16 @@
}
@Override
+ public void visit( NodeDepth depth ) {
+ super.visit(depth);
+ }
+
+ @Override
+ public void visit( NodePath path ) {
+ super.visit(path);
+ }
+
+ @Override
public void visit( NodeLocalName node ) {
symbols.add(node.getSelectorName());
}
@@ -348,6 +358,22 @@
/**
* {@inheritDoc}
*
+ * @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.NodeDepth)
+ */
+ public void visit( NodeDepth obj ) {
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.NodePath)
+ */
+ public void visit( NodePath obj ) {
+ }
+
+ /**
+ * {@inheritDoc}
+ *
* @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.NodeName)
*/
public void visit( NodeName obj ) {
@@ -689,6 +715,26 @@
/**
* {@inheritDoc}
*
+ * @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.NodeDepth)
+ */
+ public void visit( NodeDepth depth ) {
+ strategy.visit(depth);
+ visitNext();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.NodePath)
+ */
+ public void visit( NodePath path ) {
+ strategy.visit(path);
+ visitNext();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
* @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.NodeName)
*/
public void visit( NodeName nodeName ) {
@@ -1098,8 +1144,15 @@
* @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.Literal)
*/
public void visit( Literal literal ) {
- if (context == null) append(literal.getValue().toString());
- else
append(context.getValueFactories().getStringFactory().create(literal.getValue()));
+ Object value = literal.getValue();
+ boolean quote = value instanceof String || value instanceof Path || value
instanceof Name;
+ if (quote) append('\'');
+ if (context == null) {
+ append(literal.getValue().toString());
+ } else {
+
append(context.getValueFactories().getStringFactory().create(literal.getValue()));
+ }
+ if (quote) append('\'');
}
/**
@@ -1116,6 +1169,24 @@
/**
* {@inheritDoc}
*
+ * @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.NodeDepth)
+ */
+ public void visit( NodeDepth depth ) {
+
append("DEPTH(").append(depth.getSelectorName()).append(')');
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.NodePath)
+ */
+ public void visit( NodePath path ) {
+
append("PATH(").append(path.getSelectorName()).append(')');
+ }
+
+ /**
+ * {@inheritDoc}
+ *
* @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.NodeLocalName)
*/
public void visit( NodeLocalName name ) {
Modified:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/RewriteIdentityJoins.java
===================================================================
---
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/RewriteIdentityJoins.java 2009-10-03
04:07:41 UTC (rev 1285)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/RewriteIdentityJoins.java 2009-10-03
04:09:29 UTC (rev 1286)
@@ -49,8 +49,10 @@
import org.jboss.dna.graph.query.model.JoinCondition;
import org.jboss.dna.graph.query.model.Length;
import org.jboss.dna.graph.query.model.LowerCase;
+import org.jboss.dna.graph.query.model.NodeDepth;
import org.jboss.dna.graph.query.model.NodeLocalName;
import org.jboss.dna.graph.query.model.NodeName;
+import org.jboss.dna.graph.query.model.NodePath;
import org.jboss.dna.graph.query.model.Not;
import org.jboss.dna.graph.query.model.Or;
import org.jboss.dna.graph.query.model.Ordering;
@@ -338,8 +340,20 @@
PropertyValue value = (PropertyValue)operand;
SelectorName replacement = rewrittenSelectors.get(value.getSelectorName());
if (replacement == null) return operand;
- return new Length(new PropertyValue(replacement, value.getPropertyName()));
+ return new PropertyValue(replacement, value.getPropertyName());
}
+ if (operand instanceof NodeDepth) {
+ NodeDepth depth = (NodeDepth)operand;
+ SelectorName replacement = rewrittenSelectors.get(depth.getSelectorName());
+ if (replacement == null) return operand;
+ return new NodeDepth(replacement);
+ }
+ if (operand instanceof NodePath) {
+ NodePath path = (NodePath)operand;
+ SelectorName replacement = rewrittenSelectors.get(path.getSelectorName());
+ if (replacement == null) return operand;
+ return new NodePath(replacement);
+ }
return operand;
}
Copied:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/parse/InvalidQueryException.java
(from rev 1285,
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/InvalidQueryException.java)
===================================================================
---
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/parse/InvalidQueryException.java
(rev 0)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/parse/InvalidQueryException.java 2009-10-03
04:09:29 UTC (rev 1286)
@@ -0,0 +1,71 @@
+/*
+ * JBoss DNA (
http://www.jboss.org/dna)
+ * See the COPYRIGHT.txt file distributed with this work for information
+ * regarding copyright ownership. Some portions may be licensed
+ * to Red Hat, Inc. under one or more contributor license agreements.
+ * See the AUTHORS.txt file in the distribution for a full listing of
+ * individual contributors.
+ *
+ * JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA
+ * is licensed to you under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * JBoss DNA is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
+ */
+package org.jboss.dna.graph.query.parse;
+
+import org.jboss.dna.common.util.CheckArg;
+
+/**
+ * An exception signalling that an XPath query is invalid (but typically well-formed)
+ */
+public class InvalidQueryException extends RuntimeException {
+
+ /**
+ */
+ private static final long serialVersionUID = 1L;
+
+ private final String xpath;
+
+ /**
+ * Create an exception with the invalid query.
+ *
+ * @param xpath the XPath query that is invalid
+ */
+ public InvalidQueryException( String xpath ) {
+ super();
+ this.xpath = xpath;
+ }
+
+ /**
+ * Create an exception with the invalid query and a message.
+ *
+ * @param xpath the XPath query that is invalid
+ * @param message
+ */
+ public InvalidQueryException( String xpath,
+ String message ) {
+ super(message);
+ CheckArg.isNotNull(xpath, "xpath");
+ this.xpath = xpath;
+ }
+
+ /**
+ * Get the XPath query that is invalid.
+ *
+ * @return the query
+ */
+ public String getXPath() {
+ return xpath;
+ }
+
+}
Property changes on:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/parse/InvalidQueryException.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Modified: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/parse/QueryParser.java
===================================================================
---
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/parse/QueryParser.java 2009-10-03
04:07:41 UTC (rev 1285)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/parse/QueryParser.java 2009-10-03
04:09:29 UTC (rev 1286)
@@ -23,22 +23,34 @@
*/
package org.jboss.dna.graph.query.parse;
+import net.jcip.annotations.Immutable;
+import org.jboss.dna.common.text.ParsingException;
import org.jboss.dna.graph.ExecutionContext;
import org.jboss.dna.graph.query.model.QueryCommand;
/**
* The basic interface defining a component that is able to parse a string query into a
{@link QueryCommand}.
*/
+@Immutable
public interface QueryParser {
/**
+ * Get the name of the language that this parser is able to understand.
+ *
+ * @return the language name; never null
+ */
+ String getLanguage();
+
+ /**
* Parse the supplied query from a string representation into a {@link
QueryCommand}.
*
* @param query the query in string form; may not be null
* @param context the context in which the query is being parsed
* @return the query command
+ * @throws ParsingException if there is an error parsing the supplied query
+ * @throws InvalidQueryException if the supplied query can be parsed but is invalid
*/
QueryCommand parseQuery( String query,
- ExecutionContext context );
+ ExecutionContext context ) throws InvalidQueryException;
}
Modified:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/parse/SqlQueryParser.java
===================================================================
---
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/parse/SqlQueryParser.java 2009-10-03
04:07:41 UTC (rev 1285)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/parse/SqlQueryParser.java 2009-10-03
04:09:29 UTC (rev 1286)
@@ -67,8 +67,10 @@
import org.jboss.dna.graph.query.model.Literal;
import org.jboss.dna.graph.query.model.LowerCase;
import org.jboss.dna.graph.query.model.NamedSelector;
+import org.jboss.dna.graph.query.model.NodeDepth;
import org.jboss.dna.graph.query.model.NodeLocalName;
import org.jboss.dna.graph.query.model.NodeName;
+import org.jboss.dna.graph.query.model.NodePath;
import org.jboss.dna.graph.query.model.Not;
import org.jboss.dna.graph.query.model.Operator;
import org.jboss.dna.graph.query.model.Or;
@@ -102,9 +104,20 @@
*/
public class SqlQueryParser implements QueryParser {
+ public static final String LANGUAGE = "SQL";
+
/**
* {@inheritDoc}
*
+ * @see org.jboss.dna.graph.query.parse.QueryParser#getLanguage()
+ */
+ public String getLanguage() {
+ return LANGUAGE;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
* @see org.jboss.dna.graph.query.parse.QueryParser#parseQuery(String,
ExecutionContext)
*/
public QueryCommand parseQuery( String query,
@@ -188,12 +201,12 @@
List<ColumnExpression> columns = new ArrayList<ColumnExpression>();
do {
Position position = tokens.nextPosition();
- String propertyName = tokens.consume();
+ String propertyName = removeBracketsAndQuotes(tokens.consume());
SelectorName selectorName = null;
if (tokens.canConsume('.')) {
// We actually read the selector name, so now read the property name ...
selectorName = new SelectorName(propertyName);
- propertyName = tokens.consume();
+ propertyName = removeBracketsAndQuotes(tokens.consume());
}
String alias = propertyName;
if (tokens.canConsume("AS")) alias =
removeBracketsAndQuotes(tokens.consume());
@@ -304,7 +317,7 @@
propertyName = parseName(tokens, context);
} else {
if (!(source instanceof Selector)) {
- String msg =
GraphI18n.functionIsAmbiguous.text("CONTEXT()", pos.getLine(),
pos.getColumn());
+ String msg =
GraphI18n.functionIsAmbiguous.text("CONTAINS()", pos.getLine(),
pos.getColumn());
throw new ParsingException(pos, msg);
}
selectorName = ((Selector)source).getName();
@@ -681,6 +694,26 @@
}
result = new FullTextSearchScore(parseSelectorName(tokens));
tokens.consume(")");
+ } else if (tokens.canConsume("DEPTH", "(")) {
+ if (tokens.canConsume(")")) {
+ if (source instanceof Selector) {
+ return new NodeDepth(((Selector)source).getName());
+ }
+ String msg = GraphI18n.functionIsAmbiguous.text("DEPTH()",
pos.getLine(), pos.getColumn());
+ throw new ParsingException(pos, msg);
+ }
+ result = new NodeDepth(parseSelectorName(tokens));
+ tokens.consume(")");
+ } else if (tokens.canConsume("PATH", "(")) {
+ if (tokens.canConsume(")")) {
+ if (source instanceof Selector) {
+ return new NodePath(((Selector)source).getName());
+ }
+ String msg = GraphI18n.functionIsAmbiguous.text("PATH()",
pos.getLine(), pos.getColumn());
+ throw new ParsingException(pos, msg);
+ }
+ result = new NodePath(parseSelectorName(tokens));
+ tokens.consume(")");
} else {
result = parsePropertyValue(tokens, context, source);
}
@@ -743,7 +776,19 @@
* quotes
*/
protected String removeBracketsAndQuotes( String text ) {
- return text.replaceFirst("^['\"\\[]+",
"").replaceAll("['\"\\]]+$", "");
+ if (text.length() > 0) {
+ char firstChar = text.charAt(0);
+ switch (firstChar) {
+ case '\'':
+ case '"':
+ assert text.charAt(text.length() - 1) == firstChar;
+ return removeBracketsAndQuotes(text.substring(1, text.length() -
1));
+ case '[':
+ assert text.charAt(text.length() - 1) == ']';
+ return removeBracketsAndQuotes(text.substring(1, text.length() -
1));
+ }
+ }
+ return text;
}
protected NamedSelector parseNamedSelector( TokenStream tokens ) {
Modified:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/ProcessingComponent.java
===================================================================
---
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/ProcessingComponent.java 2009-10-03
04:07:41 UTC (rev 1285)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/ProcessingComponent.java 2009-10-03
04:09:29 UTC (rev 1286)
@@ -40,8 +40,10 @@
import org.jboss.dna.graph.query.model.FullTextSearchScore;
import org.jboss.dna.graph.query.model.Length;
import org.jboss.dna.graph.query.model.LowerCase;
+import org.jboss.dna.graph.query.model.NodeDepth;
import org.jboss.dna.graph.query.model.NodeLocalName;
import org.jboss.dna.graph.query.model.NodeName;
+import org.jboss.dna.graph.query.model.NodePath;
import org.jboss.dna.graph.query.model.PropertyValue;
import org.jboss.dna.graph.query.model.UpperCase;
import org.jboss.dna.graph.query.validate.Schemata.Column;
@@ -219,6 +221,40 @@
}
};
}
+ if (operand instanceof NodeDepth) {
+ NodeDepth nodeDepth = (NodeDepth)operand;
+ final int locationIndex =
columns.getLocationIndex(nodeDepth.getSelectorName().getName());
+ return new DynamicOperation() {
+ public PropertyType getExpectedType() {
+ return PropertyType.LONG;
+ }
+
+ public Object evaluate( Object[] tuple ) {
+ Location location = (Location)tuple[locationIndex];
+ if (location == null) return null;
+ Path path = location.getPath();
+ assert path != null;
+ return new Long(path.size());
+ }
+ };
+ }
+ if (operand instanceof NodePath) {
+ NodePath nodePath = (NodePath)operand;
+ final int locationIndex =
columns.getLocationIndex(nodePath.getSelectorName().getName());
+ return new DynamicOperation() {
+ public PropertyType getExpectedType() {
+ return PropertyType.PATH;
+ }
+
+ public Object evaluate( Object[] tuple ) {
+ Location location = (Location)tuple[locationIndex];
+ if (location == null) return null;
+ Path path = location.getPath();
+ assert path != null;
+ return path;
+ }
+ };
+ }
if (operand instanceof NodeName) {
NodeName nodeName = (NodeName)operand;
final int locationIndex =
columns.getLocationIndex(nodeName.getSelectorName().getName());
Modified: trunk/dna-graph/src/main/resources/org/jboss/dna/graph/GraphI18n.properties
===================================================================
--- trunk/dna-graph/src/main/resources/org/jboss/dna/graph/GraphI18n.properties 2009-10-03
04:07:41 UTC (rev 1285)
+++ trunk/dna-graph/src/main/resources/org/jboss/dna/graph/GraphI18n.properties 2009-10-03
04:09:29 UTC (rev 1286)
@@ -116,6 +116,7 @@
childNotFound = Child "{0}" could not be found under "{1}" in
workspace "{2}"
# Query
+unknownQueryLanguage = '{0}' is not a known query language
tableDoesNotExist = Table '{0}' does not exist
columnDoesNotExistOnTable = Column '{0}' does not exist on the table
'{1}'
columnDoesNotExistInQuery = Column '{0}' does not exist in query
Modified: trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/QueryBuilderTest.java
===================================================================
---
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/QueryBuilderTest.java 2009-10-03
04:07:41 UTC (rev 1285)
+++
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/QueryBuilderTest.java 2009-10-03
04:09:29 UTC (rev 1286)
@@ -433,7 +433,7 @@
.end()
.query();
assertThatSql(query, is("SELECT * FROM table AS nodes " + //
- "WHERE LENGTH(nodes.property) = literal"));
+ "WHERE LENGTH(nodes.property) =
'literal'"));
}
@Test
@@ -459,7 +459,7 @@
.end()
.query();
assertThatSql(query, is("SELECT * FROM table AS nodes " + //
- "WHERE LENGTH(nodes.property) != literal"));
+ "WHERE LENGTH(nodes.property) !=
'literal'"));
}
@Test
@@ -485,7 +485,7 @@
.end()
.query();
assertThatSql(query, is("SELECT * FROM table AS nodes " + //
- "WHERE LENGTH(nodes.property) < literal"));
+ "WHERE LENGTH(nodes.property) <
'literal'"));
}
@Test
@@ -511,7 +511,7 @@
.end()
.query();
assertThatSql(query, is("SELECT * FROM table AS nodes " + //
- "WHERE LENGTH(nodes.property) <=
literal"));
+ "WHERE LENGTH(nodes.property) <=
'literal'"));
}
@Test
@@ -537,7 +537,7 @@
.end()
.query();
assertThatSql(query, is("SELECT * FROM table AS nodes " + //
- "WHERE LENGTH(nodes.property) > literal"));
+ "WHERE LENGTH(nodes.property) >
'literal'"));
}
@Test
@@ -563,7 +563,7 @@
.end()
.query();
assertThatSql(query, is("SELECT * FROM table AS nodes " + //
- "WHERE LENGTH(nodes.property) >=
literal"));
+ "WHERE LENGTH(nodes.property) >=
'literal'"));
}
@Test
@@ -583,7 +583,7 @@
public void shouldBuildQueryWithCriteriaUsingLengthLike() {
query = builder.selectStar().from("table AS
nodes").where().length("nodes",
"property").isLike("literal").end().query();
assertThatSql(query, is("SELECT * FROM table AS nodes " + //
- "WHERE LENGTH(nodes.property) LIKE literal"));
+ "WHERE LENGTH(nodes.property) LIKE
'literal'"));
}
@Test
@@ -600,10 +600,44 @@
}
@Test
+ public void shouldBuildQueryWithCriteriaUsingNodeDepthEqualToLiteral() {
+ query = builder.selectStar().from("table AS
nodes").where().depth("nodes").isEqualTo(3).end().query();
+ assertThatSql(query, is("SELECT * FROM table AS nodes " + //
+ "WHERE DEPTH(nodes) = 3"));
+ }
+
+ @Test
+ public void shouldBuildQueryWithCriteriaUsingNodeDepthLessThanOrEqualToLongLiteral()
{
+ query = builder.selectStar().from("table AS
nodes").where().depth("nodes").isLessThanOrEqualTo(3).end().query();
+ assertThatSql(query, is("SELECT * FROM table AS nodes " + //
+ "WHERE DEPTH(nodes) <= 3"));
+ }
+
+ @Test
+ public void
shouldBuildQueryWithCriteriaUsingNodeDepthLessThanOrEqualToStringLiteral() {
+ query = builder.selectStar().from("table AS
nodes").where().depth("nodes").isLessThanOrEqualTo(3).end().query();
+ assertThatSql(query, is("SELECT * FROM table AS nodes " + //
+ "WHERE DEPTH(nodes) <= 3"));
+ }
+
+ @Test
+ public void shouldBuildQueryWithCriteriaUsingNodeDepthLessThanOrEqualToVariable() {
+ query = builder.selectStar()
+ .from("table AS nodes")
+ .where()
+ .depth("nodes")
+ .isLessThanOrEqualToVariable("value")
+ .end()
+ .query();
+ assertThatSql(query, is("SELECT * FROM table AS nodes " + //
+ "WHERE DEPTH(nodes) <= $value"));
+ }
+
+ @Test
public void shouldBuildQueryWithCriteriaUsingNodeNameEqualTo() {
query = builder.selectStar().from("table AS
nodes").where().nodeName("nodes").isEqualTo("literal").end().query();
assertThatSql(query, is("SELECT * FROM table AS nodes " + //
- "WHERE NAME(nodes) = literal"));
+ "WHERE NAME(nodes) = 'literal'"));
}
@Test
@@ -617,7 +651,7 @@
public void shouldBuildQueryWithCriteriaUsingNodeNameNotEqualTo() {
query = builder.selectStar().from("table AS
nodes").where().nodeName("nodes").isNotEqualTo("literal").end().query();
assertThatSql(query, is("SELECT * FROM table AS nodes " + //
- "WHERE NAME(nodes) != literal"));
+ "WHERE NAME(nodes) != 'literal'"));
}
@Test
@@ -637,7 +671,7 @@
public void shouldBuildQueryWithCriteriaUsingNodeNameLessThan() {
query = builder.selectStar().from("table AS
nodes").where().nodeName("nodes").isLessThan("literal").end().query();
assertThatSql(query, is("SELECT * FROM table AS nodes " + //
- "WHERE NAME(nodes) < literal"));
+ "WHERE NAME(nodes) < 'literal'"));
}
@Test
@@ -657,7 +691,7 @@
.end()
.query();
assertThatSql(query, is("SELECT * FROM table AS nodes " + //
- "WHERE NAME(nodes) <= literal"));
+ "WHERE NAME(nodes) <= 'literal'"));
}
@Test
@@ -677,7 +711,7 @@
public void shouldBuildQueryWithCriteriaUsingNodeNameGreaterThan() {
query = builder.selectStar().from("table AS
nodes").where().nodeName("nodes").isGreaterThan("literal").end().query();
assertThatSql(query, is("SELECT * FROM table AS nodes " + //
- "WHERE NAME(nodes) > literal"));
+ "WHERE NAME(nodes) > 'literal'"));
}
@Test
@@ -703,7 +737,7 @@
.end()
.query();
assertThatSql(query, is("SELECT * FROM table AS nodes " + //
- "WHERE NAME(nodes) >= literal"));
+ "WHERE NAME(nodes) >= 'literal'"));
}
@Test
@@ -723,7 +757,7 @@
public void shouldBuildQueryWithCriteriaUsingNodeNameLike() {
query = builder.selectStar().from("table AS
nodes").where().nodeName("nodes").isLike("literal").end().query();
assertThatSql(query, is("SELECT * FROM table AS nodes " + //
- "WHERE NAME(nodes) LIKE literal"));
+ "WHERE NAME(nodes) LIKE 'literal'"));
}
@Test
@@ -737,7 +771,7 @@
public void shouldBuildQueryWithCriteriaUsingNodeLocalNameEqualTo() {
query = builder.selectStar().from("table AS
nodes").where().nodeLocalName("nodes").isEqualTo("literal").end().query();
assertThatSql(query, is("SELECT * FROM table AS nodes " + //
- "WHERE LOCALNAME(nodes) = literal"));
+ "WHERE LOCALNAME(nodes) =
'literal'"));
}
@Test
@@ -757,7 +791,7 @@
public void shouldBuildQueryWithCriteriaUsingNodeLocalNameNotEqualTo() {
query = builder.selectStar().from("table AS
nodes").where().nodeLocalName("nodes").isNotEqualTo("literal").end().query();
assertThatSql(query, is("SELECT * FROM table AS nodes " + //
- "WHERE LOCALNAME(nodes) != literal"));
+ "WHERE LOCALNAME(nodes) !=
'literal'"));
}
@Test
@@ -777,7 +811,7 @@
public void shouldBuildQueryWithCriteriaUsingNodeLocalNameLessThan() {
query = builder.selectStar().from("table AS
nodes").where().nodeLocalName("nodes").isLessThan("literal").end().query();
assertThatSql(query, is("SELECT * FROM table AS nodes " + //
- "WHERE LOCALNAME(nodes) < literal"));
+ "WHERE LOCALNAME(nodes) <
'literal'"));
}
@Test
@@ -803,7 +837,7 @@
.end()
.query();
assertThatSql(query, is("SELECT * FROM table AS nodes " + //
- "WHERE LOCALNAME(nodes) <= literal"));
+ "WHERE LOCALNAME(nodes) <=
'literal'"));
}
@Test
@@ -823,7 +857,7 @@
public void shouldBuildQueryWithCriteriaUsingNodeLocalNameGreaterThan() {
query = builder.selectStar().from("table AS
nodes").where().nodeLocalName("nodes").isGreaterThan("literal").end().query();
assertThatSql(query, is("SELECT * FROM table AS nodes " + //
- "WHERE LOCALNAME(nodes) > literal"));
+ "WHERE LOCALNAME(nodes) >
'literal'"));
}
@Test
@@ -849,7 +883,7 @@
.end()
.query();
assertThatSql(query, is("SELECT * FROM table AS nodes " + //
- "WHERE LOCALNAME(nodes) >= literal"));
+ "WHERE LOCALNAME(nodes) >=
'literal'"));
}
@Test
@@ -869,7 +903,7 @@
public void shouldBuildQueryWithCriteriaUsingNodeLocalNameLike() {
query = builder.selectStar().from("table AS
nodes").where().nodeLocalName("nodes").isLike("literal").end().query();
assertThatSql(query, is("SELECT * FROM table AS nodes " + //
- "WHERE LOCALNAME(nodes) LIKE literal"));
+ "WHERE LOCALNAME(nodes) LIKE
'literal'"));
}
@Test
@@ -896,7 +930,7 @@
.end()
.query();
assertThatSql(query, is("SELECT * FROM table AS nodes " + //
- "WHERE UPPER(NAME(nodes)) = literal"));
+ "WHERE UPPER(NAME(nodes)) =
'literal'"));
}
@Test
@@ -924,7 +958,7 @@
.end()
.query();
assertThatSql(query, is("SELECT * FROM table AS nodes " + //
- "WHERE UPPER(NAME(nodes)) != literal"));
+ "WHERE UPPER(NAME(nodes)) !=
'literal'"));
}
@Test
@@ -952,7 +986,7 @@
.end()
.query();
assertThatSql(query, is("SELECT * FROM table AS nodes " + //
- "WHERE UPPER(NAME(nodes)) < literal"));
+ "WHERE UPPER(NAME(nodes)) <
'literal'"));
}
@Test
@@ -980,7 +1014,7 @@
.end()
.query();
assertThatSql(query, is("SELECT * FROM table AS nodes " + //
- "WHERE UPPER(NAME(nodes)) <= literal"));
+ "WHERE UPPER(NAME(nodes)) <=
'literal'"));
}
@Test
@@ -1008,7 +1042,7 @@
.end()
.query();
assertThatSql(query, is("SELECT * FROM table AS nodes " + //
- "WHERE UPPER(NAME(nodes)) > literal"));
+ "WHERE UPPER(NAME(nodes)) >
'literal'"));
}
@Test
@@ -1036,7 +1070,7 @@
.end()
.query();
assertThatSql(query, is("SELECT * FROM table AS nodes " + //
- "WHERE UPPER(NAME(nodes)) >= literal"));
+ "WHERE UPPER(NAME(nodes)) >=
'literal'"));
}
@Test
@@ -1064,7 +1098,7 @@
.end()
.query();
assertThatSql(query, is("SELECT * FROM table AS nodes " + //
- "WHERE UPPER(NAME(nodes)) LIKE literal"));
+ "WHERE UPPER(NAME(nodes)) LIKE
'literal'"));
}
@Test
@@ -1092,7 +1126,7 @@
.end()
.query();
assertThatSql(query, is("SELECT * FROM table AS nodes " + //
- "WHERE LOWER(NAME(nodes)) = literal"));
+ "WHERE LOWER(NAME(nodes)) =
'literal'"));
}
@Test
@@ -1120,7 +1154,7 @@
.end()
.query();
assertThatSql(query, is("SELECT * FROM table AS nodes " + //
- "WHERE LOWER(NAME(nodes)) != literal"));
+ "WHERE LOWER(NAME(nodes)) !=
'literal'"));
}
@Test
@@ -1148,7 +1182,7 @@
.end()
.query();
assertThatSql(query, is("SELECT * FROM table AS nodes " + //
- "WHERE LOWER(NAME(nodes)) < literal"));
+ "WHERE LOWER(NAME(nodes)) <
'literal'"));
}
@Test
@@ -1176,7 +1210,7 @@
.end()
.query();
assertThatSql(query, is("SELECT * FROM table AS nodes " + //
- "WHERE LOWER(NAME(nodes)) <= literal"));
+ "WHERE LOWER(NAME(nodes)) <=
'literal'"));
}
@Test
@@ -1204,7 +1238,7 @@
.end()
.query();
assertThatSql(query, is("SELECT * FROM table AS nodes " + //
- "WHERE LOWER(NAME(nodes)) > literal"));
+ "WHERE LOWER(NAME(nodes)) >
'literal'"));
}
@Test
@@ -1232,7 +1266,7 @@
.end()
.query();
assertThatSql(query, is("SELECT * FROM table AS nodes " + //
- "WHERE LOWER(NAME(nodes)) >= literal"));
+ "WHERE LOWER(NAME(nodes)) >=
'literal'"));
}
@Test
@@ -1260,7 +1294,7 @@
.end()
.query();
assertThatSql(query, is("SELECT * FROM table AS nodes " + //
- "WHERE LOWER(NAME(nodes)) LIKE literal"));
+ "WHERE LOWER(NAME(nodes)) LIKE
'literal'"));
}
@Test
@@ -1289,6 +1323,6 @@
.end()
.query();
assertThatSql(query, is("SELECT * FROM table AS nodes " + //
- "WHERE LOWER(UPPER(NAME(nodes))) = literal"));
+ "WHERE LOWER(UPPER(NAME(nodes))) =
'literal'"));
}
}
Modified:
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/parse/SqlQueryParserTest.java
===================================================================
---
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/parse/SqlQueryParserTest.java 2009-10-03
04:07:41 UTC (rev 1285)
+++
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/parse/SqlQueryParserTest.java 2009-10-03
04:09:29 UTC (rev 1286)
@@ -52,8 +52,10 @@
import org.jboss.dna.graph.query.model.Literal;
import org.jboss.dna.graph.query.model.LowerCase;
import org.jboss.dna.graph.query.model.NamedSelector;
+import org.jboss.dna.graph.query.model.NodeDepth;
import org.jboss.dna.graph.query.model.NodeLocalName;
import org.jboss.dna.graph.query.model.NodeName;
+import org.jboss.dna.graph.query.model.NodePath;
import org.jboss.dna.graph.query.model.Not;
import org.jboss.dna.graph.query.model.Operator;
import org.jboss.dna.graph.query.model.Or;
@@ -1229,6 +1231,88 @@
}
//
----------------------------------------------------------------------------------------------------------------
+ // parseDynamicOperand - DEPTH
+ //
----------------------------------------------------------------------------------------------------------------
+
+ @Test
+ public void shouldParseDynamicOperandFromStringContainingDepthOfSelector() {
+ DynamicOperand operand =
parser.parseDynamicOperand(tokens("DEPTH(tableA)"), context,
mock(Source.class));
+ assertThat(operand, is(instanceOf(NodeDepth.class)));
+ NodeDepth depth = (NodeDepth)operand;
+ assertThat(depth.getSelectorName(), is(selectorName("tableA")));
+ }
+
+ @Test
+ public void
shouldParseDynamicOperandFromStringContainingDepthWithNoSelectorOnlyIfThereIsOneSelectorAsSource()
{
+ Source source = new NamedSelector(selectorName("tableA"));
+ DynamicOperand operand = parser.parseDynamicOperand(tokens("DEPTH()"),
context, source);
+ assertThat(operand, is(instanceOf(NodeDepth.class)));
+ NodeDepth depth = (NodeDepth)operand;
+ assertThat(depth.getSelectorName(), is(selectorName("tableA")));
+ }
+
+ @Test( expected = ParsingException.class )
+ public void
shouldFailToParseDynamicOperandFromStringContainingDepthWithNoSelectorIfTheSourceIsNotASelector()
{
+ parser.parseDynamicOperand(tokens("DEPTH()"), context,
mock(Source.class));
+ }
+
+ @Test( expected = ParsingException.class )
+ public void
shouldFailToParseDynamicOperandFromStringContainingDepthWithSelectorNameAndProperty() {
+ parser.parseDynamicOperand(tokens("DEPTH(tableA.property) other"),
context, mock(Source.class));
+ }
+
+ @Test( expected = ParsingException.class )
+ public void
shouldFailToParseDynamicOperandFromStringContainingDepthWithoutClosingParenthesis() {
+ parser.parseDynamicOperand(tokens("DEPTH(tableA other"), context,
mock(Source.class));
+ }
+
+ @Test( expected = ParsingException.class )
+ public void
shouldFailToParseDynamicOperandFromStringContainingDepthWithoutOpeningParenthesis() {
+ parser.parseDynamicOperand(tokens("Depth tableA other"), context,
mock(Source.class));
+ }
+
+ //
----------------------------------------------------------------------------------------------------------------
+ // parseDynamicOperand - PATH
+ //
----------------------------------------------------------------------------------------------------------------
+
+ @Test
+ public void shouldParseDynamicOperandFromStringContainingPathOfSelector() {
+ DynamicOperand operand =
parser.parseDynamicOperand(tokens("PATH(tableA)"), context,
mock(Source.class));
+ assertThat(operand, is(instanceOf(NodePath.class)));
+ NodePath path = (NodePath)operand;
+ assertThat(path.getSelectorName(), is(selectorName("tableA")));
+ }
+
+ @Test
+ public void
shouldParseDynamicOperandFromStringContainingPathWithNoSelectorOnlyIfThereIsOneSelectorAsSource()
{
+ Source source = new NamedSelector(selectorName("tableA"));
+ DynamicOperand operand = parser.parseDynamicOperand(tokens("PATH()"),
context, source);
+ assertThat(operand, is(instanceOf(NodePath.class)));
+ NodePath path = (NodePath)operand;
+ assertThat(path.getSelectorName(), is(selectorName("tableA")));
+ }
+
+ @Test( expected = ParsingException.class )
+ public void
shouldFailToParseDynamicOperandFromStringContainingPathWithNoSelectorIfTheSourceIsNotASelector()
{
+ parser.parseDynamicOperand(tokens("PATH()"), context,
mock(Source.class));
+ }
+
+ @Test( expected = ParsingException.class )
+ public void
shouldFailToParseDynamicOperandFromStringContainingPathWithSelectorNameAndProperty() {
+ parser.parseDynamicOperand(tokens("PATH(tableA.property) other"),
context, mock(Source.class));
+ }
+
+ @Test( expected = ParsingException.class )
+ public void
shouldFailToParseDynamicOperandFromStringContainingPathWithoutClosingParenthesis() {
+ parser.parseDynamicOperand(tokens("PATH(tableA other"), context,
mock(Source.class));
+ }
+
+ @Test( expected = ParsingException.class )
+ public void
shouldFailToParseDynamicOperandFromStringContainingPathWithoutOpeningParenthesis() {
+ parser.parseDynamicOperand(tokens("Path tableA other"), context,
mock(Source.class));
+ }
+
+ //
----------------------------------------------------------------------------------------------------------------
// parseDynamicOperand - NAME
//
----------------------------------------------------------------------------------------------------------------
Modified:
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/process/AbstractQueryResultsTest.java
===================================================================
---
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/process/AbstractQueryResultsTest.java 2009-10-03
04:07:41 UTC (rev 1285)
+++
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/process/AbstractQueryResultsTest.java 2009-10-03
04:09:29 UTC (rev 1286)
@@ -37,13 +37,14 @@
import org.jboss.dna.graph.query.model.Column;
import org.jboss.dna.graph.query.model.FullTextSearchScore;
import org.jboss.dna.graph.query.model.Length;
+import org.jboss.dna.graph.query.model.NodeDepth;
import org.jboss.dna.graph.query.model.NodeLocalName;
import org.jboss.dna.graph.query.model.NodeName;
+import org.jboss.dna.graph.query.model.NodePath;
import org.jboss.dna.graph.query.model.Order;
import org.jboss.dna.graph.query.model.Ordering;
import org.jboss.dna.graph.query.model.PropertyValue;
import org.jboss.dna.graph.query.model.SelectorName;
-import org.jboss.dna.graph.query.process.QueryResultColumns;
import org.jboss.dna.graph.query.validate.ImmutableSchemata;
import org.jboss.dna.graph.query.validate.Schemata;
@@ -209,6 +210,24 @@
return new Ordering(new Length(new PropertyValue(column.getSelectorName(),
column.getPropertyName())), order);
}
+ protected Ordering orderByNodeDepth( String selectorName ) {
+ return orderByNodeDepth(selectorName, Order.ASCENDING);
+ }
+
+ protected Ordering orderByNodeDepth( String selectorName,
+ Order order ) {
+ return new Ordering(new NodeDepth(selector(selectorName)), order);
+ }
+
+ protected Ordering orderByNodePath( String selectorName ) {
+ return orderByNodePath(selectorName, Order.ASCENDING);
+ }
+
+ protected Ordering orderByNodePath( String selectorName,
+ Order order ) {
+ return new Ordering(new NodePath(selector(selectorName)), order);
+ }
+
protected Ordering orderByNodeName( String selectorName ) {
return orderByNodeName(selectorName, Order.ASCENDING);
}
Modified:
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/process/SortValuesComponentTest.java
===================================================================
---
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/process/SortValuesComponentTest.java 2009-10-03
04:07:41 UTC (rev 1285)
+++
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/process/SortValuesComponentTest.java 2009-10-03
04:09:29 UTC (rev 1286)
@@ -33,8 +33,6 @@
import org.jboss.dna.graph.query.QueryResults.Columns;
import org.jboss.dna.graph.query.model.Ordering;
import org.jboss.dna.graph.query.plan.PlanHints;
-import org.jboss.dna.graph.query.process.ProcessingComponent;
-import org.jboss.dna.graph.query.process.SortValuesComponent;
import org.jboss.dna.graph.query.validate.Schemata;
import org.junit.Before;
import org.junit.Test;
@@ -112,8 +110,40 @@
}
@Test
+ public void shouldReturnAllResultsOrderedByNodeDepth() {
+ orderings.add(orderByNodeDepth("Selector1"));
+ component = new SortValuesComponent(delegate, orderings);
+ inputTuples.add(tuple(columns, "/a/b1", "v1", 100,
"v4"));
+ inputTuples.add(tuple(columns, "/a/b2/c4", "v4", 100,
"v3"));
+ inputTuples.add(tuple(columns, "/a", 100, 100, "v2"));
+ inputTuples.add(tuple(columns, "/a/b4/c3", "v3", 100,
"v1"));
+ List<Object[]> expected = new ArrayList<Object[]>();
+ expected.add(inputTuples.get(2));
+ expected.add(inputTuples.get(0));
+ expected.add(inputTuples.get(1));
+ expected.add(inputTuples.get(3));
+ assertThat(component.execute(), is(expected));
+ }
+
+ @Test
+ public void shouldReturnAllResultsOrderedByNodePath() {
+ orderings.add(orderByNodePath("Selector1"));
+ component = new SortValuesComponent(delegate, orderings);
+ inputTuples.add(tuple(columns, "/a/b1", "v1", 100,
"v4"));
+ inputTuples.add(tuple(columns, "/a/b1/c4[2]", "v4", 100,
"v3"));
+ inputTuples.add(tuple(columns, "/a/b1/c2", 100, 100, "v2"));
+ inputTuples.add(tuple(columns, "/a/b1/c4", "v3", 100,
"v1"));
+ List<Object[]> expected = new ArrayList<Object[]>();
+ expected.add(inputTuples.get(0));
+ expected.add(inputTuples.get(2));
+ expected.add(inputTuples.get(3));
+ expected.add(inputTuples.get(1));
+ assertThat(component.execute(), is(expected));
+ }
+
+ @Test
public void shouldReturnAllResultsOrderedByNodeLocalName() {
- orderings.add(orderByNodeName("Selector1"));
+ orderings.add(orderByNodeLocalName("Selector1"));
component = new SortValuesComponent(delegate, orderings);
inputTuples.add(tuple(columns, "/a/b1/c1", "v1", 100,
"v4"));
inputTuples.add(tuple(columns, "/a/b2/c4", "v4", 100,
"v3"));
Modified: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/xpath/XPath.java
===================================================================
--- trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/xpath/XPath.java 2009-10-03 04:07:41 UTC
(rev 1285)
+++ trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/xpath/XPath.java 2009-10-03 04:09:29 UTC
(rev 1286)
@@ -23,13 +23,19 @@
*/
package org.jboss.dna.jcr.xpath;
+import java.util.ArrayList;
+import java.util.Iterator;
import java.util.List;
import org.jboss.dna.common.util.CheckArg;
+import org.jboss.dna.common.util.HashCode;
import org.jboss.dna.common.util.ObjectUtil;
import org.jboss.dna.graph.query.model.Operator;
/**
+ * Abstract syntax components of an XPath query. The supported grammar is defined by JCR
1.0, and is a subset of what is allowed
+ * by the W3C XPath 2.0 specification.
*
+ * @see XPathParser#parseXPath(String)
*/
public class XPath {
@@ -39,10 +45,18 @@
FOLLOWS;
}
- public static interface Component {
+ public static abstract class Component {
+ /**
+ * Return the collapsable form
+ *
+ * @return the collapsed form of th is component; never null and possibly the
same as this
+ */
+ public Component collapse() {
+ return this;
+ }
}
- public static abstract class UnaryComponent implements Component {
+ public static abstract class UnaryComponent extends Component {
protected final Component wrapped;
public UnaryComponent( Component wrapped ) {
@@ -85,7 +99,7 @@
}
}
- public static abstract class BinaryComponent implements Component {
+ public static abstract class BinaryComponent extends Component {
private final Component left;
private final Component right;
@@ -121,8 +135,25 @@
}
/**
+ * @return operator
+ */
+ public Operator getOperator() {
+ return operator;
+ }
+
+ /**
* {@inheritDoc}
*
+ * @see org.jboss.dna.jcr.xpath.XPath.Component#collapse()
+ */
+ @Override
+ public Component collapse() {
+ return new Comparison(getLeft().collapse(), operator,
getRight().collapse());
+ }
+
+ /**
+ * {@inheritDoc}
+ *
* @see java.lang.Object#toString()
*/
@Override
@@ -160,6 +191,16 @@
/**
* {@inheritDoc}
*
+ * @see org.jboss.dna.jcr.xpath.XPath.Component#collapse()
+ */
+ @Override
+ public Component collapse() {
+ return new NodeComparison(getLeft().collapse(), operator,
getRight().collapse());
+ }
+
+ /**
+ * {@inheritDoc}
+ *
* @see java.lang.Object#toString()
*/
@Override
@@ -193,6 +234,16 @@
/**
* {@inheritDoc}
*
+ * @see org.jboss.dna.jcr.xpath.XPath.Component#collapse()
+ */
+ @Override
+ public Component collapse() {
+ return new Add(getLeft().collapse(), getRight().collapse());
+ }
+
+ /**
+ * {@inheritDoc}
+ *
* @see java.lang.Object#toString()
*/
@Override
@@ -225,6 +276,16 @@
/**
* {@inheritDoc}
*
+ * @see org.jboss.dna.jcr.xpath.XPath.Component#collapse()
+ */
+ @Override
+ public Component collapse() {
+ return new Subtract(getLeft().collapse(), getRight().collapse());
+ }
+
+ /**
+ * {@inheritDoc}
+ *
* @see java.lang.Object#toString()
*/
@Override
@@ -257,6 +318,16 @@
/**
* {@inheritDoc}
*
+ * @see org.jboss.dna.jcr.xpath.XPath.Component#collapse()
+ */
+ @Override
+ public Component collapse() {
+ return new And(getLeft().collapse(), getRight().collapse());
+ }
+
+ /**
+ * {@inheritDoc}
+ *
* @see java.lang.Object#toString()
*/
@Override
@@ -312,6 +383,70 @@
}
}
+ public static class Intersect extends BinaryComponent {
+ public Intersect( Component left,
+ Component right ) {
+ super(left, right);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return getLeft() + " intersect " + getRight();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals( Object obj ) {
+ if (obj == this) return true;
+ if (obj instanceof Intersect) {
+ Intersect that = (Intersect)obj;
+ return this.getLeft().equals(that.getLeft()) &&
this.getRight().equals(that.getRight());
+ }
+ return false;
+ }
+ }
+
+ public static class Except extends BinaryComponent {
+ public Except( Component left,
+ Component right ) {
+ super(left, right);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return getLeft() + " except " + getRight();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals( Object obj ) {
+ if (obj == this) return true;
+ if (obj instanceof Except) {
+ Except that = (Except)obj;
+ return this.getLeft().equals(that.getLeft()) &&
this.getRight().equals(that.getRight());
+ }
+ return false;
+ }
+ }
+
public static class Or extends BinaryComponent {
public Or( Component left,
Component right ) {
@@ -321,6 +456,16 @@
/**
* {@inheritDoc}
*
+ * @see org.jboss.dna.jcr.xpath.XPath.Component#collapse()
+ */
+ @Override
+ public Component collapse() {
+ return new Or(getLeft().collapse(), getRight().collapse());
+ }
+
+ /**
+ * {@inheritDoc}
+ *
* @see java.lang.Object#toString()
*/
@Override
@@ -344,7 +489,7 @@
}
}
- public static class ContextItem implements Component {
+ public static class ContextItem extends Component {
/**
* {@inheritDoc}
*
@@ -366,7 +511,7 @@
}
}
- public static class Literal implements Component {
+ public static class Literal extends Component {
private final String value;
public Literal( String value ) {
@@ -380,6 +525,19 @@
return value;
}
+ public boolean isInteger() {
+ try {
+ Integer.parseInt(value);
+ return true;
+ } catch (NumberFormatException e) {
+ return false;
+ }
+ }
+
+ public int getValueAsInteger() {
+ return Integer.parseInt(value);
+ }
+
/**
* {@inheritDoc}
*
@@ -405,7 +563,7 @@
}
}
- public static class FunctionCall implements Component {
+ public static class FunctionCall extends Component {
private final NameTest name;
private final List<Component> arguments;
@@ -434,6 +592,20 @@
/**
* {@inheritDoc}
*
+ * @see org.jboss.dna.jcr.xpath.XPath.Component#collapse()
+ */
+ @Override
+ public Component collapse() {
+ List<Component> args = new
ArrayList<Component>(arguments.size());
+ for (Component arg : arguments) {
+ args.add(arg.collapse());
+ }
+ return new FunctionCall(name, args);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
@@ -467,7 +639,7 @@
return sb.toString();
}
- public static class PathExpression implements Component {
+ public static class PathExpression extends Component implements
Iterable<StepExpression> {
private final List<StepExpression> steps;
private final boolean relative;
@@ -491,9 +663,37 @@
return steps;
}
+ public StepExpression getLastStep() {
+ return steps.isEmpty() ? null : steps.get(steps.size() - 1);
+ }
+
+ public PathExpression withoutLast() {
+ assert !steps.isEmpty();
+ return new PathExpression(relative, steps.subList(0, steps.size() - 1));
+ }
+
+ public PathExpression withoutFirst() {
+ assert !steps.isEmpty();
+ return new PathExpression(relative, steps.subList(1, steps.size()));
+ }
+
/**
* {@inheritDoc}
*
+ * @see java.lang.Iterable#iterator()
+ */
+ public Iterator<StepExpression> iterator() {
+ return steps.iterator();
+ }
+
+ @Override
+ public Component collapse() {
+ return steps.size() == 1 ? steps.get(0).collapse() : this;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
@@ -517,10 +717,10 @@
}
}
- public static interface StepExpression extends Component {
+ public static abstract class StepExpression extends Component {
}
- public static class FilterStep implements StepExpression {
+ public static class FilterStep extends StepExpression {
private final Component primaryExpression;
private final List<Component> predicates;
@@ -546,6 +746,11 @@
return predicates;
}
+ @Override
+ public Component collapse() {
+ return predicates.isEmpty() ? primaryExpression.collapse() : this;
+ }
+
/**
* {@inheritDoc}
*
@@ -572,7 +777,7 @@
}
}
- public static class DescendantOrSelf implements StepExpression {
+ public static class DescendantOrSelf extends StepExpression {
/**
* {@inheritDoc}
*
@@ -594,7 +799,7 @@
}
}
- public static class AxisStep implements StepExpression {
+ public static class AxisStep extends StepExpression {
private final NodeTest nodeTest;
private final List<Component> predicates;
@@ -620,6 +825,11 @@
return predicates;
}
+ @Override
+ public Component collapse() {
+ return predicates.isEmpty() ? nodeTest.collapse() : this;
+ }
+
/**
* {@inheritDoc}
*
@@ -646,7 +856,7 @@
}
}
- public static class ParenthesizedExpression implements Component {
+ public static class ParenthesizedExpression extends Component {
private final Component wrapped;
public ParenthesizedExpression() {
@@ -667,6 +877,16 @@
/**
* {@inheritDoc}
*
+ * @see org.jboss.dna.jcr.xpath.XPath.Component#collapse()
+ */
+ @Override
+ public Component collapse() {
+ return wrapped instanceof BinaryComponent ? this : wrapped;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
@@ -690,13 +910,13 @@
}
}
- public static interface NodeTest extends Component {
+ public static abstract class NodeTest extends Component {
}
- public static interface KindTest extends NodeTest {
+ public static abstract class KindTest extends NodeTest {
}
- public static class NameTest implements NodeTest {
+ public static class NameTest extends NodeTest {
private final String prefixTest;
private final String localTest;
@@ -707,22 +927,55 @@
}
/**
- * @return prefixTest
+ * @return the prefix criteria, or null if the prefix criteria is a wildcard
*/
public String getPrefixTest() {
return prefixTest;
}
/**
- * @return localTest
+ * @return the local name criteria, or null if the local name criteria is a
wildcard
*/
public String getLocalTest() {
return localTest;
}
/**
+ * Determine if this name test exactly matches the supplied prefix and local name
values.
+ *
+ * @param prefix the prefix; may be null
+ * @param local the local name; may be null
+ * @return true if this name matches the supplied values, or false otherwise.
+ */
+ public boolean matches( String prefix,
+ String local ) {
+ if (this.prefixTest != null && !this.prefixTest.equals(prefix))
return false;
+ if (this.localTest != null && !this.localTest.equals(local)) return
false;
+ return true;
+ }
+
+ /**
+ * Return whether this represents a wildcard, meaning both {@link
#getPrefixTest()} and {@link #getLocalTest()} are null.
+ *
+ * @return true if this is a wildcard name test.
+ */
+ public boolean isWildcard() {
+ return prefixTest == null && localTest == null;
+ }
+
+ /**
* {@inheritDoc}
*
+ * @see java.lang.Object#hashCode()
+ */
+ @Override
+ public int hashCode() {
+ return HashCode.compute(this.prefixTest, this.localTest);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
@@ -748,18 +1001,18 @@
}
}
- public static class AttributeNameTest implements NodeTest {
- private final NodeTest nodeTest;
+ public static class AttributeNameTest extends NodeTest {
+ private final NameTest nameTest;
- public AttributeNameTest( NodeTest nodeTest ) {
- this.nodeTest = nodeTest;
+ public AttributeNameTest( NameTest nameTest ) {
+ this.nameTest = nameTest;
}
/**
* @return nodeTest
*/
- public NodeTest getNodeTest() {
- return nodeTest;
+ public NameTest getNameTest() {
+ return nameTest;
}
/**
@@ -769,7 +1022,7 @@
*/
@Override
public String toString() {
- return "@" + nodeTest;
+ return "@" + nameTest;
}
/**
@@ -782,14 +1035,14 @@
if (obj == this) return true;
if (obj instanceof AttributeNameTest) {
AttributeNameTest that = (AttributeNameTest)obj;
- return this.nodeTest.equals(that.nodeTest);
+ return this.nameTest.equals(that.nameTest);
}
return false;
}
}
- public static class AnyKindTest implements KindTest {
+ public static class AnyKindTest extends KindTest {
/**
* {@inheritDoc}
*
@@ -811,7 +1064,7 @@
}
}
- public static class TextTest implements KindTest {
+ public static class TextTest extends KindTest {
/**
* {@inheritDoc}
*
@@ -833,7 +1086,7 @@
}
}
- public static class CommentTest implements KindTest {
+ public static class CommentTest extends KindTest {
/**
* {@inheritDoc}
*
@@ -855,7 +1108,7 @@
}
}
- public static class ProcessingInstructionTest implements KindTest {
+ public static class ProcessingInstructionTest extends KindTest {
private final String nameOrStringLiteral;
public ProcessingInstructionTest( String nameOrStringLiteral ) {
@@ -895,7 +1148,7 @@
}
}
- public static class DocumentTest implements KindTest {
+ public static class DocumentTest extends KindTest {
private KindTest elementOrSchemaElementTest;
public DocumentTest( ElementTest elementTest ) {
@@ -948,7 +1201,7 @@
}
}
- public static class AttributeTest implements KindTest {
+ public static class AttributeTest extends KindTest {
private final NameTest attributeNameOrWildcard;
private final NameTest typeName;
@@ -999,7 +1252,7 @@
}
}
- public static class ElementTest implements KindTest {
+ public static class ElementTest extends KindTest {
private final NameTest elementNameOrWildcard;
private final NameTest typeName;
@@ -1050,7 +1303,7 @@
}
}
- public static class SchemaElementTest implements KindTest {
+ public static class SchemaElementTest extends KindTest {
private final NameTest elementDeclarationName;
public SchemaElementTest( NameTest elementDeclarationName ) {
@@ -1090,7 +1343,7 @@
}
}
- public static class SchemaAttributeTest implements KindTest {
+ public static class SchemaAttributeTest extends KindTest {
private final NameTest attributeDeclarationName;
public SchemaAttributeTest( NameTest attributeDeclarationName ) {
Modified: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/xpath/XPathParser.java
===================================================================
--- trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/xpath/XPathParser.java 2009-10-03
04:07:41 UTC (rev 1285)
+++ trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/xpath/XPathParser.java 2009-10-03
04:09:29 UTC (rev 1286)
@@ -39,8 +39,6 @@
import org.jboss.dna.graph.property.ValueFactory;
import org.jboss.dna.graph.property.ValueFormatException;
import org.jboss.dna.graph.query.model.Operator;
-import org.jboss.dna.graph.query.model.QueryCommand;
-import org.jboss.dna.graph.query.parse.QueryParser;
import org.jboss.dna.jcr.xpath.XPath.Add;
import org.jboss.dna.jcr.xpath.XPath.And;
import org.jboss.dna.jcr.xpath.XPath.AnyKindTest;
@@ -54,8 +52,10 @@
import org.jboss.dna.jcr.xpath.XPath.DescendantOrSelf;
import org.jboss.dna.jcr.xpath.XPath.DocumentTest;
import org.jboss.dna.jcr.xpath.XPath.ElementTest;
+import org.jboss.dna.jcr.xpath.XPath.Except;
import org.jboss.dna.jcr.xpath.XPath.FilterStep;
import org.jboss.dna.jcr.xpath.XPath.FunctionCall;
+import org.jboss.dna.jcr.xpath.XPath.Intersect;
import org.jboss.dna.jcr.xpath.XPath.KindTest;
import org.jboss.dna.jcr.xpath.XPath.Literal;
import org.jboss.dna.jcr.xpath.XPath.NameTest;
@@ -75,602 +75,606 @@
import org.jboss.dna.jcr.xpath.XPath.Union;
/**
- *
+ * A component that parses an XPath query string and creates an abstract syntax tree
representation. The supported grammar is
+ * defined by JCR 1.0, and is a subset of what is allowed by the W3C XPath 2.0
specification.
*/
-public class XPathParser implements QueryParser {
+public class XPathParser {
+ private final ExecutionContext context;
- /**
- * {@inheritDoc}
- *
- * @see org.jboss.dna.graph.query.parse.QueryParser#parseQuery(java.lang.String,
org.jboss.dna.graph.ExecutionContext)
- */
- public QueryCommand parseQuery( String query,
- ExecutionContext context ) {
+ public XPathParser( ExecutionContext context ) {
+ this.context = context;
+ }
+
+ public Component parseXPath( String xpath ) {
Tokenizer tokenizer = new XPathTokenizer(false); // skip comments
- TokenStream tokens = new TokenStream(query, tokenizer, true).start(); // case
sensitive!!
- Component result = new Parser(context).parseXPath(tokens);
- System.out.println(query);
- System.out.println(" --> " + result);
- return null;
+ TokenStream tokens = new TokenStream(xpath, tokenizer, true).start(); // case
sensitive!!
+ return parseXPath(tokens);
}
- protected static class Parser {
- private final ExecutionContext context;
+ protected Component parseXPath( TokenStream tokens ) {
+ return parseExpr(tokens);
+ }
- protected Parser( ExecutionContext context ) {
- this.context = context;
+ protected Component parseExpr( TokenStream tokens ) {
+ Component result = parseExprSingle(tokens);
+ if (tokens.matches(',')) {
+ throw new ParsingException(tokens.nextPosition(), "Multiple XPath
expressions are not supported");
}
+ return result;
+ }
- protected Component parseXPath( TokenStream tokens ) {
- return parseExpr(tokens);
+ protected Component parseExprSingle( TokenStream tokens ) {
+ if (tokens.matches("for", "$", ANY_VALUE, "IN")) {
+ throw new ParsingException(tokens.nextPosition(), "XPath 'for'
expressions are not supported");
}
+ if (tokens.matches("some", "$", ANY_VALUE, "IN"))
{
+ throw new ParsingException(tokens.nextPosition(), "XPath 'some'
expressions are not supported");
+ }
+ if (tokens.matches("every", "$", ANY_VALUE, "IN"))
{
+ throw new ParsingException(tokens.nextPosition(), "XPath 'every'
expressions are not supported");
+ }
+ if (tokens.matches("if", "(", ANY_VALUE, "IN")) {
+ throw new ParsingException(tokens.nextPosition(), "XPath if-then-else
expressions are not supported");
+ }
+ return parseOrExpr(tokens);
+ }
- protected Component parseExpr( TokenStream tokens ) {
- Component result = parseExprSingle(tokens);
- if (tokens.matches(',')) {
- throw new ParsingException(tokens.nextPosition(), "Multiple XPath
expressions are not supported");
- }
- return result;
+ protected Component parseOrExpr( TokenStream tokens ) {
+ Component result = parseAndExpr(tokens);
+ while (tokens.canConsume("or")) {
+ result = new Or(result, parseInstanceofExpr(tokens));
}
+ return result;
+ }
- protected Component parseExprSingle( TokenStream tokens ) {
- if (tokens.matches("for", "$", ANY_VALUE,
"IN")) {
- throw new ParsingException(tokens.nextPosition(), "XPath
'for' expressions are not supported");
- }
- if (tokens.matches("some", "$", ANY_VALUE,
"IN")) {
- throw new ParsingException(tokens.nextPosition(), "XPath
'some' expressions are not supported");
- }
- if (tokens.matches("every", "$", ANY_VALUE,
"IN")) {
- throw new ParsingException(tokens.nextPosition(), "XPath
'every' expressions are not supported");
- }
- if (tokens.matches("if", "(", ANY_VALUE, "IN"))
{
- throw new ParsingException(tokens.nextPosition(), "XPath
if-then-else expressions are not supported");
- }
- return parseOrExpr(tokens);
+ protected Component parseAndExpr( TokenStream tokens ) {
+ Component result = parseInstanceofExpr(tokens);
+ while (tokens.canConsume("and")) {
+ result = new And(result, parseInstanceofExpr(tokens));
}
+ return result;
+ }
- protected Component parseOrExpr( TokenStream tokens ) {
- Component result = parseAndExpr(tokens);
- while (tokens.canConsume("or")) {
- result = new Or(result, parseInstanceofExpr(tokens));
- }
- return result;
+ protected Component parseInstanceofExpr( TokenStream tokens ) {
+ Component result = parseTreatExpr(tokens);
+ if (tokens.matches("instance", "of")) {
+ throw new ParsingException(tokens.nextPosition(), "XPath 'instance
of' expressions are not supported");
}
+ return result;
+ }
- protected Component parseAndExpr( TokenStream tokens ) {
- Component result = parseInstanceofExpr(tokens);
- while (tokens.canConsume("and")) {
- result = new And(result, parseInstanceofExpr(tokens));
- }
- return result;
+ protected Component parseTreatExpr( TokenStream tokens ) {
+ Component result = parseCastableExpr(tokens);
+ if (tokens.matches("treat", "as")) {
+ throw new ParsingException(tokens.nextPosition(), "XPath 'treat
as' expressions are not supported");
}
+ return result;
+ }
- protected Component parseInstanceofExpr( TokenStream tokens ) {
- Component result = parseTreatExpr(tokens);
- if (tokens.matches("instance", "of")) {
- throw new ParsingException(tokens.nextPosition(), "XPath
'instance of' expressions are not supported");
- }
- return result;
+ protected Component parseCastableExpr( TokenStream tokens ) {
+ Component result = parseCastExpr(tokens);
+ if (tokens.matches("castable", "as")) {
+ throw new ParsingException(tokens.nextPosition(), "XPath 'castable
as' expressions are not supported");
}
+ return result;
+ }
- protected Component parseTreatExpr( TokenStream tokens ) {
- Component result = parseCastableExpr(tokens);
- if (tokens.matches("treat", "as")) {
- throw new ParsingException(tokens.nextPosition(), "XPath 'treat
as' expressions are not supported");
- }
- return result;
+ protected Component parseCastExpr( TokenStream tokens ) {
+ Component result = parseComparisonExpr(tokens);
+ if (tokens.matches("cast", "as")) {
+ throw new ParsingException(tokens.nextPosition(), "XPath 'cast
as' expressions are not supported");
}
+ return result;
+ }
- protected Component parseCastableExpr( TokenStream tokens ) {
- Component result = parseCastExpr(tokens);
- if (tokens.matches("castable", "as")) {
- throw new ParsingException(tokens.nextPosition(), "XPath
'castable as' expressions are not supported");
- }
- return result;
+ protected Component parseComparisonExpr( TokenStream tokens ) {
+ Component result = parseRangeExpr(tokens);
+ // General comparison is optional ...
+ Operator operator = parseGeneralComp(tokens);
+ if (operator == null) parseValueComp(tokens);
+ if (operator != null) {
+ return new Comparison(result, operator, parseRangeExpr(tokens));
}
+ NodeComparisonOperator nodeComp = parseNodeComp(tokens);
+ if (nodeComp != null) {
+ return new NodeComparison(result, nodeComp, parseRangeExpr(tokens));
+ }
+ return result;
+ }
- protected Component parseCastExpr( TokenStream tokens ) {
- Component result = parseComparisonExpr(tokens);
- if (tokens.matches("cast", "as")) {
- throw new ParsingException(tokens.nextPosition(), "XPath 'cast
as' expressions are not supported");
- }
- return result;
+ protected Component parseValueComp( TokenStream tokens ) {
+ if (tokens.matchesAnyOf("eq", "ne", "lt",
"le", "gt")) {
+ throw new ParsingException(tokens.nextPosition(),
+ "XPath value comparisons using 'eq',
'ne', 'lt', 'le', or 'gt' are not supported");
}
+ return null;
+ }
- protected Component parseComparisonExpr( TokenStream tokens ) {
- Component result = parseRangeExpr(tokens);
- // General comparison is optional ...
- Operator operator = parseGeneralComp(tokens);
- if (operator == null) parseValueComp(tokens);
- if (operator != null) {
- return new Comparison(result, operator, parseRangeExpr(tokens));
- }
- NodeComparisonOperator nodeComp = parseNodeComp(tokens);
- if (nodeComp != null) {
- return new NodeComparison(result, nodeComp, parseRangeExpr(tokens));
- }
- return result;
+ protected NodeComparisonOperator parseNodeComp( TokenStream tokens ) {
+ if (tokens.matches("is") || tokens.matches("<",
"<") || tokens.matches(">", ">")) {
+ throw new ParsingException(tokens.nextPosition(), "XPath 'is',
'<<' and '>>' expressions are not supported");
}
+ return null;
+ }
- protected Component parseValueComp( TokenStream tokens ) {
- if (tokens.matchesAnyOf("eq", "ne", "lt",
"le", "gt")) {
- throw new ParsingException(tokens.nextPosition(),
- "XPath value comparisons using
'eq', 'ne', 'lt', 'le', or 'gt' are not
supported");
- }
- return null;
+ protected Component parseRangeExpr( TokenStream tokens ) {
+ Component result = parseAdditiveExpr(tokens);
+ if (tokens.matches("to")) {
+ throw new ParsingException(tokens.nextPosition(), "XPath range
expressions with 'to' are not supported");
}
+ return result;
+ }
- protected NodeComparisonOperator parseNodeComp( TokenStream tokens ) {
- if (tokens.matches("is") || tokens.matches("<",
"<") || tokens.matches(">", ">")) {
- throw new ParsingException(tokens.nextPosition(), "XPath
'is', '<<' and '>>' expressions are not
supported");
+ protected Component parseAdditiveExpr( TokenStream tokens ) {
+ Component result = parseMultiplicativeExpr(tokens);
+ while (true) {
+ if (tokens.canConsume("+")) {
+ result = new Add(result, parseMultiplicativeExpr(tokens));
+ } else if (tokens.canConsume("-")) {
+ result = new Subtract(result, parseMultiplicativeExpr(tokens));
+ } else {
+ break; // no more additions
}
- return null;
}
+ return result;
+ }
- protected Component parseRangeExpr( TokenStream tokens ) {
- Component result = parseAdditiveExpr(tokens);
- if (tokens.matches("to")) {
- throw new ParsingException(tokens.nextPosition(), "XPath range
expressions with 'to' are not supported");
- }
- return result;
+ protected Component parseMultiplicativeExpr( TokenStream tokens ) {
+ Component result = parseUnaryExpr(tokens);
+ if (tokens.matchesAnyOf("+", "div", "idiv",
"mod")) {
+ throw new ParsingException(tokens.nextPosition(),
+ "XPath multiplicative expressions using
'+', 'div', 'idiv', or 'mod' are not supported");
}
+ return result;
+ }
- protected Component parseAdditiveExpr( TokenStream tokens ) {
- Component result = parseMultiplicativeExpr(tokens);
- while (true) {
- if (tokens.canConsume("+")) {
- result = new Add(result, parseMultiplicativeExpr(tokens));
- } else if (tokens.canConsume("-")) {
- result = new Subtract(result, parseMultiplicativeExpr(tokens));
- } else {
- break; // no more additions
- }
- }
- return result;
+ protected Component parseUnaryExpr( TokenStream tokens ) {
+ boolean negative = false;
+ // Technically more than one +/- are allowed by the spec
+ while (tokens.matchesAnyOf("+", "-")) {
+ if (tokens.canConsume("-")) negative = true;
+ tokens.canConsume("+");
}
+ Component result = parseUnionExpr(tokens);
+ return negative ? new Negation(result) : result;
+ }
- protected Component parseMultiplicativeExpr( TokenStream tokens ) {
- Component result = parseUnaryExpr(tokens);
- if (tokens.matchesAnyOf("+", "div", "idiv",
"mod")) {
- throw new ParsingException(tokens.nextPosition(),
- "XPath multiplicative expressions using
'+', 'div', 'idiv', or 'mod' are not supported");
+ protected Component parseUnionExpr( TokenStream tokens ) {
+ Component result = parseIntersectExceptExpr(tokens);
+ while (true) {
+ if (tokens.canConsumeAnyOf("union", "|")) {
+ result = new Union(result, parseIntersectExceptExpr(tokens));
+ } else {
+ break; // no more
}
- return result;
}
+ return result;
+ }
- protected Component parseUnaryExpr( TokenStream tokens ) {
- boolean negative = false;
- // Technically more than one +/- are allowed by the spec
- while (tokens.matchesAnyOf("+", "-")) {
- if (tokens.canConsume("-")) negative = true;
- tokens.canConsume("+");
+ protected Component parseIntersectExceptExpr( TokenStream tokens ) {
+ Component result = parseValueExpr(tokens);
+ while (true) {
+ if (tokens.canConsumeAnyOf("intersect")) {
+ result = new Intersect(result, parseValueExpr(tokens));
+ } else if (tokens.canConsumeAnyOf("except")) {
+ result = new Except(result, parseValueExpr(tokens));
+ } else {
+ break; // no more
}
- Component result = parseUnionExpr(tokens);
- return negative ? new Negation(result) : result;
}
+ return result;
+ }
- protected Component parseUnionExpr( TokenStream tokens ) {
- Component result = parseIntersectExceptExpr(tokens);
- while (true) {
- if (tokens.canConsumeAnyOf("union", "|")) {
- result = new Union(result, parseIntersectExceptExpr(tokens));
- } else {
- break; // no more
+ protected Component parseValueExpr( TokenStream tokens ) {
+ return parsePathExpr(tokens);
+ }
+
+ protected PathExpression parsePathExpr( TokenStream tokens ) {
+ boolean relative = true;
+ boolean prependDependentOrSelf = false;
+ if (tokens.canConsume('/')) {
+ if (tokens.canConsume('/')) {
+ if (!tokens.hasNext()) {
+ // See
http://www.w3.org/XML/2007/qt-errata/xpath20-errata.html#E3
+ throw new ParsingException(tokens.previousPosition(),
"'//' is not a valid XPath expression");
}
+ prependDependentOrSelf = true;
}
- return result;
+ relative = false;
}
-
- protected Component parseIntersectExceptExpr( TokenStream tokens ) {
- Component result = parseValueExpr(tokens);
- if (tokens.matchesAnyOf("intersect", "except")) {
- throw new ParsingException(tokens.nextPosition(),
- "XPath multiplicative expressions using
'+', 'div', 'idiv', or 'mod' are not supported");
- }
- return result;
+ PathExpression result = new PathExpression(relative,
parseRelativePathExpr(tokens).getSteps());
+ if (prependDependentOrSelf) {
+ result.getSteps().add(0, new DescendantOrSelf());
}
+ return result;
+ }
- protected Component parseValueExpr( TokenStream tokens ) {
- return parsePathExpr(tokens);
- }
-
- protected PathExpression parsePathExpr( TokenStream tokens ) {
- boolean relative = true;
- boolean prependDependentOrSelf = false;
+ protected PathExpression parseRelativePathExpr( TokenStream tokens ) {
+ List<StepExpression> steps = new ArrayList<StepExpression>();
+ steps.add(parseStepExpr(tokens));
+ while (tokens.canConsume('/')) {
if (tokens.canConsume('/')) {
- if (tokens.canConsume('/')) {
- if (!tokens.hasNext()) {
- // See
http://www.w3.org/XML/2007/qt-errata/xpath20-errata.html#E3
- throw new ParsingException(tokens.previousPosition(),
"'//' is not a valid XPath expression");
- }
- prependDependentOrSelf = true;
- }
- relative = false;
+ steps.add(new DescendantOrSelf());
}
- PathExpression result = new PathExpression(relative,
parseRelativePathExpr(tokens).getSteps());
- if (prependDependentOrSelf) {
- result.getSteps().add(0, new DescendantOrSelf());
+ if (tokens.hasNext()) {
+ steps.add(parseStepExpr(tokens));
}
- return result;
}
+ return new PathExpression(true, steps);
+ }
- protected PathExpression parseRelativePathExpr( TokenStream tokens ) {
- List<StepExpression> steps = new ArrayList<StepExpression>();
- steps.add(parseStepExpr(tokens));
- while (tokens.canConsume('/')) {
- if (tokens.canConsume('/')) {
- steps.add(new DescendantOrSelf());
- }
- if (tokens.hasNext()) {
- steps.add(parseStepExpr(tokens));
- }
- }
- return new PathExpression(true, steps);
+ protected StepExpression parseStepExpr( TokenStream tokens ) {
+ KindTest kindTest = parseKindTest(tokens);
+ if (kindTest != null) {
+ // Now parse the predicates ...
+ List<Component> predicates = parsePredicates(tokens);
+ return new AxisStep(kindTest, predicates);
}
-
- protected StepExpression parseStepExpr( TokenStream tokens ) {
- if (tokens.matches('(') || tokens.matches('.') ||
tokens.matches(XPathTokenizer.QUOTED_STRING)
- || tokens.matches(ANY_VALUE, "(") || tokens.matches(ANY_VALUE,
":", ANY_VALUE, "(")) {
- // We know its a filter expression (though literals don't fit this
pattern) ...
- return parseFilterExpr(tokens);
- }
- AxisStep result = parseAxisStep(tokens);
- if (result != null) return result;
- // It must be the remaining kind of filter expression ...
+ if (tokens.matches('(') || tokens.matches('.') ||
tokens.matches(XPathTokenizer.QUOTED_STRING)
+ || tokens.matches(ANY_VALUE, "(") || tokens.matches(ANY_VALUE,
":", ANY_VALUE, "(")) {
+ // We know its a filter expression (though literals don't fit this
pattern) ...
return parseFilterExpr(tokens);
}
+ AxisStep result = parseAxisStep(tokens);
+ if (result != null) return result;
+ // It must be the remaining kind of filter expression ...
+ return parseFilterExpr(tokens);
+ }
- protected AxisStep parseAxisStep( TokenStream tokens ) {
- NodeTest nodeTest = null;
- if (tokens.canConsume('@')) {
- // Abbreviated forward step with an attribute...
- nodeTest = new AttributeNameTest(parseNodeTest(tokens));
- } else if (tokens.matches('*')) {
- // Abbreviated forward step with an wildcard element ...
- nodeTest = parseNodeTest(tokens);
+ protected AxisStep parseAxisStep( TokenStream tokens ) {
+ NodeTest nodeTest = null;
+ if (tokens.canConsume('@')) {
+ // Abbreviated forward step with an attribute...
+ nodeTest = new AttributeNameTest(parseNameTest(tokens));
+ } else if (tokens.matches('*')) {
+ // Abbreviated forward step with an wildcard element ...
+ nodeTest = parseNodeTest(tokens);
- } else if (tokens.matches("child", ":", ":") ||
tokens.matches("attribute", ":", ":")
- || tokens.matches("self", ":", ":")
|| tokens.matches("descendant", ":", ":")
- || tokens.matches("descendant-or-self", ":",
":") || tokens.matches("following-sibling", ":",
":")
- || tokens.matches("following", ":",
":") || tokens.matches("namespace", ":", ":")) {
- // No non-abbreviated forward steps allowed
- throw new ParsingException(
- tokens.nextPosition(),
- "XPath non-abbreviated forward steps
(e.g., 'child::', 'attribute::', 'self::', 'descendant::',
'descendant-or-self::', 'following-sibling::', 'following::', or
'namespace::') are not supported");
- } else if (tokens.matches("..")) {
- // No abbreviated reverse steps allowed ...
- throw new ParsingException(tokens.nextPosition(),
- "XPath abbreviated reverse steps (e.g.,
'..') are not supported");
- } else if (tokens.matches("parent", ":", ":")
|| tokens.matches("ancestor-or-self", ":", ":")
- || tokens.matches("preceding-sibling", ":",
":") || tokens.matches("preceding", ":", ":")
- || tokens.matches("ancestor", ":",
":")) {
- // No non-abbreviated reverse steps allowed ...
- throw new ParsingException(
- tokens.nextPosition(),
- "XPath non-abbreviated reverse steps
(e.g., 'parent::', 'ancestor::', 'ancestor-or-self::',
'preceding-or-sibling::', or 'preceding::') are not supported");
- } else if (tokens.matches(ANY_VALUE, ":", ANY_VALUE)
- && tokens.matches(XPathTokenizer.NAME,
XPathTokenizer.SYMBOL, XPathTokenizer.NAME)) {
- // This is probably a forward step with a (qualified) name test ...
- nodeTest = parseQName(tokens);
- } else if (tokens.matches(XPathTokenizer.NAME)) {
- // This is probably a forward step with an unqualified name test ...
- nodeTest = parseNodeTest(tokens);
- } else {
- return null;
- }
-
- // Parse the predicates
- List<Component> predicates = parsePredicates(tokens);
- return new AxisStep(nodeTest, predicates);
+ } else if (tokens.matches("child", ":", ":") ||
tokens.matches("attribute", ":", ":") ||
tokens.matches("self", ":", ":")
+ || tokens.matches("descendant", ":",
":") || tokens.matches("descendant-or-self", ":",
":")
+ || tokens.matches("following-sibling", ":",
":") || tokens.matches("following", ":", ":")
+ || tokens.matches("namespace", ":",
":")) {
+ // No non-abbreviated forward steps allowed
+ throw new ParsingException(
+ tokens.nextPosition(),
+ "XPath non-abbreviated forward steps (e.g.,
'child::', 'attribute::', 'self::', 'descendant::',
'descendant-or-self::', 'following-sibling::', 'following::', or
'namespace::') are not supported");
+ } else if (tokens.matches("..")) {
+ // No abbreviated reverse steps allowed ...
+ throw new ParsingException(tokens.nextPosition(), "XPath abbreviated
reverse steps (e.g., '..') are not supported");
+ } else if (tokens.matches("parent", ":", ":") ||
tokens.matches("ancestor-or-self", ":", ":")
+ || tokens.matches("preceding-sibling", ":",
":") || tokens.matches("preceding", ":", ":")
+ || tokens.matches("ancestor", ":", ":"))
{
+ // No non-abbreviated reverse steps allowed ...
+ throw new ParsingException(
+ tokens.nextPosition(),
+ "XPath non-abbreviated reverse steps (e.g.,
'parent::', 'ancestor::', 'ancestor-or-self::',
'preceding-or-sibling::', or 'preceding::') are not supported");
+ } else if (tokens.matches(ANY_VALUE, ":", ANY_VALUE)
+ && tokens.matches(XPathTokenizer.NAME, XPathTokenizer.SYMBOL,
XPathTokenizer.NAME)) {
+ // This is probably a forward step with a (qualified) name test ...
+ nodeTest = parseQName(tokens);
+ } else if (tokens.matches(XPathTokenizer.NAME)) {
+ // This is probably a forward step with an unqualified name test ...
+ nodeTest = parseNodeTest(tokens);
+ } else {
+ return null;
}
- protected List<Component> parsePredicates( TokenStream tokens ) {
- List<Component> predicates = new ArrayList<Component>();
- while (tokens.canConsume('[')) {
- predicates.add(parseExpr(tokens));
- tokens.consume(']');
- }
- return predicates;
- }
+ // Parse the predicates
+ List<Component> predicates = parsePredicates(tokens);
+ return new AxisStep(nodeTest, predicates);
+ }
- protected FilterStep parseFilterExpr( TokenStream tokens ) {
- Component primaryExpr = parsePrimaryExpr(tokens);
- List<Component> predicates = parsePredicates(tokens);
- return new FilterStep(primaryExpr, predicates);
+ protected List<Component> parsePredicates( TokenStream tokens ) {
+ List<Component> predicates = new ArrayList<Component>();
+ while (tokens.canConsume('[')) {
+ predicates.add(collapse(parseExpr(tokens)));
+ tokens.consume(']');
}
+ return predicates;
+ }
- protected Component parsePrimaryExpr( TokenStream tokens ) {
- if (tokens.matches('(')) {
- return parseParenthesizedExpr(tokens);
- }
- if (tokens.matches('.')) {
- return parseContextItemExpr(tokens);
- }
- if (tokens.matches(XPathTokenizer.QUOTED_STRING)) {
- return parseStringLiteral(tokens);
- }
- if (tokens.matches(ANY_VALUE, "(") || tokens.matches(ANY_VALUE,
":", ANY_VALUE, "(")) {
- return parseFunctionCall(tokens);
- }
- return parseNumericLiteral(tokens);
- }
+ protected FilterStep parseFilterExpr( TokenStream tokens ) {
+ Component primaryExpr = parsePrimaryExpr(tokens);
+ List<Component> predicates = parsePredicates(tokens);
+ return new FilterStep(primaryExpr, predicates);
+ }
- protected ContextItem parseContextItemExpr( TokenStream tokens ) {
- tokens.consume('.');
- return new ContextItem();
+ protected Component parsePrimaryExpr( TokenStream tokens ) {
+ if (tokens.matches('(')) {
+ return parseParenthesizedExpr(tokens);
}
+ if (tokens.matches('.')) {
+ return parseContextItemExpr(tokens);
+ }
+ if (tokens.matches(XPathTokenizer.QUOTED_STRING)) {
+ return parseStringLiteral(tokens);
+ }
+ if (tokens.matches(ANY_VALUE, "(") || tokens.matches(ANY_VALUE,
":", ANY_VALUE, "(")) {
+ return parseFunctionCall(tokens);
+ }
+ return parseNumericLiteral(tokens);
+ }
- protected ParenthesizedExpression parseParenthesizedExpr( TokenStream tokens ) {
- tokens.consume('(');
- if (tokens.canConsume(')')) {
- return new ParenthesizedExpression();
- }
- Component expr = parseExpr(tokens);
- tokens.consume(')');
- return new ParenthesizedExpression(expr);
+ protected ContextItem parseContextItemExpr( TokenStream tokens ) {
+ tokens.consume('.');
+ return new ContextItem();
+ }
+
+ protected ParenthesizedExpression parseParenthesizedExpr( TokenStream tokens ) {
+ tokens.consume('(');
+ if (tokens.canConsume(')')) {
+ return new ParenthesizedExpression();
}
+ Component expr = collapse(parseExpr(tokens));
+ tokens.consume(')');
+ return new ParenthesizedExpression(expr);
+ }
- protected Literal parseNumericLiteral( TokenStream tokens ) {
- Position pos = tokens.nextPosition();
- String sign = "";
- if (tokens.canConsume('-')) sign = "-";
- else if (tokens.canConsume('+')) sign = "";
+ protected Literal parseNumericLiteral( TokenStream tokens ) {
+ Position pos = tokens.nextPosition();
+ String sign = "";
+ if (tokens.canConsume('-')) sign = "-";
+ else if (tokens.canConsume('+')) sign = "";
- // Try to parse this value as a number ...
- ValueFactory<String> stringFactory =
context.getValueFactories().getStringFactory();
- String number = tokens.consume();
- if (number.indexOf(".") != -1) {
- String value = sign + number;
- if (value.endsWith("e") && (tokens.matches('+')
|| tokens.matches('-'))) {
- // There's more to the number ...
- value = value + tokens.consume() + tokens.consume(); // +/-EXP
- }
- try {
- // Convert to a double and then back to a string to get canonical
form ...
- String canonical =
stringFactory.create(context.getValueFactories().getDoubleFactory().create(value));
- return new Literal(canonical);
- } catch (ValueFormatException e) {
- String msg =
GraphI18n.expectingLiteralAndUnableToParseAsDouble.text(value, pos.getLine(),
pos.getColumn());
- throw new ParsingException(pos, msg);
- }
+ // Try to parse this value as a number ...
+ ValueFactory<String> stringFactory =
context.getValueFactories().getStringFactory();
+ String number = tokens.consume();
+ if (number.indexOf(".") != -1) {
+ String value = sign + number;
+ if (value.endsWith("e") && (tokens.matches('+') ||
tokens.matches('-'))) {
+ // There's more to the number ...
+ value = value + tokens.consume() + tokens.consume(); // +/-EXP
}
- // try to parse an a long ...
- String value = sign + number;
try {
- // Convert to a long and then back to a string to get canonical form ...
- String canonical =
stringFactory.create(context.getValueFactories().getLongFactory().create(value));
+ // Convert to a double and then back to a string to get canonical form
...
+ String canonical =
stringFactory.create(context.getValueFactories().getDoubleFactory().create(value));
return new Literal(canonical);
} catch (ValueFormatException e) {
- String msg = GraphI18n.expectingLiteralAndUnableToParseAsLong.text(value,
pos.getLine(), pos.getColumn());
+ String msg =
GraphI18n.expectingLiteralAndUnableToParseAsDouble.text(value, pos.getLine(),
pos.getColumn());
throw new ParsingException(pos, msg);
}
}
-
- protected Literal parseStringLiteral( TokenStream tokens ) {
- boolean removeQuotes = tokens.matches(XPathTokenizer.QUOTED_STRING);
- String value = tokens.consume();
- if (removeQuotes) value = removeQuotes(value);
- return new Literal(value);
+ // try to parse an a long ...
+ String value = sign + number;
+ try {
+ // Convert to a long and then back to a string to get canonical form ...
+ String canonical =
stringFactory.create(context.getValueFactories().getLongFactory().create(value));
+ return new Literal(canonical);
+ } catch (ValueFormatException e) {
+ String msg = GraphI18n.expectingLiteralAndUnableToParseAsLong.text(value,
pos.getLine(), pos.getColumn());
+ throw new ParsingException(pos, msg);
}
+ }
- protected FunctionCall parseFunctionCall( TokenStream tokens ) {
- NameTest name = parseQName(tokens);
- tokens.consume("(");
- List<Component> args = new ArrayList<Component>();
- if (!tokens.matches(')')) {
- do {
- args.add(parseExprSingle(tokens));
- } while (tokens.canConsume(","));
- tokens.consume(")");
- }
- return new FunctionCall(name, args);
- }
+ protected Literal parseStringLiteral( TokenStream tokens ) {
+ boolean removeQuotes = tokens.matches(XPathTokenizer.QUOTED_STRING);
+ String value = tokens.consume();
+ if (removeQuotes) value = removeQuotes(value);
+ return new Literal(value);
+ }
- protected Operator parseGeneralComp( TokenStream tokens ) {
- if (tokens.canConsume("!", "=")) return
Operator.NOT_EQUAL_TO;
- if (tokens.canConsume("=")) return Operator.EQUAL_TO;
- if (tokens.canConsume("<", "=")) return
Operator.LESS_THAN_OR_EQUAL_TO;
- if (tokens.canConsume(">", "=")) return
Operator.GREATER_THAN_OR_EQUAL_TO;
- if (tokens.canConsume("<")) return Operator.LESS_THAN;
- if (tokens.canConsume(">")) return Operator.GREATER_THAN;
- return null;
+ protected FunctionCall parseFunctionCall( TokenStream tokens ) {
+ NameTest name = parseQName(tokens);
+ tokens.consume("(");
+ List<Component> args = new ArrayList<Component>();
+ if (!tokens.matches(')')) {
+ do {
+ args.add(collapse(parseExprSingle(tokens)));
+ } while (tokens.canConsume(","));
+ tokens.consume(")");
}
+ return new FunctionCall(name, args);
+ }
- protected NodeTest parseNodeTest( TokenStream tokens ) {
- KindTest kind = parseKindTest(tokens);
- if (kind != null) return kind;
- return parseNameTest(tokens);
- }
+ protected Operator parseGeneralComp( TokenStream tokens ) {
+ if (tokens.canConsume("!", "=")) return
Operator.NOT_EQUAL_TO;
+ if (tokens.canConsume("=")) return Operator.EQUAL_TO;
+ if (tokens.canConsume("<", "=")) return
Operator.LESS_THAN_OR_EQUAL_TO;
+ if (tokens.canConsume(">", "=")) return
Operator.GREATER_THAN_OR_EQUAL_TO;
+ if (tokens.canConsume("<")) return Operator.LESS_THAN;
+ if (tokens.canConsume(">")) return Operator.GREATER_THAN;
+ return null;
+ }
- protected NameTest parseNameTest( TokenStream tokens ) {
- NameTest wildcard = parseWildcard(tokens);
- if (wildcard != null) return wildcard;
- return parseQName(tokens);
- }
+ protected NodeTest parseNodeTest( TokenStream tokens ) {
+ KindTest kind = parseKindTest(tokens);
+ if (kind != null) return kind;
+ return parseNameTest(tokens);
+ }
- protected NameTest parseQName( TokenStream tokens ) {
- String firstPart = parseNCName(tokens);
- if (tokens.canConsume(':')) {
- String secondPart = tokens.consume();
- return new NameTest(firstPart, secondPart);
- }
- return new NameTest(null, firstPart);
+ protected NameTest parseNameTest( TokenStream tokens ) {
+ NameTest wildcard = parseWildcard(tokens);
+ if (wildcard != null) return wildcard;
+ return parseQName(tokens);
+ }
+
+ protected NameTest parseQName( TokenStream tokens ) {
+ String firstPart = parseNCName(tokens);
+ if (tokens.canConsume(':')) {
+ String secondPart = tokens.consume();
+ return new NameTest(firstPart, secondPart);
}
+ return new NameTest(null, firstPart);
+ }
- protected String parseNCName( TokenStream tokens ) {
- String name = tokens.consume();
- if (!XmlCharacters.isValidNcName(name)) {
- throw new ParsingException(tokens.previousPosition(), "Expected
valid NCName but found " + name);
- }
- return name;
+ protected String parseNCName( TokenStream tokens ) {
+ String name = tokens.consume();
+ if (!XmlCharacters.isValidNcName(name)) {
+ throw new ParsingException(tokens.previousPosition(), "Expected valid
NCName but found " + name);
}
+ return name;
+ }
- protected NameTest parseWildcard( TokenStream tokens ) {
- if (tokens.canConsume('*')) {
- if (tokens.canConsume(':')) {
- if (tokens.canConsume('*')) {
- return new NameTest(null, null);
- }
- String localName = tokens.consume();
- return new NameTest(null, localName);
+ protected NameTest parseWildcard( TokenStream tokens ) {
+ if (tokens.canConsume('*')) {
+ if (tokens.canConsume(':')) {
+ if (tokens.canConsume('*')) {
+ return new NameTest(null, null);
}
- return new NameTest(null, null);
+ String localName = tokens.consume();
+ return new NameTest(null, localName);
}
- if (tokens.matches(XPathTokenizer.NAME, XPathTokenizer.SYMBOL,
XPathTokenizer.SYMBOL)
- && tokens.matches(TokenStream.ANY_VALUE, ":",
"*")) {
- String prefix = tokens.consume();
- tokens.consume(':');
- tokens.consume('*');
- return new NameTest(prefix, null);
- }
- return null;
+ return new NameTest(null, null);
}
-
- protected NameTest parseItemType( TokenStream tokens ) {
- return parseQName(tokens);
+ if (tokens.matches(XPathTokenizer.NAME, XPathTokenizer.SYMBOL,
XPathTokenizer.SYMBOL)
+ && tokens.matches(TokenStream.ANY_VALUE, ":",
"*")) {
+ String prefix = tokens.consume();
+ tokens.consume(':');
+ tokens.consume('*');
+ return new NameTest(prefix, null);
}
+ return null;
+ }
- protected NameTest parseAtomicType( TokenStream tokens ) {
- return parseQName(tokens);
- }
+ protected NameTest parseItemType( TokenStream tokens ) {
+ return parseQName(tokens);
+ }
- protected KindTest parseKindTest( TokenStream tokens ) {
- KindTest result = parseAnyKindTest(tokens);
- if (result == null) result = parseDocumentTest(tokens);
- if (result == null) result = parseElementTest(tokens);
- if (result == null) result = parseAttributeTest(tokens);
- if (result == null) result = parseSchemaElementTest(tokens);
- if (result == null) result = parseSchemaAttributeTest(tokens);
- if (result == null) result = parsePITest(tokens);
- if (result == null) result = parseCommentTest(tokens);
- if (result == null) result = parseTextTest(tokens);
- return result;
- }
+ protected NameTest parseAtomicType( TokenStream tokens ) {
+ return parseQName(tokens);
+ }
- protected AnyKindTest parseAnyKindTest( TokenStream tokens ) {
- if (tokens.canConsume("node", "(", ")")) {
- return new AnyKindTest();
- }
- return null;
- }
+ protected KindTest parseKindTest( TokenStream tokens ) {
+ KindTest result = parseAnyKindTest(tokens);
+ if (result == null) result = parseDocumentTest(tokens);
+ if (result == null) result = parseElementTest(tokens);
+ if (result == null) result = parseAttributeTest(tokens);
+ if (result == null) result = parseSchemaElementTest(tokens);
+ if (result == null) result = parseSchemaAttributeTest(tokens);
+ if (result == null) result = parsePITest(tokens);
+ if (result == null) result = parseCommentTest(tokens);
+ if (result == null) result = parseTextTest(tokens);
+ return result;
+ }
- protected ProcessingInstructionTest parsePITest( TokenStream tokens ) {
- if (tokens.canConsume("processing-instruction", "(")) {
- if (tokens.canConsume(")")) return new
ProcessingInstructionTest(null);
- String nameOrStringLiteral = tokens.consume();
- tokens.consume(")");
- return new ProcessingInstructionTest(nameOrStringLiteral);
- }
- return null;
+ protected AnyKindTest parseAnyKindTest( TokenStream tokens ) {
+ if (tokens.canConsume("node", "(", ")")) {
+ return new AnyKindTest();
}
+ return null;
+ }
- protected CommentTest parseCommentTest( TokenStream tokens ) {
- if (tokens.canConsume("comment", "(", ")")) {
- return new CommentTest();
- }
- return null;
+ protected ProcessingInstructionTest parsePITest( TokenStream tokens ) {
+ if (tokens.canConsume("processing-instruction", "(")) {
+ if (tokens.canConsume(")")) return new
ProcessingInstructionTest(null);
+ String nameOrStringLiteral = tokens.consume();
+ tokens.consume(")");
+ return new ProcessingInstructionTest(nameOrStringLiteral);
}
+ return null;
+ }
- protected TextTest parseTextTest( TokenStream tokens ) {
- if (tokens.canConsume("text", "(", ")")) {
- return new TextTest();
- }
- return null;
+ protected CommentTest parseCommentTest( TokenStream tokens ) {
+ if (tokens.canConsume("comment", "(", ")")) {
+ return new CommentTest();
}
+ return null;
+ }
- protected DocumentTest parseDocumentTest( TokenStream tokens ) {
- if (tokens.canConsume("document-node", "(")) {
- // Document test ...
- ElementTest elementTest = parseElementTest(tokens);
- DocumentTest result = null;
- if (elementTest != null) {
- result = new DocumentTest(elementTest);
- } else {
- SchemaElementTest schemaTest = parseSchemaElementTest(tokens);
- result = schemaTest != null ? new DocumentTest(schemaTest) : null;
- }
- tokens.consume(")");
- return result;
- }
- return null;
+ protected TextTest parseTextTest( TokenStream tokens ) {
+ if (tokens.canConsume("text", "(", ")")) {
+ return new TextTest();
}
+ return null;
+ }
- protected ElementTest parseElementTest( TokenStream tokens ) {
- if (tokens.canConsume("element", "(")) {
- if (tokens.canConsume(")") || tokens.canConsume("*",
")")) {
- return new ElementTest(new NameTest(null, null), new NameTest(null,
null));
- }
- ElementTest result = null;
- NameTest elementName = parseNameTest(tokens);
- if (tokens.canConsume(",")) {
- NameTest typeName = parseNameTest(tokens);
- result = new ElementTest(elementName, typeName);
- } else {
- result = new ElementTest(elementName, new NameTest(null, null));
- }
- tokens.consume(")");
- return result;
+ protected DocumentTest parseDocumentTest( TokenStream tokens ) {
+ if (tokens.canConsume("document-node", "(")) {
+ // Document test ...
+ ElementTest elementTest = parseElementTest(tokens);
+ DocumentTest result = null;
+ if (elementTest != null) {
+ result = new DocumentTest(elementTest);
+ } else {
+ SchemaElementTest schemaTest = parseSchemaElementTest(tokens);
+ result = schemaTest != null ? new DocumentTest(schemaTest) : null;
}
- return null;
+ tokens.consume(")");
+ return result;
}
+ return null;
+ }
- protected SchemaElementTest parseSchemaElementTest( TokenStream tokens ) {
- if (tokens.canConsume("schema-element", "(")) {
- NameTest elementDeclarationName = parseNameTest(tokens);
- SchemaElementTest result = new
SchemaElementTest(elementDeclarationName);
- tokens.consume(")");
- return result;
+ protected ElementTest parseElementTest( TokenStream tokens ) {
+ if (tokens.canConsume("element", "(")) {
+ if (tokens.canConsume(")") || tokens.canConsume("*",
")")) {
+ return new ElementTest(new NameTest(null, null), new NameTest(null,
null));
}
- return null;
+ ElementTest result = null;
+ NameTest elementName = parseNameTest(tokens);
+ if (tokens.canConsume(",")) {
+ NameTest typeName = parseNameTest(tokens);
+ result = new ElementTest(elementName, typeName);
+ tokens.canConsume('?'); // just eat this
+ } else {
+ result = new ElementTest(elementName, new NameTest(null, null));
+ }
+ tokens.consume(")");
+ return result;
}
+ return null;
+ }
- protected AttributeTest parseAttributeTest( TokenStream tokens ) {
- if (tokens.canConsume("attribute", "(")) {
- if (tokens.canConsume(")") || tokens.canConsume("*",
")")) {
- return new AttributeTest(new NameTest(null, null), new NameTest(null,
null));
- }
- AttributeTest result = null;
- NameTest attributeName = parseNameTest(tokens);
- if (tokens.canConsume(",")) {
- NameTest typeName = parseNameTest(tokens);
- result = new AttributeTest(attributeName, typeName);
- } else {
- result = new AttributeTest(attributeName, new NameTest(null, null));
- }
- tokens.consume(")");
- return result;
- }
- return null;
+ protected SchemaElementTest parseSchemaElementTest( TokenStream tokens ) {
+ if (tokens.canConsume("schema-element", "(")) {
+ NameTest elementDeclarationName = parseNameTest(tokens);
+ SchemaElementTest result = new SchemaElementTest(elementDeclarationName);
+ tokens.consume(")");
+ return result;
}
+ return null;
+ }
- protected SchemaAttributeTest parseSchemaAttributeTest( TokenStream tokens ) {
- if (tokens.canConsume("schema-attribute", "(")) {
- NameTest attributeDeclarationName = parseNameTest(tokens);
- SchemaAttributeTest result = new
SchemaAttributeTest(attributeDeclarationName);
- tokens.consume(")");
- return result;
+ protected AttributeTest parseAttributeTest( TokenStream tokens ) {
+ if (tokens.canConsume("attribute", "(")) {
+ if (tokens.canConsume(")") || tokens.canConsume("*",
")")) {
+ return new AttributeTest(new NameTest(null, null), new NameTest(null,
null));
}
- return null;
+ AttributeTest result = null;
+ NameTest attributeName = parseNameTest(tokens);
+ if (tokens.canConsume(",")) {
+ NameTest typeName = parseNameTest(tokens);
+ result = new AttributeTest(attributeName, typeName);
+ } else {
+ result = new AttributeTest(attributeName, new NameTest(null, null));
+ }
+ tokens.consume(")");
+ return result;
}
+ return null;
+ }
- protected void parseSingleType( TokenStream tokens ) {
+ protected SchemaAttributeTest parseSchemaAttributeTest( TokenStream tokens ) {
+ if (tokens.canConsume("schema-attribute", "(")) {
+ NameTest attributeDeclarationName = parseNameTest(tokens);
+ SchemaAttributeTest result = new
SchemaAttributeTest(attributeDeclarationName);
+ tokens.consume(")");
+ return result;
}
+ return null;
+ }
- protected void parseSequenceType( TokenStream tokens ) {
- }
+ protected void parseSingleType( TokenStream tokens ) {
+ }
- /**
- * Remove any leading and trailing single-quotes or double-quotes from the
supplied text.
- *
- * @param text the input text; may not be null
- * @return the text without leading and trailing quotes, or
<code>text</code> if there were no square brackets or quotes
- */
- protected String removeQuotes( String text ) {
- return text.replaceFirst("^['\"]+",
"").replaceAll("['\"]+$", "");
- }
+ protected void parseSequenceType( TokenStream tokens ) {
}
/**
+ * Remove any leading and trailing single-quotes or double-quotes from the supplied
text.
+ *
+ * @param text the input text; may not be null
+ * @return the text without leading and trailing quotes, or
<code>text</code> if there were no square brackets or quotes
+ */
+ protected String removeQuotes( String text ) {
+ return text.replaceFirst("^['\"]+",
"").replaceAll("['\"]+$", "");
+ }
+
+ protected Component collapse( Component component ) {
+ return XPathQueryParser.COLLAPSE_INNER_COMPONENTS ? component.collapse() :
component;
+ }
+
+ /**
* A {@link TokenStream.Tokenizer} implementation that parses single- and
double-quoted strings, symbols, words consisting of
* {@link TokenStream.CharacterStream#isNextValidXmlNcNameCharacter() NCName}s (as
defined by the <a
*
href="http://www.w3.org/TR/REC-xml-names/#NT-NCName">Namespaces in
XML 1.0</a> specification), XPath comments,and
Added: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/xpath/XPathQueryParser.java
===================================================================
--- trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/xpath/XPathQueryParser.java
(rev 0)
+++ trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/xpath/XPathQueryParser.java 2009-10-03
04:09:29 UTC (rev 1286)
@@ -0,0 +1,65 @@
+/*
+ * JBoss DNA (
http://www.jboss.org/dna)
+ * See the COPYRIGHT.txt file distributed with this work for information
+ * regarding copyright ownership. Some portions may be licensed
+ * to Red Hat, Inc. under one or more contributor license agreements.
+ * See the AUTHORS.txt file in the distribution for a full listing of
+ * individual contributors.
+ *
+ * JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA
+ * is licensed to you under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * JBoss DNA is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
+ */
+package org.jboss.dna.jcr.xpath;
+
+import org.jboss.dna.common.text.ParsingException;
+import org.jboss.dna.graph.ExecutionContext;
+import org.jboss.dna.graph.query.model.QueryCommand;
+import org.jboss.dna.graph.query.parse.InvalidQueryException;
+import org.jboss.dna.graph.query.parse.QueryParser;
+import org.jboss.dna.jcr.xpath.XPath.Component;
+
+/**
+ * A {@link QueryParser} implementation that accepts XPath expressions and converts them
to a {@link QueryCommand DNA Abstract
+ * Query Model} representation.
+ */
+public class XPathQueryParser implements QueryParser {
+
+ static final boolean COLLAPSE_INNER_COMPONENTS = true;
+ private static final String LANGUAGE = "XPath";
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.graph.query.parse.QueryParser#getLanguage()
+ */
+ public String getLanguage() {
+ return LANGUAGE;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.graph.query.parse.QueryParser#parseQuery(java.lang.String,
org.jboss.dna.graph.ExecutionContext)
+ */
+ public QueryCommand parseQuery( String query,
+ ExecutionContext context ) throws
InvalidQueryException, ParsingException {
+ Component xpath = new XPathParser(context).parseXPath(query);
+ System.out.println(query);
+ System.out.println(" --> " + xpath);
+ // Convert the result into a QueryCommand ...
+ QueryCommand command = new XPathToQueryTranslator(context,
query).createQuery(xpath);
+ return command;
+ }
+}
Property changes on:
trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/xpath/XPathQueryParser.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/xpath/XPathToQueryTranslator.java
===================================================================
--- trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/xpath/XPathToQueryTranslator.java
(rev 0)
+++
trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/xpath/XPathToQueryTranslator.java 2009-10-03
04:09:29 UTC (rev 1286)
@@ -0,0 +1,807 @@
+/*
+ * JBoss DNA (
http://www.jboss.org/dna)
+ * See the COPYRIGHT.txt file distributed with this work for information
+ * regarding copyright ownership. Some portions may be licensed
+ * to Red Hat, Inc. under one or more contributor license agreements.
+ * See the AUTHORS.txt file in the distribution for a full listing of
+ * individual contributors.
+ *
+ * JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA
+ * is licensed to you under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * JBoss DNA is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
+ */
+package org.jboss.dna.jcr.xpath;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import org.jboss.dna.graph.ExecutionContext;
+import org.jboss.dna.graph.property.PropertyType;
+import org.jboss.dna.graph.query.QueryBuilder;
+import org.jboss.dna.graph.query.QueryBuilder.ConstraintBuilder;
+import org.jboss.dna.graph.query.model.Operator;
+import org.jboss.dna.graph.query.model.QueryCommand;
+import org.jboss.dna.graph.query.parse.InvalidQueryException;
+import org.jboss.dna.jcr.xpath.XPath.And;
+import org.jboss.dna.jcr.xpath.XPath.AttributeNameTest;
+import org.jboss.dna.jcr.xpath.XPath.AxisStep;
+import org.jboss.dna.jcr.xpath.XPath.BinaryComponent;
+import org.jboss.dna.jcr.xpath.XPath.Comparison;
+import org.jboss.dna.jcr.xpath.XPath.Component;
+import org.jboss.dna.jcr.xpath.XPath.ContextItem;
+import org.jboss.dna.jcr.xpath.XPath.DescendantOrSelf;
+import org.jboss.dna.jcr.xpath.XPath.ElementTest;
+import org.jboss.dna.jcr.xpath.XPath.Except;
+import org.jboss.dna.jcr.xpath.XPath.FilterStep;
+import org.jboss.dna.jcr.xpath.XPath.FunctionCall;
+import org.jboss.dna.jcr.xpath.XPath.Intersect;
+import org.jboss.dna.jcr.xpath.XPath.Literal;
+import org.jboss.dna.jcr.xpath.XPath.NameTest;
+import org.jboss.dna.jcr.xpath.XPath.NodeTest;
+import org.jboss.dna.jcr.xpath.XPath.Or;
+import org.jboss.dna.jcr.xpath.XPath.ParenthesizedExpression;
+import org.jboss.dna.jcr.xpath.XPath.PathExpression;
+import org.jboss.dna.jcr.xpath.XPath.StepExpression;
+import org.jboss.dna.jcr.xpath.XPath.Union;
+
+/**
+ * A component that translates an {@link XPath} abstract syntax model representation into
a {@link QueryCommand DNA abstract query
+ * model}.
+ */
+public class XPathToQueryTranslator {
+
+ protected static final Map<NameTest, PropertyType> CAST_FUNCTION_NAME_TO_TYPE;
+
+ static {
+ Map<NameTest, PropertyType> map = new HashMap<NameTest,
PropertyType>();
+ map.put(new NameTest("fn", "string"), PropertyType.STRING);
+ map.put(new NameTest("xs", "string"), PropertyType.STRING);
+ map.put(new NameTest("xs", "base64Binary"),
PropertyType.BINARY);
+ map.put(new NameTest("xs", "double"), PropertyType.DOUBLE);
+ map.put(new NameTest("xs", "long"), PropertyType.LONG);
+ map.put(new NameTest("xs", "boolean"),
PropertyType.BOOLEAN);
+ map.put(new NameTest("xs", "dateTime"), PropertyType.DATE);
+ map.put(new NameTest("xs", "string"), PropertyType.PATH);
+ map.put(new NameTest("xs", "string"), PropertyType.NAME);
+ map.put(new NameTest("xs", "IDREF"),
PropertyType.REFERENCE);
+ CAST_FUNCTION_NAME_TO_TYPE = Collections.unmodifiableMap(map);
+ }
+
+ private final String query;
+ private final ExecutionContext context;
+ private final QueryBuilder builder;
+ private final Set<String> aliases = new HashSet<String>();
+
+ public XPathToQueryTranslator( ExecutionContext context,
+ String query ) {
+ this.query = query;
+ this.context = context;
+ this.builder = new QueryBuilder(this.context);
+ }
+
+ public QueryCommand createQuery( Component xpath ) {
+ if (xpath instanceof BinaryComponent) {
+ BinaryComponent binary = (BinaryComponent)xpath;
+ if (binary instanceof Union) {
+ createQuery(binary.getLeft());
+ builder.union();
+ createQuery(binary.getRight());
+ return builder.query();
+ } else if (binary instanceof Intersect) {
+ createQuery(binary.getLeft());
+ builder.intersect();
+ createQuery(binary.getRight());
+ return builder.query();
+ } else if (binary instanceof Except) {
+ createQuery(binary.getLeft());
+ builder.except();
+ createQuery(binary.getRight());
+ return builder.query();
+ }
+ } else if (xpath instanceof PathExpression) {
+ translate((PathExpression)xpath);
+ return builder.query();
+ }
+ // unexpected component ...
+ throw new InvalidQueryException(query,
+ "Acceptable XPath queries must lead with a
path expression or must be a union, intersect or except");
+ }
+
+ protected void translate( PathExpression pathExpression ) {
+ List<StepExpression> steps = pathExpression.getSteps();
+ assert !steps.isEmpty();
+ if (!pathExpression.isRelative()) {
+ // Absolute path must start with "/jcr:root/" or "//"
...
+ Component first = steps.get(0).collapse();
+ // Result will be NameTest("jcr","root") or
DescendantOrSelf ...
+ if (first instanceof DescendantOrSelf) {
+ // do nothing ...
+ } else if (first instanceof NameTest && steps.size() > 1
&& ((NameTest)first).matches("jcr", "root")) {
+ // We can actually remove this first step, since relative paths are
relative to the root ...
+ steps = steps.subList(1, steps.size());
+ } else {
+ throw new InvalidQueryException(query, "An absolute path expression
must start with '//' or '/jcr:root/...'");
+ }
+ }
+
+ // Walk the steps along the path expression ...
+ ConstraintBuilder where = builder.where();
+ List<StepExpression> path = new ArrayList<StepExpression>();
+ String tableName = null;
+ for (StepExpression step : steps) {
+ if (step instanceof AxisStep) {
+ AxisStep axis = (AxisStep)step;
+ NodeTest nodeTest = axis.getNodeTest();
+ if (nodeTest instanceof NameTest) {
+ if (appliesToPathConstraint(axis.getPredicates())) {
+ // Can go into the path constraint ...
+ path.add(step);
+ } else {
+ // The constraints are more complicated, so we need to define a
new source/table ...
+ // path.add(step);
+ tableName = translateSource(tableName, path, where);
+ translatePredicates(axis.getPredicates(), tableName, where);
+ path.clear();
+ }
+ } else if (nodeTest instanceof ElementTest) {
+ // We need to build a new source with the partial path we have so far
...
+ tableName = translateElementTest((ElementTest)nodeTest, path,
where);
+ translatePredicates(axis.getPredicates(), tableName, where);
+ path.clear();
+ } else if (nodeTest instanceof AttributeNameTest) {
+ AttributeNameTest attributeName = (AttributeNameTest)nodeTest;
+ builder.select(nameFrom(attributeName.getNameTest()));
+ } else {
+ throw new InvalidQueryException(query, "The '" + step +
"' step is not supported");
+ }
+ } else if (step instanceof FilterStep) {
+ FilterStep filter = (FilterStep)step;
+ Component primary = filter.getPrimaryExpression();
+ List<Component> predicates = filter.getPredicates();
+ if (primary instanceof ContextItem) {
+ if (appliesToPathConstraint(predicates)) {
+ // Can ignore the '.' ...
+ } else {
+ // The constraints are more complicated, so we need to define a
new source/table ...
+ path.add(step);
+ tableName = translateSource(tableName, path, where);
+ translatePredicates(predicates, tableName, where);
+ path.clear();
+ }
+ } else if (primary instanceof Literal) {
+ throw new InvalidQueryException(query,
+ "A literal is not supported in
the primary path expression; therefore '"
+ + primary + "' is not
valid");
+ } else if (primary instanceof FunctionCall) {
+ throw new InvalidQueryException(query,
+ "A function call is not
supported in the primary path expression; therefore '"
+ + primary + "' is not
valid");
+ } else if (primary instanceof ParenthesizedExpression) {
+ // This can be used to define an OR-ed set of expressions defining
select columns ...
+ ParenthesizedExpression paren = (ParenthesizedExpression)primary;
+ Component wrapped = paren.getWrapped().collapse();
+ if (wrapped instanceof AttributeNameTest) {
+ AttributeNameTest attributeName = (AttributeNameTest)wrapped;
+ builder.select(nameFrom(attributeName.getNameTest()));
+ } else if (wrapped instanceof BinaryComponent) {
+ for (AttributeNameTest attributeName :
extractAttributeNames((BinaryComponent)wrapped)) {
+ builder.select(nameFrom(attributeName.getNameTest()));
+ }
+ path.add(filter); // in case any element names are there
+ } else {
+ throw new InvalidQueryException(query,
+ "A parenthesized expression
of this type is not supported in the primary path expression; therefore '"
+ + primary + "' is not
valid");
+ }
+ }
+
+ } else {
+ path.add(step);
+ }
+ }
+ if (!path.isEmpty()) {
+ translateSource(tableName, path, where);
+ }
+ where.end();
+ }
+
+ /**
+ * Find any {@link AttributeNameTest attribute names} that have been unioned together
(with '|'). Any other combination of
+ * objects results in an error.
+ *
+ * @param binary the binary component
+ * @return the list of attribute names, if that's all that's in the supplied
component; may be empty
+ */
+ protected List<AttributeNameTest> extractAttributeNames( BinaryComponent binary
) {
+ List<AttributeNameTest> results = new
ArrayList<AttributeNameTest>();
+ boolean failed = false;
+ if (binary instanceof Union) {
+ for (int i = 0; i != 2; ++i) {
+ Component comp = i == 0 ? binary.getLeft() : binary.getRight();
+ comp = comp.collapse();
+ if (comp instanceof Union) {
+ results.addAll(extractAttributeNames((BinaryComponent)comp));
+ } else if (comp instanceof AttributeNameTest) {
+ results.add((AttributeNameTest)comp);
+ } else if (comp instanceof NameTest) {
+ // Element names, which are fine but we'll ignore
+ } else {
+ failed = true;
+ break;
+ }
+ }
+ } else {
+ failed = true;
+ }
+ if (failed) {
+ throw new InvalidQueryException(query,
+ "A parenthesized expression in a path
step may only contain ORed and ANDed attribute names or element names; therefore
'"
+ + binary + "' is not valid");
+ }
+ return results;
+ }
+
+ /**
+ * Find any {@link NameTest element names} that have been unioned together (with
'|'). Any other combination of objects
+ * results in an error.
+ *
+ * @param binary the binary component
+ * @return the list of attribute names, if that's all that's in the supplied
component; may be empty
+ */
+ protected List<NameTest> extractElementNames( BinaryComponent binary ) {
+ List<NameTest> results = new ArrayList<NameTest>();
+ boolean failed = false;
+ if (binary instanceof Union) {
+ for (int i = 0; i != 2; ++i) {
+ Component comp = i == 0 ? binary.getLeft() : binary.getRight();
+ comp = comp.collapse();
+ if (comp instanceof Union) {
+ results.addAll(extractElementNames((BinaryComponent)comp));
+ } else if (comp instanceof AttributeNameTest) {
+ // ignore these ...
+ } else if (comp instanceof NameTest) {
+ results.add((NameTest)comp);
+ } else {
+ failed = true;
+ break;
+ }
+ }
+ } else {
+ failed = true;
+ }
+ if (failed) {
+ throw new InvalidQueryException(query,
+ "A parenthesized expression in a path
step may only contain ORed element names; therefore '"
+ + binary + "' is not valid");
+ }
+ return results;
+ }
+
+ protected String translateSource( String tableName,
+ List<StepExpression> path,
+ ConstraintBuilder where ) {
+ String alias = newAlias();
+ if (tableName != null) {
+ // This is after some element(...) steps, so we need to join ...
+ builder.joinAllNodesAs(alias);
+ } else {
+ // This is the only part of the query ...
+ builder.fromAllNodesAs(alias);
+ }
+ tableName = alias;
+ if (path.size() == 1 && path.get(0).collapse() instanceof NameTest) {
+ // Node immediately below root ...
+ NameTest nodeName = (NameTest)path.get(0).collapse();
+
where.nodeName(alias).isEqualTo(nameFrom(nodeName)).and().depth(alias).isEqualTo(1);
+ } else if (path.size() == 2 && path.get(0) instanceof DescendantOrSelf
&& path.get(1).collapse() instanceof NameTest) {
+ // Node anywhere ...
+ NameTest nodeName = (NameTest)path.get(1).collapse();
+ if (!nodeName.isWildcard()) {
+ where.nodeName(alias).isEqualTo(nameFrom(nodeName));
+ }
+ } else {
+ // Must be just a bunch of descendant-or-self, axis and filter steps ...
+ translatePathExpressionConstraint(new PathExpression(true, path), where,
alias);
+ }
+ return tableName;
+ }
+
+ protected String translateElementTest( ElementTest elementTest,
+ List<StepExpression> pathConstraint,
+ ConstraintBuilder where ) {
+ String tableName = null;
+ NameTest typeName = elementTest.getTypeName();
+ if (typeName.isWildcard()) {
+ tableName = newAlias();
+ builder.fromAllNodesAs(tableName);
+ } else {
+ if (typeName.getLocalTest() == null) {
+ throw new InvalidQueryException(
+ query,
+ "The '"
+ + elementTest
+ + "' clause uses a partial
wildcard in the type name, but only a wildcard on the whole name is supported");
+ }
+ tableName = nameFrom(typeName);
+ builder.from(tableName);
+ }
+ if (elementTest.getElementName() != null) {
+ NameTest nodeName = elementTest.getElementName();
+ if (!nodeName.isWildcard()) {
+ where.nodeName(tableName).isEqualTo(nameFrom(nodeName));
+ }
+ }
+ if (pathConstraint.isEmpty()) {
+ where.depth(tableName).isEqualTo(1);
+ }
+ return tableName;
+ }
+
+ protected void translatePredicates( List<Component> predicates,
+ String tableName,
+ ConstraintBuilder where ) {
+ assert tableName != null;
+ for (Component predicate : predicates) {
+ translatePredicate(predicate, tableName, where);
+ }
+ }
+
+ protected String translatePredicate( Component predicate,
+ String tableName,
+ ConstraintBuilder where ) {
+ predicate = predicate.collapse();
+ assert tableName != null;
+ if (predicate instanceof ParenthesizedExpression) {
+ ParenthesizedExpression paren = (ParenthesizedExpression)predicate;
+ where = where.openParen();
+ translatePredicate(paren.getWrapped(), tableName, where);
+ where.closeParen();
+ } else if (predicate instanceof And) {
+ And and = (And)predicate;
+ where = where.openParen();
+ translatePredicate(and.getLeft(), tableName, where);
+ where.and();
+ translatePredicate(and.getRight(), tableName, where);
+ where.closeParen();
+ } else if (predicate instanceof Or) {
+ Or or = (Or)predicate;
+ where = where.openParen();
+ translatePredicate(or.getLeft(), tableName, where);
+ where.or();
+ translatePredicate(or.getRight(), tableName, where);
+ where.closeParen();
+ } else if (predicate instanceof Union) {
+ Union union = (Union)predicate;
+ where = where.openParen();
+ translatePredicate(union.getLeft(), tableName, where);
+ where.or();
+ translatePredicate(union.getRight(), tableName, where);
+ where.closeParen();
+ } else if (predicate instanceof Literal) {
+ Literal literal = (Literal)predicate;
+ if (literal.isInteger()) return tableName; // do nothing, since this is a
path constraint and is handled elsewhere
+ } else if (predicate instanceof AttributeNameTest) {
+ // This adds the criteria that the attribute exists, and adds it to the
select ...
+ AttributeNameTest attribute = (AttributeNameTest)predicate;
+ String propertyName = nameFrom(attribute.getNameTest());
+ where.hasProperty(tableName, propertyName);
+ } else if (predicate instanceof NameTest) {
+ // This adds the criteria that the child node exists ...
+ NameTest childName = (NameTest)predicate;
+ String alias = newAlias();
+ builder.joinAllNodesAs(alias).onChildNode(tableName, alias);
+ if (!childName.isWildcard())
where.nodeName(alias).isEqualTo(nameFrom(childName));
+ tableName = alias;
+ } else if (predicate instanceof Comparison) {
+ Comparison comparison = (Comparison)predicate;
+ Component left = comparison.getLeft();
+ Component right = comparison.getRight();
+ Operator operator = comparison.getOperator();
+ if (left instanceof Literal) {
+ Component temp = left;
+ left = right;
+ right = temp;
+ operator = operator.getReverse();
+ }
+ if (left instanceof AttributeNameTest) {
+ AttributeNameTest attribute = (AttributeNameTest)left;
+ String propertyName = nameFrom(attribute.getNameTest());
+ if (right instanceof Literal) {
+ String value = ((Literal)right).getValue();
+ where.propertyValue(tableName, propertyName).is(operator, value);
+ } else if (right instanceof FunctionCall) {
+ FunctionCall call = (FunctionCall)right;
+ NameTest functionName = call.getName();
+ List<Component> parameters = call.getParameters();
+ // Is this a cast ...
+ PropertyType castType =
CAST_FUNCTION_NAME_TO_TYPE.get(functionName);
+ if (castType != null) {
+ if (parameters.size() == 1 &&
parameters.get(0).collapse() instanceof Literal) {
+ // The first parameter can be the type name (or table name)
...
+ Literal value = (Literal)parameters.get(0).collapse();
+ where.propertyValue(tableName,
propertyName).is(operator).cast(value.getValue()).as(castType);
+ } else {
+ throw new InvalidQueryException(query, "A cast function
requires one literal parameter; therefore '"
+ + comparison +
"' is not valid");
+ }
+ } else {
+ throw new InvalidQueryException(query,
+ "Only the
'jcr:score' function is allowed in a comparison predicate; therefore '"
+ + comparison + "' is not
valid");
+ }
+ }
+ } else if (left instanceof FunctionCall && right instanceof Literal)
{
+ FunctionCall call = (FunctionCall)left;
+ NameTest functionName = call.getName();
+ List<Component> parameters = call.getParameters();
+ String value = ((Literal)right).getValue();
+ if (functionName.matches("jcr", "score")) {
+ String scoreTableName = tableName;
+ if (parameters.isEmpty()) {
+ scoreTableName = tableName;
+ } else if (parameters.size() == 1 && parameters.get(0)
instanceof NameTest) {
+ // The first parameter can be the type name (or table name) ...
+ NameTest name = (NameTest)parameters.get(0);
+ if (!name.isWildcard()) scoreTableName = nameFrom(name);
+ } else {
+ throw new InvalidQueryException(query,
+ "The 'jcr:score'
function may have no parameters or the type name as the only parameter.");
+
+ }
+ where.fullTextSearchScore(scoreTableName).is(operator, value);
+ } else {
+ throw new InvalidQueryException(query,
+ "Only the 'jcr:score'
function is allowed in a comparison predicate; therefore '"
+ + comparison + "' is not
valid");
+ }
+ }
+ } else if (predicate instanceof FunctionCall) {
+ FunctionCall call = (FunctionCall)predicate;
+ NameTest functionName = call.getName();
+ List<Component> parameters = call.getParameters();
+ Component param1 = parameters.size() > 0 ? parameters.get(0) : null;
+ Component param2 = parameters.size() > 1 ? parameters.get(1) : null;
+ if (functionName.matches(null, "not")) {
+ if (parameters.size() != 1) {
+ throw new InvalidQueryException(query, "The 'not'
function requires one parameter; therefore '" + predicate
+ + "' is not
valid");
+ }
+ where = where.not().openParen();
+ translatePredicate(param1, tableName, where);
+ where.closeParen();
+ } else if (functionName.matches("jcr", "like")) {
+ if (parameters.size() != 2) {
+ throw new InvalidQueryException(query, "The 'jcr:like'
function requires two parameters; therefore '"
+ + predicate + "' is
not valid");
+ }
+ if (!(param1 instanceof AttributeNameTest)) {
+ throw new InvalidQueryException(query,
+ "The first parameter of
'jcr:like' must be an property reference with the '@' symbol; therefore
'"
+ + predicate + "' is not
valid");
+ }
+ if (!(param2 instanceof Literal)) {
+ throw new InvalidQueryException(query, "The second parameter of
'jcr:like' must be a literal; therefore '"
+ + predicate + "' is
not valid");
+ }
+ NameTest attributeName = ((AttributeNameTest)param1).getNameTest();
+ String value = ((Literal)param2).getValue();
+ where.propertyValue(tableName, nameFrom(attributeName)).isLike(value);
+ } else if (functionName.matches("jcr", "contains")) {
+ if (parameters.size() != 2) {
+ throw new InvalidQueryException(query, "The
'jcr:contains' function requires two parameters; therefore '"
+ + predicate + "' is
not valid");
+ }
+ if (!(param2 instanceof Literal)) {
+ throw new InvalidQueryException(query,
+ "The second parameter of
'jcr:contains' must be a literal; therefore '"
+ + predicate + "' is not
valid");
+ }
+ String value = ((Literal)param2).getValue();
+ if (param1 instanceof ContextItem) {
+ // refers to the current node (or table) ...
+ where.search(tableName, value);
+ } else if (param1 instanceof AttributeNameTest) {
+ // refers to an attribute on the current node (or table) ...
+ NameTest attributeName = ((AttributeNameTest)param1).getNameTest();
+ where.search(tableName, nameFrom(attributeName), value);
+ } else if (param1 instanceof NameTest) {
+ // refers to child node, so we need to add a join ...
+ String alias = newAlias();
+ builder.joinAllNodesAs(alias).onChildNode(tableName, alias);
+ // Now add the criteria ...
+ where.search(alias, value);
+ tableName = alias;
+ } else if (param1 instanceof PathExpression) {
+ // refers to a descendant node ...
+ PathExpression pathExpr = (PathExpression)param1;
+ if (pathExpr.getLastStep().collapse() instanceof AttributeNameTest)
{
+ AttributeNameTest attributeName =
(AttributeNameTest)pathExpr.getLastStep().collapse();
+ pathExpr = pathExpr.withoutLast();
+ String searchTable = translatePredicate(pathExpr, tableName,
where);
+ if (attributeName.getNameTest().isWildcard()) {
+ where.search(searchTable, value);
+ } else {
+ where.search(searchTable,
nameFrom(attributeName.getNameTest()), value);
+ }
+ } else {
+ String searchTable = translatePredicate(param1, tableName,
where);
+ where.search(searchTable, value);
+ }
+ } else {
+ throw new InvalidQueryException(query,
+ "The first parameter of
'jcr:contains' must be a relative path (e.g., '.', an attribute name, a
child name, etc.); therefore '"
+ + predicate + "' is not
valid");
+ }
+ } else if (functionName.matches("jcr", "deref")) {
+ throw new InvalidQueryException(query,
+ "The 'jcr:deref' function is
not required by JCR and is not currently supported; therefore '"
+ + predicate + "' is not
valid");
+ } else {
+ throw new InvalidQueryException(query,
+ "Only the 'jcr:like' and
'jcr:contains' functions are allowed in a predicate; therefore '"
+ + predicate + "' is not
valid");
+ }
+ } else if (predicate instanceof PathExpression) {
+ // Requires that the descendant node with the relative path does exist ...
+ PathExpression pathExpr = (PathExpression)predicate;
+ List<StepExpression> steps = pathExpr.getSteps();
+ assert steps.size() > 1; // 1 or 0 would have been collapsed ...
+ Component firstStep = steps.get(0).collapse();
+ if (firstStep instanceof ContextItem) {
+ // Remove the context and retry ...
+ return translatePredicate(new PathExpression(true, steps.subList(1,
steps.size())), tableName, where);
+ }
+ if (firstStep instanceof NameTest) {
+ // Special case where this is similar to '[a/@id]'
+ NameTest childName = (NameTest)firstStep;
+ String alias = newAlias();
+ builder.joinAllNodesAs(alias).onChildNode(tableName, alias);
+ if (!childName.isWildcard()) {
+ where.nodeName(alias).isEqualTo(nameFrom(childName));
+ }
+ return translatePredicate(new PathExpression(true, steps.subList(1,
steps.size())), alias, where);
+ }
+ if (firstStep instanceof DescendantOrSelf) {
+ // Special case where this is similar to '[a/@id]'
+ String alias = newAlias();
+ builder.joinAllNodesAs(alias).onDescendant(tableName, alias);
+ return translatePredicate(new PathExpression(true, steps.subList(1,
steps.size())), alias, where);
+ }
+ // Add the join ...
+ String alias = newAlias();
+ builder.joinAllNodesAs(alias).onDescendant(tableName, alias);
+ // Now add the criteria ...
+ translatePathExpressionConstraint(pathExpr, where, alias);
+ } else {
+ throw new InvalidQueryException(query, "Unsupported criteria '"
+ predicate + "'");
+ }
+ return tableName;
+ }
+
+ /**
+ * Determine if the predicates contain any expressions that cannot be put into a LIKE
constraint on the path.
+ *
+ * @param predicates the predicates
+ * @return true if the supplied predicates can be handled entirely in the LIKE
constraint on the path, or false if they have
+ * to be handled as other criteria
+ */
+ protected boolean appliesToPathConstraint( List<Component> predicates ) {
+ if (predicates.isEmpty()) return true;
+ if (predicates.size() > 1) return false;
+ assert predicates.size() == 1;
+ Component predicate = predicates.get(0);
+ if (predicate instanceof Literal && ((Literal)predicate).isInteger())
return true;
+ if (predicate instanceof NameTest && ((NameTest)predicate).isWildcard())
return true;
+ return false;
+ }
+
+ protected boolean translatePathExpressionConstraint( PathExpression pathExrp,
+ ConstraintBuilder where,
+ String tableName ) {
+ String[] paths = relativePathLikeExpressions(pathExrp);
+ if (paths == null || paths.length == 0) return false;
+ where = where.openParen();
+ boolean first = true;
+ int number = 0;
+ for (String path : paths) {
+ if (path == null || path.length() == 0 || path.equals("%/"))
continue;
+ if (first) first = false;
+ else where.or();
+ if (path.indexOf('%') != -1) {
+ where.path(tableName).isLike(path);
+ } else {
+ where.path(tableName).isEqualTo(path);
+ }
+ ++number;
+ }
+ if (number > 0) where.closeParen();
+ return true;
+ }
+
+ protected String[] relativePathLikeExpressions( PathExpression pathExpression ) {
+ List<StepExpression> steps = pathExpression.getSteps();
+ if (steps.isEmpty()) return new String[] {};
+ if (steps.size() == 1 && steps.get(0) instanceof DescendantOrSelf) return
new String[] {};
+ PathLikeBuilder builder = new SinglePathLikeBuilder();
+ for (StepExpression step : steps) {
+ if (step instanceof DescendantOrSelf) {
+ if (builder.isEmpty()) {
+ builder.append("%/");
+ } else {
+ builder = new DualPathLikeBuilder(builder.clone(),
builder.append("/%"));
+ }
+ } else if (step instanceof AxisStep) {
+ AxisStep axis = (AxisStep)step;
+ NodeTest nodeTest = axis.getNodeTest();
+ assert !(nodeTest instanceof ElementTest);
+ if (nodeTest instanceof NameTest) {
+ NameTest nameTest = (NameTest)nodeTest;
+ builder.append('/');
+ if (nameTest.getPrefixTest() != null) {
+ builder.append(nameTest.getPrefixTest()).append(':');
+ }
+ if (nameTest.getLocalTest() != null) {
+ builder.append(nameTest.getLocalTest());
+ } else {
+ builder.append('%');
+ }
+ List<Component> predicates = axis.getPredicates();
+ if (!predicates.isEmpty()) {
+ assert predicates.size() == 1;
+ Component predicate = predicates.get(0);
+ if (predicate instanceof Literal &&
((Literal)predicate).isInteger()) {
+
builder.append('[').append(((Literal)predicate).getValue()).append(']');
+ }
+ }
+ }
+ } else if (step instanceof FilterStep) {
+ FilterStep filter = (FilterStep)step;
+ Component primary = filter.getPrimaryExpression();
+ if (primary instanceof ContextItem) {
+ continue; // ignore this '.'
+ } else if (primary instanceof ParenthesizedExpression) {
+ ParenthesizedExpression paren = (ParenthesizedExpression)primary;
+ Component wrapped = paren.getWrapped().collapse();
+ if (wrapped instanceof AttributeNameTest) {
+ // ignore this; handled earlier ...
+ } else if (wrapped instanceof BinaryComponent) {
+ List<NameTest> names =
extractElementNames((BinaryComponent)wrapped);
+ if (names.size() >= 1) {
+ PathLikeBuilder orig = builder.clone();
+ builder.append('/').append(nameFrom(names.get(0)));
+ if (names.size() > 1) {
+ for (NameTest name : names.subList(1, names.size())) {
+ builder = new
DualPathLikeBuilder(orig.clone().append('/').append(nameFrom(name)), builder);
+ }
+ }
+ }
+ } else {
+ throw new InvalidQueryException(query,
+ "A parenthesized expression
of this type is not supported in the primary path expression; therefore '"
+ + primary + "' is not
valid");
+ }
+ }
+ }
+ }
+ return builder.getPaths();
+ }
+
+ protected static interface PathLikeBuilder {
+ PathLikeBuilder append( String string );
+
+ PathLikeBuilder append( char c );
+
+ boolean isEmpty();
+
+ PathLikeBuilder clone();
+
+ String[] getPaths();
+ }
+
+ protected static class SinglePathLikeBuilder implements PathLikeBuilder {
+ private final StringBuilder builder = new StringBuilder();
+
+ public SinglePathLikeBuilder append( String string ) {
+ builder.append(string);
+ return this;
+ }
+
+ public SinglePathLikeBuilder append( char c ) {
+ builder.append(c);
+ return this;
+ }
+
+ public boolean isEmpty() {
+ return builder.length() == 0;
+ }
+
+ @Override
+ public SinglePathLikeBuilder clone() {
+ return new SinglePathLikeBuilder().append(builder.toString());
+ }
+
+ @Override
+ public String toString() {
+ return builder.toString();
+ }
+
+ public String[] getPaths() {
+ return isEmpty() ? new String[] {} : new String[] {builder.toString()};
+ }
+ }
+
+ protected static class DualPathLikeBuilder implements PathLikeBuilder {
+ private final PathLikeBuilder builder1;
+ private final PathLikeBuilder builder2;
+
+ protected DualPathLikeBuilder( PathLikeBuilder builder1,
+ PathLikeBuilder builder2 ) {
+ this.builder1 = builder1;
+ this.builder2 = builder2;
+ }
+
+ public DualPathLikeBuilder append( String string ) {
+ builder1.append(string);
+ builder2.append(string);
+ return this;
+ }
+
+ public DualPathLikeBuilder append( char c ) {
+ builder1.append(c);
+ builder2.append(c);
+ return this;
+ }
+
+ public boolean isEmpty() {
+ return false;
+ }
+
+ @Override
+ public DualPathLikeBuilder clone() {
+ return new DualPathLikeBuilder(builder1.clone(), builder2.clone());
+ }
+
+ public String[] getPaths() {
+ String[] paths1 = builder1.getPaths();
+ String[] paths2 = builder2.getPaths();
+ String[] result = new String[paths1.length + paths2.length];
+ System.arraycopy(paths1, 0, result, 0, paths1.length);
+ System.arraycopy(paths2, 0, result, paths1.length, paths2.length);
+ return result;
+ }
+ }
+
+ protected String nameFrom( NameTest name ) {
+ String prefix = name.getPrefixTest();
+ String local = name.getLocalTest();
+ assert local != null;
+ return (prefix != null ? prefix + ":" : "") + local;
+ }
+
+ protected String newAlias() {
+ String root = "nodeSet";
+ int num = 1;
+ String alias = root + num;
+ while (aliases.contains(alias)) {
+ num += 1;
+ alias = root + num;
+ }
+ aliases.add(alias);
+ return alias;
+ }
+}
Property changes on:
trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/xpath/XPathToQueryTranslator.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Modified: trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/xpath/XPathParserTest.java
===================================================================
--- trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/xpath/XPathParserTest.java 2009-10-03
04:07:41 UTC (rev 1285)
+++ trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/xpath/XPathParserTest.java 2009-10-03
04:09:29 UTC (rev 1286)
@@ -36,11 +36,13 @@
import org.jboss.dna.common.text.TokenStream.Tokenizer;
import org.jboss.dna.graph.ExecutionContext;
import org.jboss.dna.graph.query.model.Operator;
+import org.jboss.dna.jcr.xpath.XPath.And;
import org.jboss.dna.jcr.xpath.XPath.AnyKindTest;
import org.jboss.dna.jcr.xpath.XPath.AttributeNameTest;
import org.jboss.dna.jcr.xpath.XPath.AttributeTest;
import org.jboss.dna.jcr.xpath.XPath.AxisStep;
import org.jboss.dna.jcr.xpath.XPath.CommentTest;
+import org.jboss.dna.jcr.xpath.XPath.Comparison;
import org.jboss.dna.jcr.xpath.XPath.Component;
import org.jboss.dna.jcr.xpath.XPath.ContextItem;
import org.jboss.dna.jcr.xpath.XPath.DescendantOrSelf;
@@ -51,15 +53,16 @@
import org.jboss.dna.jcr.xpath.XPath.Literal;
import org.jboss.dna.jcr.xpath.XPath.NameTest;
import org.jboss.dna.jcr.xpath.XPath.NodeTest;
+import org.jboss.dna.jcr.xpath.XPath.Or;
import org.jboss.dna.jcr.xpath.XPath.ParenthesizedExpression;
import org.jboss.dna.jcr.xpath.XPath.PathExpression;
import org.jboss.dna.jcr.xpath.XPath.SchemaAttributeTest;
import org.jboss.dna.jcr.xpath.XPath.SchemaElementTest;
import org.jboss.dna.jcr.xpath.XPath.StepExpression;
import org.jboss.dna.jcr.xpath.XPath.TextTest;
-import org.jboss.dna.jcr.xpath.XPathParser.XPathTokenizer;
import org.junit.After;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Test;
/**
@@ -68,12 +71,12 @@
public class XPathParserTest {
private ExecutionContext context;
- private XPathParser.Parser parser;
+ private XPathParser parser;
@Before
public void beforeEach() {
context = new ExecutionContext();
- parser = new XPathParser.Parser(context);
+ parser = new XPathParser(context);
}
@After
@@ -85,19 +88,59 @@
public void shouldParseXPathExpressions() {
assertParsable("/jcr:root/a/b/c");
assertParsable("/jcr:root/a/b/c[*]");
- assertParsable("/jcr:root/some[1]/element(nodes, my:type)[1]");
+ assertParsable("/jcr:root/some[1]/element(nodes, my:type)[2]");
assertParsable("//element(*,my:type)");
assertParsable("//element(*,my:type)[@jcr:title='something' and
@globalProperty='something else']");
assertParsable("//element(*,my:type)[@jcr:title | @globalProperty]");
+ assertParsable("//element(*,my:type)/(@jcr:title | @globalProperty)");
assertParsable("//element(*, my:type) order by @my:title");
assertParsable("//element(*, my:type) [jcr:contains(., 'jcr')] order
by jcr:score() descending");
assertParsable("//element(*, employee)[@secretary and @assistant]");
+ assertParsable("//element(*, employee)[@secretary or @assistant]");
}
@Test
+ public void shouldParseXPathExpressions2() {
+ assertParsable("/jcr:root/a/b/c",
pathExpr(axisStep(nameTest("jcr", "root")),
+ axisStep(nameTest("a")),
+ axisStep(nameTest("b")),
+ axisStep(nameTest("c"))));
+ assertParsable("/jcr:root/a/b/c[*]",
pathExpr(axisStep(nameTest("jcr", "root")),
+ axisStep(nameTest("a")),
+ axisStep(nameTest("b")),
+ axisStep(nameTest("c"),
wildcard())));
+ assertParsable("/jcr:root/some[1]",
pathExpr(axisStep(nameTest("jcr", "root")),
axisStep(nameTest("some"), literal("1"))));
+ assertParsable("/jcr:root/element(*)",
pathExpr(axisStep(nameTest("jcr", "root")),
axisStep(element(wildcard(),
+
wildcard()))));
+ assertParsable("/jcr:root/element(name)",
pathExpr(axisStep(nameTest("jcr", "root")),
axisStep(element(nameTest("name"),
+
wildcard()))));
+ assertParsable("/jcr:root/element(*, *)",
pathExpr(axisStep(nameTest("jcr", "root")),
axisStep(element(wildcard(),
+
wildcard()))));
+ assertParsable("/jcr:root/element(*, my:type)",
pathExpr(axisStep(nameTest("jcr", "root")),
+
axisStep(element(wildcard(), nameTest("my", "type")))));
+ assertParsable("/jcr:root/element(ex:name, my:type)",
+ pathExpr(axisStep(nameTest("jcr", "root")),
axisStep(element(nameTest("ex", "name"),
+
nameTest("my", "type")))));
+ assertParsable("/jcr:root/element(name, my:type)",
pathExpr(axisStep(nameTest("jcr", "root")),
+
axisStep(element(nameTest("name"), nameTest("my",
"type")))));
+ assertParsable("/jcr:root/element(name, type)",
pathExpr(axisStep(nameTest("jcr", "root")),
+
axisStep(element(nameTest("name"), nameTest("type")))));
+ assertParsable("/jcr:root/some[1]/element(nodes, my:type)[1]",
pathExpr(axisStep(nameTest("jcr", "root")),
+
axisStep(nameTest("some"), literal("1")),
+
axisStep(element(nameTest("nodes"),
+
nameTest("my", "type")),
+
literal("1"))));
+ assertParsable("/jcr:root/some[1]/element(*, my:type)[1]",
pathExpr(axisStep(nameTest("jcr", "root")),
+
axisStep(nameTest("some"), literal("1")),
+
axisStep(element(wildcard(), nameTest("my", "type")),
+
literal("1"))));
+ }
+
+ @Ignore
+ @Test
public void shouldParseXPathExpressionsThatCombineSeparateExpressions() {
- assertParsable("/jcr:root/a/b/c and /jcr:root/c/d/e");
- assertParsable("/jcr:root/a/b/c and /jcr:root/c/d/e or
/jcr:root/f/g/h");
+ assertParsable("/jcr:root/a/b/c union /jcr:root/c/d/e");
+ assertParsable("/jcr:root/a/b/c union /jcr:root/c/d/e intersect
/jcr:root/f/g/h");
}
@Test
@@ -116,16 +159,14 @@
@Test
public void
shouldParsePathExpressionWithAbbreviatedDescendantOrSelfWithRelativeNamePathPredicate() {
- assertThat(parser.parsePathExpr(tokenize("//.[c]")),
is(pathExpr(descendantOrSelf(),
-
filterStep(contextItem(),
-
relativePathExpr(axisStep(nameTest("c")))))));
+ assertThat(parser.parsePathExpr(tokenize("//.[c]")),
is(pathExpr(descendantOrSelf(), filterStep(contextItem(),
+
nameTest("c")))));
}
@Test
public void
shouldParsePathExpressionWithAbbreviatedDescendantOrSelfWithRelativeNumericLiteralPredicate()
{
- assertThat(parser.parsePathExpr(tokenize("//.[3]")),
is(pathExpr(descendantOrSelf(),
-
filterStep(contextItem(),
-
relativePathExpr(filterStep(literal("3")))))));
+ assertThat(parser.parsePathExpr(tokenize("//.[3]")),
is(pathExpr(descendantOrSelf(), filterStep(contextItem(),
+
literal("3")))));
}
//
----------------------------------------------------------------------------------------------------------------
@@ -148,17 +189,13 @@
@Test
public void
shouldParseRelativePathExpressionWithAbbreviatedDescendantOrSelfWithRelativeNamePathPredicate()
{
assertThat(parser.parseRelativePathExpr(tokenize("a//.[c]")),
- is(relativePathExpr(axisStep(nameTest("a")),
- descendantOrSelf(),
- filterStep(contextItem(),
relativePathExpr(axisStep(nameTest("c")))))));
+ is(relativePathExpr(axisStep(nameTest("a")),
descendantOrSelf(), filterStep(contextItem(), nameTest("c")))));
}
@Test
public void
shouldParseRelativePathExpressionWithAbbreviatedDescendantOrSelfWithRelativeNumericLiteralPredicate()
{
assertThat(parser.parseRelativePathExpr(tokenize("a//.[3]")),
- is(relativePathExpr(axisStep(nameTest("a")),
- descendantOrSelf(),
- filterStep(contextItem(),
relativePathExpr(filterStep(literal("3")))))));
+ is(relativePathExpr(axisStep(nameTest("a")),
descendantOrSelf(), filterStep(contextItem(), literal("3")))));
}
//
----------------------------------------------------------------------------------------------------------------
@@ -167,8 +204,7 @@
@Test
public void shouldParseStepExpressionFromParenthesizedLiteral() {
- assertThat(parser.parseStepExpr(tokenize("('foo')")),
-
is((StepExpression)filterStep(paren(relativePathExpr(filterStep(literal("foo")))))));
+ assertThat(parser.parseStepExpr(tokenize("('foo')")),
is((StepExpression)filterStep(paren(literal("foo")))));
}
@Test
@@ -178,22 +214,20 @@
@Test
public void shouldParseStepExpressionFromFunctionCallWithUnqualifiedName() {
- assertThat(parser.parseStepExpr(tokenize("element(*,*)")),
-
is((StepExpression)filterStep(functionCall(nameTest("element"),
-
relativePathExpr(axisStep(wildcard())),
-
relativePathExpr(axisStep(wildcard()))))));
+ assertThat(parser.parseStepExpr(tokenize("element2(*,*)")),
+
is((StepExpression)filterStep(functionCall(nameTest("element2"), wildcard(),
wildcard()))));
}
@Test
public void shouldParseStepExpressionFromFunctionCallWithQualifiedName() {
assertThat(parser.parseStepExpr(tokenize("foo:bar(*)")),
- is((StepExpression)filterStep(functionCall(nameTest("foo",
"bar"), relativePathExpr(axisStep(wildcard()))))));
+ is((StepExpression)filterStep(functionCall(nameTest("foo",
"bar"), wildcard()))));
}
@Test
public void shouldParseStepExpressionFromQualifiedNameWithPredicate() {
assertThat(parser.parseStepExpr(tokenize("foo:bar[3]")),
- is((StepExpression)axisStep(nameTest("foo",
"bar"), relativePathExpr(filterStep(literal("3"))))));
+ is((StepExpression)axisStep(nameTest("foo",
"bar"), literal("3"))));
}
//
----------------------------------------------------------------------------------------------------------------
@@ -255,18 +289,17 @@
@Test
public void
shouldParseAxisStepFromWildcardPrefixAndNonWildcardLocalNameWithPredicates() {
- assertThat(parser.parseAxisStep(tokenize("*:name[3]")),
is(axisStep(nameTest(null, "name"),
-
relativePathExpr(filterStep(literal("3"))))));
+ assertThat(parser.parseAxisStep(tokenize("*:name[3]")),
is(axisStep(nameTest(null, "name"), literal("3"))));
}
@Test
public void shouldParseAxisStepFromWildcardLocalNameWithPredicates() {
- assertThat(parser.parseAxisStep(tokenize("*[3]")),
is(axisStep(wildcard(), relativePathExpr(filterStep(literal("3"))))));
+ assertThat(parser.parseAxisStep(tokenize("*[3]")),
is(axisStep(wildcard(), literal("3"))));
}
@Test
public void shouldParseAxisStepFromWildcardPrefixAndLocalNameWithPredicates() {
- assertThat(parser.parseAxisStep(tokenize("*:*[3]")),
is(axisStep(wildcard(), relativePathExpr(filterStep(literal("3"))))));
+ assertThat(parser.parseAxisStep(tokenize("*:*[3]")),
is(axisStep(wildcard(), literal("3"))));
}
//
----------------------------------------------------------------------------------------------------------------
@@ -342,16 +375,63 @@
//
----------------------------------------------------------------------------------------------------------------
@Test
+ public void shouldParsePredicatesWithAttributeEqualsStringLiteral() {
+
assertThat(parser.parsePredicates(tokenize("[@jcr:title='something']")),
+ is(predicates(comparison(attributeNameTest(nameTest("jcr",
"title")), Operator.EQUAL_TO, literal("something")))));
+ }
+
+ @Test
+ public void shouldParsePredicatesWithAttributeLessThanIntegerLiteral() {
+ assertThat(parser.parsePredicates(tokenize("[@ex:age<3]")),
is(predicates(comparison(attributeNameTest(nameTest("ex",
+
"age")),
+
Operator.LESS_THAN,
+
literal("3")))));
+ }
+
+ @Test
+ public void shouldParsePredicatesWithAttributeLikeStringLiteral() {
+
assertThat(parser.parsePredicates(tokenize("[jcr:like(@jcr:title,'%something%')]")),
+ is(predicates(functionCall(nameTest("jcr",
"like"),
+ attributeNameTest(nameTest("jcr",
"title")),
+ literal("%something%")))));
+ }
+
+ @Test
+ public void shouldParsePredicatesWithAndedExpressions() {
+ assertThat(parser.parsePredicates(tokenize("[@ex:age<3 and
jcr:like(@jcr:title,'%something%')]")),
+
is(predicates(and(comparison(attributeNameTest(nameTest("ex", "age")),
Operator.LESS_THAN, literal("3")),
+ functionCall(nameTest("jcr",
"like"),
+
attributeNameTest(nameTest("jcr", "title")),
+ literal("%something%"))))));
+ }
+
+ @Test
+ public void shouldParsePredicatesWithOredExpressions() {
+ assertThat(parser.parsePredicates(tokenize("[@ex:age<3 or
jcr:like(@jcr:title,'%something%')]")),
+ is(predicates(or(comparison(attributeNameTest(nameTest("ex",
"age")), Operator.LESS_THAN, literal("3")),
+ functionCall(nameTest("jcr",
"like"),
+
attributeNameTest(nameTest("jcr", "title")),
+ literal("%something%"))))));
+ }
+
+ @Test
+ public void shouldParsePredicatesWithMultipleSeparatePredicates() {
+
assertThat(parser.parsePredicates(tokenize("[@ex:age<3][jcr:like(@jcr:title,'%something%')]")),
+ is(predicates(comparison(attributeNameTest(nameTest("ex",
"age")), Operator.LESS_THAN, literal("3")),
+ functionCall(nameTest("jcr",
"like"),
+ attributeNameTest(nameTest("jcr",
"title")),
+ literal("%something%")))));
+ }
+
+ @Test
public void shouldParsePredicatesWhenThereIsOnlyOnePredicate() {
- assertThat(parser.parsePredicates(tokenize("[foo]")),
is(predicates(relativePathExpr(axisStep(nameTest("foo"))))));
+ assertThat(parser.parsePredicates(tokenize("[foo]")),
is(predicates(nameTest("foo"))));
}
@Test
public void shouldParsePredicatesWhenThereAreMultiplePredicates() {
-
assertThat(parser.parsePredicates(tokenize("['foo']['bar']")),
- is(predicates(relativePathExpr(filterStep(literal("foo"))),
relativePathExpr(filterStep(literal("bar"))))));
- assertThat(parser.parsePredicates(tokenize("[foo][bar]")),
is(predicates(relativePathExpr(axisStep(nameTest("foo"))),
-
relativePathExpr(axisStep(nameTest("bar"))))));
+
assertThat(parser.parsePredicates(tokenize("['foo']['bar']")),
is(predicates(literal("foo"), literal("bar"))));
+ assertThat(parser.parsePredicates(tokenize("[foo][bar]")),
is(predicates(nameTest("foo"), nameTest("bar"))));
}
//
----------------------------------------------------------------------------------------------------------------
@@ -369,7 +449,7 @@
@Test
public void shouldParseParenthesizedExpression() {
- assertThat(parser.parseParenthesizedExpr(tokenize("('foo')")),
is(paren(relativePathExpr(filterStep(literal("foo"))))));
+ assertThat(parser.parseParenthesizedExpr(tokenize("('foo')")),
is(paren(literal("foo"))));
}
//
----------------------------------------------------------------------------------------------------------------
@@ -430,7 +510,7 @@
assertThat(func, is(notNullValue()));
assertThat(func.getName(), is(nameTest("a")));
assertThat(func.getParameters().size(), is(1));
- assertThat(func.getParameters().get(0),
is((Component)relativePathExpr(filterStep(literal("foo")))));
+ assertThat(func.getParameters().get(0),
is((Component)literal("foo")));
}
@Test
@@ -439,7 +519,7 @@
assertThat(func, is(notNullValue()));
assertThat(func.getName(), is(nameTest("a")));
assertThat(func.getParameters().size(), is(1));
- assertThat(func.getParameters().get(0),
is((Component)relativePathExpr(axisStep(nameTest("foo")))));
+ assertThat(func.getParameters().get(0),
is((Component)nameTest("foo")));
}
//
----------------------------------------------------------------------------------------------------------------
@@ -557,7 +637,7 @@
@Test
public void shouldParseAnyKindTest() {
- assertThat(parser.parseAnyKindTest(tokenize("node()")),
is(notNullValue()));
+ assertThat(parser.parseAnyKindTest(tokenize("node()")),
is(instanceOf(AnyKindTest.class)));
}
@Test
@@ -573,7 +653,7 @@
@Test
public void shouldParseCommentTest() {
- assertThat(parser.parseCommentTest(tokenize("comment()")),
is(notNullValue()));
+ assertThat(parser.parseCommentTest(tokenize("comment()")),
is(instanceOf(CommentTest.class)));
}
@Test
@@ -589,7 +669,7 @@
@Test
public void shouldParseTextTest() {
- assertThat(parser.parseTextTest(tokenize("text()")),
is(notNullValue()));
+ assertThat(parser.parseTextTest(tokenize("text()")),
is(instanceOf(TextTest.class)));
}
@Test
@@ -769,8 +849,12 @@
assertThat(result, is(nullValue()));
}
+ //
----------------------------------------------------------------------------------------------------------------
+ // utility methods
+ //
----------------------------------------------------------------------------------------------------------------
+
protected TokenStream tokenize( String xpath ) {
- Tokenizer tokenizer = new XPathTokenizer(false); // skip comments
+ Tokenizer tokenizer = new XPathParser.XPathTokenizer(false); // skip comments
return new TokenStream(xpath, tokenizer, true).start(); // case sensitive!!
}
@@ -812,6 +896,22 @@
return new ContextItem();
}
+ protected And and( Component left,
+ Component right ) {
+ return new And(left, right);
+ }
+
+ protected Or or( Component left,
+ Component right ) {
+ return new Or(left, right);
+ }
+
+ protected Comparison comparison( Component left,
+ Operator operator,
+ Component right ) {
+ return new Comparison(left, operator, right);
+ }
+
protected List<Component> predicates( Component... predicates ) {
return Arrays.asList(predicates);
}
@@ -833,6 +933,15 @@
return new AttributeTest(name, type);
}
+ protected ElementTest element( NameTest name ) {
+ return new ElementTest(name, null);
+ }
+
+ protected ElementTest element( NameTest name,
+ NameTest type ) {
+ return new ElementTest(name, type);
+ }
+
protected FunctionCall functionCall( NameTest name,
Component... parameters ) {
return new FunctionCall(name, Arrays.asList(parameters));
@@ -852,12 +961,22 @@
}
protected void assertParsable( String xpath ) {
- new XPathParser().parseQuery(xpath, context);
+ new XPathQueryParser().parseQuery(xpath, context);
}
+ protected void assertParsable( String xpath,
+ Component component ) {
+ Component actual = parser.parseExpr(tokenize(xpath));
+ if (component != null) {
+ assertThat(actual, is(component));
+ } else {
+ assertThat(actual, is(nullValue()));
+ }
+ }
+
protected void assertNotParsable( String xpath ) {
try {
- new XPathParser().parseQuery(xpath, context);
+ new XPathQueryParser().parseQuery(xpath, context);
fail("Expected an invalid XPath: " + xpath);
} catch (ParsingException e) {
// expected
Added:
trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/xpath/XPathToQueryTranslatorTest.java
===================================================================
--- trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/xpath/XPathToQueryTranslatorTest.java
(rev 0)
+++
trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/xpath/XPathToQueryTranslatorTest.java 2009-10-03
04:09:29 UTC (rev 1286)
@@ -0,0 +1,297 @@
+/*
+ * JBoss DNA (
http://www.jboss.org/dna)
+ * See the COPYRIGHT.txt file distributed with this work for information
+ * regarding copyright ownership. Some portions may be licensed
+ * to Red Hat, Inc. under one or more contributor license agreements.
+ * See the AUTHORS.txt file in the distribution for a full listing of
+ * individual contributors.
+ *
+ * JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA
+ * is licensed to you under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * JBoss DNA is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
+ */
+package org.jboss.dna.jcr.xpath;
+
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertThat;
+import org.hamcrest.Matcher;
+import org.jboss.dna.graph.ExecutionContext;
+import org.jboss.dna.graph.query.model.QueryCommand;
+import org.jboss.dna.graph.query.parse.SqlQueryParser;
+import org.jboss.dna.jcr.xpath.XPath.Component;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ *
+ */
+public class XPathToQueryTranslatorTest {
+
+ private ExecutionContext context;
+ private XPathParser parser;
+
+ @Before
+ public void beforeEach() {
+ context = new ExecutionContext();
+ context.getNamespaceRegistry().register("x",
"http://example.com");
+ parser = new XPathParser(context);
+ }
+
+ @After
+ public void afterEach() {
+ parser = null;
+ }
+
+ @Test
+ public void shouldTranslateXPathExpressionsToSql() {
+ printSqlFor("//element(*,my:type)");
+ printSqlFor("//element(nodeName,my:type)");
+ }
+
+ @Test
+ public void shouldTranslateFromXPathOfAnyNode() {
+ assertThat(xpath("//element(*)"), isSql("SELECT * FROM
__ALLNODES__ AS nodeSet1"));
+ assertThat(xpath("/jcr:root//element(*)"), isSql("SELECT * FROM
__ALLNODES__ AS nodeSet1"));
+ assertThat(xpath("//*"), isSql("SELECT * FROM __ALLNODES__ AS
nodeSet1"));
+ assertThat(xpath("/jcr:root//*"), isSql("SELECT * FROM
__ALLNODES__ AS nodeSet1"));
+ assertThat(xpath("//."), isSql("SELECT * FROM __ALLNODES__ AS
nodeSet1"));
+ assertThat(xpath("/jcr:root//."), isSql("SELECT * FROM
__ALLNODES__ AS nodeSet1"));
+ }
+
+ @Test
+ public void shouldTranslateFromXPathContainingExplicitPath() {
+ assertThat(xpath("/jcr:root/a"),
+ isSql("SELECT * FROM __ALLNODES__ AS nodeSet1 WHERE
NAME(nodeSet1) = 'a' AND DEPTH(nodeSet1) = 1"));
+ assertThat(xpath("/jcr:root/a/b"), isSql("SELECT * FROM
__ALLNODES__ AS nodeSet1 WHERE PATH(nodeSet1) = '/a/b'"));
+ assertThat(xpath("/jcr:root/a/b/c"), isSql("SELECT * FROM
__ALLNODES__ AS nodeSet1 WHERE PATH(nodeSet1) = '/a/b/c'"));
+ assertThat(xpath("/jcr:root/a/b/c/d"), isSql("SELECT * FROM
__ALLNODES__ AS nodeSet1 WHERE PATH(nodeSet1) = '/a/b/c/d'"));
+ }
+
+ @Test
+ public void shouldTranslateFromXPathContainingExplicitPathWithChildNumbers() {
+ assertThat(xpath("/jcr:root/a[2]/b"), isSql("SELECT * FROM
__ALLNODES__ AS nodeSet1 WHERE PATH(nodeSet1) = '/a[2]/b'"));
+ assertThat(xpath("/jcr:root/a/b[3]"), isSql("SELECT * FROM
__ALLNODES__ AS nodeSet1 WHERE PATH(nodeSet1) = '/a/b[3]'"));
+ assertThat(xpath("/jcr:root/a[2]/b[3]"),
+ isSql("SELECT * FROM __ALLNODES__ AS nodeSet1 WHERE
PATH(nodeSet1) = '/a[2]/b[3]'"));
+ }
+
+ @Test
+ public void shouldTranslateFromXPathContainingExplicitPathWithWildcardChildNumbers()
{
+ assertThat(xpath("/jcr:root/a[*]/b"), isSql("SELECT * FROM
__ALLNODES__ AS nodeSet1 WHERE PATH(nodeSet1) = '/a/b'"));
+ assertThat(xpath("/jcr:root/a/b[*]"), isSql("SELECT * FROM
__ALLNODES__ AS nodeSet1 WHERE PATH(nodeSet1) = '/a/b'"));
+ assertThat(xpath("/jcr:root/a[*]/b[*]"), isSql("SELECT * FROM
__ALLNODES__ AS nodeSet1 WHERE PATH(nodeSet1) = '/a/b'"));
+ }
+
+ @Test
+ public void shouldTranslateFromXPathContainingPathWithDescendantOrSelf() {
+ assertThat(xpath("/jcr:root/a/b//c"),
+ isSql("SELECT * FROM __ALLNODES__ AS nodeSet1 WHERE
PATH(nodeSet1) = '/a/b/c' OR PATH(nodeSet1) LIKE '/a/b/%/c'"));
+ assertThat(xpath("/jcr:root/a/b[2]//c"),
+ isSql("SELECT * FROM __ALLNODES__ AS nodeSet1 WHERE
PATH(nodeSet1) = '/a/b[2]/c' OR PATH(nodeSet1) LIKE
'/a/b[2]/%/c'"));
+ assertThat(xpath("/jcr:root/a/b//c[4]"),
+ isSql("SELECT * FROM __ALLNODES__ AS nodeSet1 WHERE
PATH(nodeSet1) = '/a/b/c[4]' OR PATH(nodeSet1) LIKE
'/a/b/%/c[4]'"));
+ assertThat(xpath("/jcr:root/a/b[2]//c[4]"),
+ isSql("SELECT * FROM __ALLNODES__ AS nodeSet1 WHERE
PATH(nodeSet1) = '/a/b[2]/c[4]' OR PATH(nodeSet1) LIKE
'/a/b[2]/%/c[4]'"));
+ }
+
+ @Test
+ public void shouldTranslateFromXPathContainingPathWithMultipleDescendantOrSelf() {
+ assertThat(xpath("/jcr:root/a/b//c//d"),
+ isSql("SELECT * FROM __ALLNODES__ AS nodeSet1 WHERE
(((PATH(nodeSet1) = '/a/b/c/d' OR PATH(nodeSet1) LIKE '/a/b/%/c/d') OR
PATH(nodeSet1) LIKE '/a/b/c/%/d') OR PATH(nodeSet1) LIKE
'/a/b/%/c/%/d')"));
+ }
+
+ @Test
+ public void shouldTranslateFromXPathContainingPredicatesUsingRelativePaths() {
+ assertThat(xpath("//element(*,my:type)[a/@id]"),
+ isSql("SELECT * FROM [my:type] JOIN __ALLNODES__ as nodeSet1 ON
ISCHILDNODE(nodeSet1,[my:type]) WHERE NAME(nodeSet1) = 'a' AND nodeSet1.id IS NOT
NULL"));
+ assertThat(xpath("//element(*,my:type)[a/b/@id]"),
+ isSql("SELECT * FROM [my:type] JOIN __ALLNODES__ as nodeSet1 ON
ISCHILDNODE(nodeSet1,[my:type]) JOIN __ALLNODES__ as nodeSet2 ON
ISCHILDNODE(nodeSet2,nodeSet1) WHERE (NAME(nodeSet1) = 'a' AND NAME(nodeSet2) =
'b') AND nodeSet2.id IS NOT NULL"));
+ assertThat(xpath("//element(*,my:type)[a/b/((@id and @name) or
not(@address))]"),
+ isSql("SELECT * FROM [my:type] JOIN __ALLNODES__ as nodeSet1 ON
ISCHILDNODE(nodeSet1,[my:type]) JOIN __ALLNODES__ as nodeSet2 ON
ISCHILDNODE(nodeSet2,nodeSet1) WHERE (NAME(nodeSet1) = 'a' AND NAME(nodeSet2) =
'b') AND ((nodeSet2.id IS NOT NULL and nodeSet2.name IS NOT NULL) OR
(NOT(nodeSet2.address IS NOT NULL)))"));
+ assertThat(xpath("//element(*,my:type)[./a/b/((@id and @name) or
not(@address))]"),
+ isSql("SELECT * FROM [my:type] JOIN __ALLNODES__ as nodeSet1 ON
ISCHILDNODE(nodeSet1,[my:type]) JOIN __ALLNODES__ as nodeSet2 ON
ISCHILDNODE(nodeSet2,nodeSet1) WHERE (NAME(nodeSet1) = 'a' AND NAME(nodeSet2) =
'b') AND ((nodeSet2.id IS NOT NULL and nodeSet2.name IS NOT NULL) OR
(NOT(nodeSet2.address IS NOT NULL)))"));
+ assertThat(xpath("//element(*,my:type)[a/b/((@id and @name) or
not(jcr:contains(@desc,'rock star')))]"),
+ isSql("SELECT * FROM [my:type] JOIN __ALLNODES__ as nodeSet1 ON
ISCHILDNODE(nodeSet1,[my:type]) JOIN __ALLNODES__ as nodeSet2 ON
ISCHILDNODE(nodeSet2,nodeSet1) WHERE (NAME(nodeSet1) = 'a' AND NAME(nodeSet2) =
'b') AND ((nodeSet2.id IS NOT NULL and nodeSet2.name IS NOT NULL) OR
(NOT(CONTAINS(nodeSet2.desc,'rock star'))))"));
+ assertThat(xpath("//element(*,my:type)[*/@id]"),
+ isSql("SELECT * FROM [my:type] JOIN __ALLNODES__ as nodeSet1 ON
ISCHILDNODE(nodeSet1,[my:type]) WHERE nodeSet1.id IS NOT NULL"));
+ assertThat(xpath("//element(*,my:type)[*/*/@id]"),
+ isSql("SELECT * FROM [my:type] JOIN __ALLNODES__ as nodeSet1 ON
ISCHILDNODE(nodeSet1,[my:type]) JOIN __ALLNODES__ as nodeSet2 ON
ISCHILDNODE(nodeSet2,nodeSet1) WHERE nodeSet2.id IS NOT NULL"));
+ assertThat(xpath("//element(*,my:type)[./*/*/@id]"),
+ isSql("SELECT * FROM [my:type] JOIN __ALLNODES__ as nodeSet1 ON
ISCHILDNODE(nodeSet1,[my:type]) JOIN __ALLNODES__ as nodeSet2 ON
ISCHILDNODE(nodeSet2,nodeSet1) WHERE nodeSet2.id IS NOT NULL"));
+ assertThat(xpath("//element(*,my:type)[.//@id]"),
+ isSql("SELECT * FROM [my:type] JOIN __ALLNODES__ as nodeSet1 ON
ISDESCENDANTNODE(nodeSet1,[my:type]) WHERE nodeSet1.id IS NOT NULL"));
+ }
+
+ @Test
+ public void
shouldTranslateFromXPathContainingPredicatesIdentifyingPropertiesThatMustHaveValues() {
+ assertThat(xpath("//element(*,my:type)[@id]"), isSql("SELECT *
FROM [my:type] WHERE id IS NOT NULL"));
+ assertThat(xpath("//element(*,my:type)[@id][@name]"),
+ isSql("SELECT * FROM [my:type] WHERE id IS NOT NULL AND name IS
NOT NULL"));
+ assertThat(xpath("//element(*,my:type)[@id | @name]"),
+ isSql("SELECT * FROM [my:type] WHERE id IS NOT NULL OR name IS
NOT NULL"));
+ assertThat(xpath("//element(*,my:type)[@id | (@name and @address)]"),
+ isSql("SELECT * FROM [my:type] WHERE id IS NOT NULL OR (name IS
NOT NULL AND address IS NOT NULL)"));
+ }
+
+ @Test
+ public void shouldTranslateFromXPathContainingPredicatesUsingNot() {
+ assertThat(xpath("//element(*,my:type)[not(@id)]"), isSql("SELECT
* FROM [my:type] WHERE NOT(id IS NOT NULL)"));
+ assertThat(xpath("//element(*,my:type)[not(jcr:contains(@desc,'rock
star'))]"),
+ isSql("SELECT * FROM [my:type] WHERE NOT(CONTAINS(desc,'rock
star'))"));
+ assertThat(xpath("//element(*,my:type)[not(@id < 1 and
jcr:contains(@desc,'rock star'))]"),
+ isSql("SELECT * FROM [my:type] WHERE NOT(id < 1 AND
CONTAINS(desc,'rock star'))"));
+ }
+
+ @Test
+ public void shouldTranslateFromXPathContainingPredicatesIdentifyingPropertyCriteria()
{
+ assertThat(xpath("//element(*,my:type)[@id = 1]"), isSql("SELECT *
FROM [my:type] WHERE id = 1"));
+ assertThat(xpath("//element(*,my:type)[@id < 1 and @name =
'john']"),
+ isSql("SELECT * FROM [my:type] WHERE id < 1 AND name =
'john'"));
+ assertThat(xpath("//element(*,my:type)[@id < 1 and ( @name =
'john' or @name = 'mary')]"),
+ isSql("SELECT * FROM [my:type] WHERE id < 1 AND (name =
'john' OR name = 'mary')"));
+ assertThat(xpath("//element(*,my:type)[@id < 1 and (
jcr:like(@name,'%john') or @name = 'mary')]"),
+ isSql("SELECT * FROM [my:type] WHERE id < 1 AND (name like
'%john' OR name = 'mary')"));
+ assertThat(xpath("//element(*,my:type)[@id < 1 and
jcr:contains(@desc,'rock star')]"),
+ isSql("SELECT * FROM [my:type] WHERE id < 1 AND
CONTAINS(desc,'rock star')"));
+ }
+
+ @Test
+ public void
shouldTranslateFromXPathContainingPredicatesIdentifyingPropertyCriteriaWithTypeCasts() {
+
assertThat(xpath("//element(*,my:type)[@datestart<=xs:dateTime('2009-09-24T11:53:23.293-05:00')]"),
+ isSql("SELECT * FROM [my:type] WHERE datestart <=
CAST('2009-09-24T11:53:23.293-05:00' AS DATE)"));
+
assertThat(xpath("//element(*,my:type)[@prop<=xs:boolean('true')]"),
+ isSql("SELECT * FROM [my:type] WHERE prop <=
CAST('true' AS BOOLEAN)"));
+ }
+
+ @Test
+ public void
shouldTranslateFromXPathContainingAttributesInPathIdentifyingPropertiesToBeSelected() {
+ assertThat(xpath("//element(*,my:type)/@id"), isSql("SELECT id
FROM [my:type]"));
+ assertThat(xpath("//element(*,my:type)/(@id|@name)"),
isSql("SELECT id, name FROM [my:type]"));
+ assertThat(xpath("//element(*,my:type)/(@id|@x:address)"),
isSql("SELECT id, [x:address] FROM [my:type]"));
+ assertThat(xpath("//element(*,my:type)/(@id|@name|@x:address)"),
isSql("SELECT id, name, [x:address] FROM [my:type]"));
+ assertThat(xpath("//element(*,my:type)/(@id union @name)"),
isSql("SELECT id, name FROM [my:type]"));
+ assertThat(xpath("//element(*,my:type)/(@id union @name union
@x:address)"),
+ isSql("SELECT id, name, [x:address] FROM [my:type]"));
+ assertThat(xpath("//(@id|@name)"), isSql("SELECT nodeSet1.id,
nodeSet1.name FROM __ALLNODES__ AS nodeSet1"));
+ assertThat(xpath("//./(@id|@name)"), isSql("SELECT nodeSet1.id,
nodeSet1.name FROM __ALLNODES__ AS nodeSet1"));
+ }
+
+ @Test
+ public void shouldTranslateFromXPathOfAnyNodeOfSpecificType() {
+ assertThat(xpath("//element(*,my:type)"), isSql("SELECT * FROM
[my:type]"));
+ }
+
+ @Test
+ public void shouldTranslateFromXPathOfAnyNodeOfSpecificTypeAndWithSpecificName() {
+ assertThat(xpath("//element(nodeName,my:type)"), isSql("SELECT *
FROM [my:type] WHERE NAME([my:type]) = 'nodeName'"));
+ }
+
+ @Test
+ public void shouldTranslateFromXPathOfAnyNodeWithName() {
+ assertThat(xpath("//element(nodeName,*)"),
+ isSql("SELECT * FROM __ALLNODES__ AS nodeSet1 WHERE
NAME(nodeSet1) = 'nodeName'"));
+
+ assertThat(xpath("//element(nodeName,*)"),
+ isSql("SELECT * FROM __ALLNODES__ AS nodeSet1 WHERE
NAME(nodeSet1) = 'nodeName'"));
+
+ assertThat(xpath("//nodeName"), isSql("SELECT * FROM __ALLNODES__
AS nodeSet1 WHERE NAME(nodeSet1) = 'nodeName'"));
+
+ assertThat(xpath("/jcr:root//element(nodeName,*)"),
+ isSql("SELECT * FROM __ALLNODES__ AS nodeSet1 WHERE
NAME(nodeSet1) = 'nodeName'"));
+
+ assertThat(xpath("/jcr:root//nodeName"),
+ isSql("SELECT * FROM __ALLNODES__ AS nodeSet1 WHERE
NAME(nodeSet1) = 'nodeName'"));
+ }
+
+ @Test
+ public void shouldTranslateFromXPathOfNodeWithNameUnderRoot() {
+ assertThat(xpath("/jcr:root/element(nodeName,*)"),
+ isSql("SELECT * FROM __ALLNODES__ AS nodeSet1 WHERE
NAME(nodeSet1) = 'nodeName' AND DEPTH(nodeSet1) = 1"));
+
+ assertThat(xpath("/jcr:root/nodeName"),
+ isSql("SELECT * FROM __ALLNODES__ AS nodeSet1 WHERE
NAME(nodeSet1) = 'nodeName' AND DEPTH(nodeSet1) = 1"));
+
+ assertThat(xpath("nodeName"),
+ isSql("SELECT * FROM __ALLNODES__ AS nodeSet1 WHERE
NAME(nodeSet1) = 'nodeName' AND DEPTH(nodeSet1) = 1"));
+ }
+
+ @Test
+ public void shouldTranslateFromXPathOfAnyNodeUsingPredicate() {
+ assertThat(xpath("//.[jcr:contains(.,'bar')]"),
+ isSql("SELECT * FROM __ALLNODES__ AS nodeSet1 WHERE
CONTAINS(nodeSet1.*,'bar')"));
+ assertThat(xpath("//.[jcr:contains(a,'bar')]"),
+ isSql("SELECT * FROM __ALLNODES__ AS nodeSet1 JOIN __ALLNODES__
AS nodeSet2 ON ISCHILDNODE(nodeSet2,nodeSet1) WHERE
CONTAINS(nodeSet2.*,'bar')"));
+ assertThat(xpath("//*[jcr:contains(.,'bar')]"),
+ isSql("SELECT * FROM __ALLNODES__ AS nodeSet1 WHERE
CONTAINS(nodeSet1.*,'bar')"));
+ assertThat(xpath("//*[jcr:contains(a,'bar')]"),
+ isSql("SELECT * FROM __ALLNODES__ AS nodeSet1 JOIN __ALLNODES__
AS nodeSet2 ON ISCHILDNODE(nodeSet2,nodeSet1) WHERE
CONTAINS(nodeSet2.*,'bar')"));
+ assertThat(xpath("//*[jcr:contains(a/@b,'bar')]"),
+ isSql("SELECT * FROM __ALLNODES__ AS nodeSet1 JOIN __ALLNODES__
AS nodeSet2 ON ISCHILDNODE(nodeSet2,nodeSet1) WHERE NAME(nodeSet2) = 'a' AND
CONTAINS(nodeSet2.b,'bar')"));
+ assertThat(xpath("//*[jcr:contains(a/*/@b,'bar')]"),
+ isSql("SELECT * FROM __ALLNODES__ AS nodeSet1 JOIN __ALLNODES__
AS nodeSet2 ON ISCHILDNODE(nodeSet2,nodeSet1) JOIN __ALLNODES__ AS nodeSet3 ON
ISCHILDNODE(nodeSet3,nodeSet2) WHERE NAME(nodeSet2) = 'a' AND
CONTAINS(nodeSet3.b,'bar')"));
+
assertThat(xpath("/jcr:root//element(*)[jcr:contains(a/@b,'bar')]"),
+ isSql("SELECT * FROM __ALLNODES__ AS nodeSet1 JOIN __ALLNODES__
AS nodeSet2 ON ISCHILDNODE(nodeSet2,nodeSet1) WHERE NAME(nodeSet2) = 'a' AND
CONTAINS(nodeSet2.b,'bar')"));
+
assertThat(xpath("/jcr:root//element(*)[jcr:contains(a/*/@b,'bar')]"),
+ isSql("SELECT * FROM __ALLNODES__ AS nodeSet1 JOIN __ALLNODES__
AS nodeSet2 ON ISCHILDNODE(nodeSet2,nodeSet1) JOIN __ALLNODES__ AS nodeSet3 ON
ISCHILDNODE(nodeSet3,nodeSet2) WHERE NAME(nodeSet2) = 'a' AND
CONTAINS(nodeSet3.b,'bar')"));
+ assertThat(xpath("/jcr:root//*[jcr:contains(a/@b,'bar')]"),
+ isSql("SELECT * FROM __ALLNODES__ AS nodeSet1 JOIN __ALLNODES__
AS nodeSet2 ON ISCHILDNODE(nodeSet2,nodeSet1) WHERE NAME(nodeSet2) = 'a' AND
CONTAINS(nodeSet2.b,'bar')"));
+ assertThat(xpath("/jcr:root//*[jcr:contains(a/*/@b,'bar')]"),
+ isSql("SELECT * FROM __ALLNODES__ AS nodeSet1 JOIN __ALLNODES__
AS nodeSet2 ON ISCHILDNODE(nodeSet2,nodeSet1) JOIN __ALLNODES__ AS nodeSet3 ON
ISCHILDNODE(nodeSet3,nodeSet2) WHERE NAME(nodeSet2) = 'a' AND
CONTAINS(nodeSet3.b,'bar')"));
+ }
+
+ // @Test
+ // public void shouldParseXPathExpressions() {
+ // assertParsable("/jcr:root/a/b/c");
+ // assertParsable("/jcr:root/a/b/c[*]");
+ // assertParsable("/jcr:root/some[1]/element(nodes, my:type)[1]");
+ // assertParsable("//element(*,my:type)");
+ // assertParsable("//element(*,my:type)[@jcr:title='something' and
@globalProperty='something else']");
+ // assertParsable("//element(*,my:type)[@jcr:title | @globalProperty]");
+ // assertParsable("//element(*, my:type) order by @my:title");
+ // assertParsable("//element(*, my:type) [jcr:contains(., 'jcr')] order
by jcr:score() descending");
+ // assertParsable("//element(*, employee)[@secretary and @assistant]");
+ // }
+
+ //
----------------------------------------------------------------------------------------------------------------
+ // utility methods
+ //
----------------------------------------------------------------------------------------------------------------
+
+ protected void printSqlFor( String xpath ) {
+ System.out.println("XPath: " + xpath);
+ System.out.println("SQL: " + translateToSql(xpath));
+ System.out.println();
+ }
+
+ private QueryCommand translateToSql( String xpath ) {
+ Component component = parser.parseXPath(xpath);
+ XPathToQueryTranslator translator = new XPathToQueryTranslator(context, xpath);
+ return translator.createQuery(component);
+ }
+
+ private QueryCommand xpath( String xpath ) {
+ Component component = parser.parseXPath(xpath);
+ XPathToQueryTranslator translator = new XPathToQueryTranslator(context, xpath);
+ return translator.createQuery(component);
+ }
+
+ protected QueryCommand sql( String sql ) {
+ return new SqlQueryParser().parseQuery(sql, context);
+ }
+
+ protected Matcher<QueryCommand> isSql( String sql ) {
+ return is(sql(sql));
+ }
+
+}
Property changes on:
trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/xpath/XPathToQueryTranslatorTest.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Modified: trunk/dna-search/src/main/java/org/jboss/dna/search/LuceneQueryEngine.java
===================================================================
--- trunk/dna-search/src/main/java/org/jboss/dna/search/LuceneQueryEngine.java 2009-10-03
04:07:41 UTC (rev 1285)
+++ trunk/dna-search/src/main/java/org/jboss/dna/search/LuceneQueryEngine.java 2009-10-03
04:09:29 UTC (rev 1286)
@@ -25,7 +25,10 @@
import java.io.IOException;
import java.util.LinkedList;
+import java.util.Set;
import org.apache.lucene.queryParser.ParseException;
+import org.jboss.dna.common.text.ParsingException;
+import org.jboss.dna.common.util.CheckArg;
import org.jboss.dna.graph.query.QueryContext;
import org.jboss.dna.graph.query.QueryEngine;
import org.jboss.dna.graph.query.QueryResults;
@@ -34,6 +37,8 @@
import org.jboss.dna.graph.query.optimize.Optimizer;
import org.jboss.dna.graph.query.optimize.OptimizerRule;
import org.jboss.dna.graph.query.optimize.RuleBasedOptimizer;
+import org.jboss.dna.graph.query.parse.InvalidQueryException;
+import org.jboss.dna.graph.query.parse.QueryParser;
import org.jboss.dna.graph.query.plan.CanonicalPlanner;
import org.jboss.dna.graph.query.plan.PlanHints;
import org.jboss.dna.graph.query.plan.PlanNode;
@@ -53,10 +58,66 @@
engine = new QueryEngine(new CanonicalPlanner(), new LuceneOptimizer(), new
LuceneProcessor(), schemata);
}
+ public LuceneQueryEngine( Schemata schemata,
+ QueryParser... languages ) {
+ engine = new QueryEngine(new CanonicalPlanner(), new LuceneOptimizer(), new
LuceneProcessor(), schemata, languages);
+ }
+
/**
+ * Add a language to this engine by supplying its parser.
+ *
+ * @param languageParser the query parser for the language
+ * @throws IllegalArgumentException if the language parser is null
+ */
+ public void addLanguage( QueryParser languageParser ) {
+ this.engine.addLanguage(languageParser);
+ }
+
+ /**
+ * Remove from this engine the language with the given name.
+ *
+ * @param language the name of the language, which is to match the {@link
QueryParser#getLanguage() language} of the parser
+ * @return the parser for the language, or null if the engine had no support for the
named language
+ * @throws IllegalArgumentException if the language is null
+ */
+ public QueryParser removeLanguage( String language ) {
+ return this.engine.removeLanguage(language);
+ }
+
+ /**
+ * Get the set of languages that this engine is capable of parsing.
+ *
+ * @return the unmodifiable copy of the set of languages; never null but possibly
empty
+ */
+ public Set<String> getLanguages() {
+ return this.engine.getLanguages();
+ }
+
+ /**
* Execute the supplied query by planning, optimizing, and then processing it.
*
+ * @param indexes the context in which the query should be executed
+ * @param language the language in which the query is expressed; must be one of the
supported {@link #getLanguages()
+ * languages}
* @param query the query that is to be executed
+ * @return the query results; never null
+ * @throws IllegalArgumentException if the language, context or query references are
null, or if the language is not know
+ * @throws ParsingException if there is an error parsing the supplied query
+ * @throws InvalidQueryException if the supplied query can be parsed but is invalid
+ */
+ public QueryResults execute( IndexContext indexes,
+ String language,
+ String query ) {
+ CheckArg.isNotNull(language, "language");
+ CheckArg.isNotNull(indexes, "indexes");
+ CheckArg.isNotNull(query, "query");
+ return engine.execute(indexes.context(), language, query);
+ }
+
+ /**
+ * Execute the supplied query by planning, optimizing, and then processing it.
+ *
+ * @param query the query that is to be executed
* @param indexes the indexes that should be used to execute the query; never null
* @return the query results; never null
* @throws IllegalArgumentException if the context or query references are null