Author: rhauch
Date: 2010-01-06 18:48:32 -0500 (Wed, 06 Jan 2010)
New Revision: 1536
Added:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/ArithmeticOperand.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/ArithmeticOperator.java
Modified:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/GraphI18n.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/QueryResults.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/DynamicOperand.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/FullTextSearchScore.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Length.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/LowerCase.java
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/NodeLocalName.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/NodeName.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/model/PropertyValue.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/UpperCase.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/parse/SqlQueryParser.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/plan/CanonicalPlanner.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/plan/PlanHints.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/plan/PlanUtil.java
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/QueryProcessor.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/QueryResults.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/SortValuesComponent.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/validate/Validator.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/optimize/RuleBasedOptimizerTest.java
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/plan/CanonicalPlannerTest.java
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/process/QueryResultColumnsTest.java
trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrQueryManager.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/main/java/org/jboss/dna/jcr/xpath/XPathToQueryTranslator.java
trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/JcrQueryManagerTest.java
trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/JcrTckTest.java
trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/xpath/XPathParserTest.java
trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/xpath/XPathToQueryTranslatorTest.java
trunk/dna-jcr/src/test/resources/tck/repositoryForTckTests.xml
Log:
DNA-613 One problem was when there was an ORDER BY SCORE(selectorName) when the selector
was a view that joined multiple tables. In this case, the score function would need to be
resolved against the scores from multiple source tables. The existing FullTextSearchScore
subclass of DynamicOperand required a single selector name, so it was not possible to
represent a single DynamicOperand that represented the score from multiple tables.
One option considered was the creation of a FullTextSearchCompositeScore that would accept
multiple selector names. Unfortunately, DynamicOperand makes the assumption that it only
applies to a single selector, and thus has a 'getSelectorName()' method. This
could be changed to no longer make this assumption (as well as adjust all places that use
this method), but the composite score DynamicOperand seems to be a bit of a hack.
Instead, I decided that it was better to support a DynamicOperand that represented an
arithmetic operation between two other DynamicOperands, and thus there is a new
ArithmeticOperand class that contains a left DynamicOperand, an arithmetic operator, and a
right DynamicOperand. And of course, ArithmeticOperands can be nested inside each other
to create relatively complex operations. Because an ArithmeticOperand applies to two
DynamicOperands, those DynamicOperands might apply to different selectors. Thus, this
approach also required changing DynamicOperand.getSelectorName():SelectorName to
getSelectorNames():Set<SelectorName>. Also, it was critical for this set to
maintain the order of insertion of the set, so a LinkedHashSet was used.
This addition required changes to the SQL parser, the planner, and the optimizer (along
with the corresponding tests). The change to multiple selector names for DynamicOperand
also required changes to all other DynamicOperand subclasses, and the code that used these
methods.
Another change was made to the PlanHints so that a string representation of the query plan
could be requested and placed in the results. In the JCR layer, this was leveraged by
always requesting the plan and then making the string representation of the plan publicly
available on the JcrQueryResult class.
Finally, there were changes made to the way the XPath-to-SQL translator works (along with
the test cases).
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 2010-01-06 22:13:11
UTC (rev 1535)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/GraphI18n.java 2010-01-06 23:48:32
UTC (rev 1536)
@@ -143,6 +143,8 @@
public static I18n columnDoesNotExistInQuery;
public static I18n columnIsNotFullTextSearchable;
public static I18n tableIsNotFullTextSearchable;
+ public static I18n columnTypeCannotBeUsedInArithmeticOperation;
+ public static I18n dynamicOperandCannotBeUsedInArithmeticOperation;
public static I18n selectorDoesNotExistInQuery;
public static I18n propertyOnSelectorIsNotUsedInQuery;
public static I18n errorResolvingNodesFromLocationsUsingSourceAndWorkspace;
@@ -167,6 +169,7 @@
public static I18n expectingLiteralAndUnableToParseAsLong;
public static I18n expectingLiteralAndUnableToParseAsDouble;
public static I18n expectingLiteralAndUnableToParseAsDate;
+ public static I18n unexpectedClosingParenthesis;
/* Search */
public static I18n interruptedWhileClosingChannel;
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 2010-01-06
22:13:11 UTC (rev 1535)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/QueryBuilder.java 2010-01-06
23:48:32 UTC (rev 1536)
@@ -41,6 +41,8 @@
import org.jboss.dna.graph.property.PropertyType;
import org.jboss.dna.graph.query.model.AllNodes;
import org.jboss.dna.graph.query.model.And;
+import org.jboss.dna.graph.query.model.ArithmeticOperand;
+import org.jboss.dna.graph.query.model.ArithmeticOperator;
import org.jboss.dna.graph.query.model.Between;
import org.jboss.dna.graph.query.model.BindVariableName;
import org.jboss.dna.graph.query.model.ChildNode;
@@ -69,6 +71,7 @@
import org.jboss.dna.graph.query.model.Not;
import org.jboss.dna.graph.query.model.Operator;
import org.jboss.dna.graph.query.model.Or;
+import org.jboss.dna.graph.query.model.Order;
import org.jboss.dna.graph.query.model.Ordering;
import org.jboss.dna.graph.query.model.PropertyExistence;
import org.jboss.dna.graph.query.model.PropertyValue;
@@ -703,6 +706,17 @@
}
/**
+ * Obtain a builder that will create the order-by clause (with one or more {@link
Ordering} statements) for the query. This
+ * method need be called only once to build the order-by clause, but can be called
multiple times (it merely adds additional
+ * {@link Ordering} statements).
+ *
+ * @return the order-by builder; never null
+ */
+ public OrderByBuilder orderBy() {
+ return new OrderByBuilder();
+ }
+
+ /**
* Return a {@link QueryCommand} representing the currently-built query.
*
* @return the resulting query command; never null
@@ -726,7 +740,256 @@
return result;
}
+ public interface OrderByOperandBuilder {
+ /**
+ * Adds to the order-by clause by using the length of the value for the given
table and 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
+ * @param property the name of the property; may not be null and must refer to a
valid property name
+ * @return the interface for completing the order-by specification; never null
+ */
+ public OrderByBuilder length( String table,
+ String property );
+
+ /**
+ * Adds to the order-by clause by using the value for the given table and
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
+ * @param property the name of the property; may not be null and must refer to a
valid property name
+ * @return the interface for completing the order-by specification; never null
+ */
+ public OrderByBuilder propertyValue( String table,
+ String property );
+
+ /**
+ * Adds to the order-by clause by using the full-text search score for the given
table.
+ *
+ * @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 order-by specification; never null
+ */
+ public OrderByBuilder fullTextSearchScore( String table );
+
+ /**
+ * Adds to the order-by clause by using the depth of the node given by the named
table.
+ *
+ * @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 order-by specification; never null
+ */
+ public OrderByBuilder depth( String table );
+
+ /**
+ * Adds to the order-by clause by using the path of the node given by the named
table.
+ *
+ * @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 order-by specification; never null
+ */
+ public OrderByBuilder path( String table );
+
+ /**
+ * Adds to the order-by clause by using the local name of the node given by the
named table.
+ *
+ * @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 order-by specification; never null
+ */
+ public OrderByBuilder nodeLocalName( String table );
+
+ /**
+ * Adds to the order-by clause by using the node name (including namespace) of
the node given by the named table.
+ *
+ * @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 order-by specification; never null
+ */
+ public OrderByBuilder nodeName( String table );
+
+ /**
+ * Adds to the order-by clause by using the uppercase form of the next operand.
+ *
+ * @return the interface for completing the order-by specification; never null
+ */
+ public OrderByOperandBuilder upperCaseOf();
+
+ /**
+ * Adds to the order-by clause by using the lowercase form of the next operand.
+ *
+ * @return the interface for completing the order-by specification; never null
+ */
+ public OrderByOperandBuilder lowerCaseOf();
+ }
+
/**
+ * The component used to build the order-by clause. When the clause is completed,
{@link #end()} should be called to return to
+ * the {@link QueryBuilder} instance.
+ */
+ public class OrderByBuilder {
+
+ protected OrderByBuilder() {
+ }
+
+ /**
+ * Begin specifying an order-by specification using {@link Order#ASCENDING
ascending order}.
+ *
+ * @return the interface for specifying the operand that is to be ordered; never
null
+ */
+ public OrderByOperandBuilder ascending() {
+ return new SingleOrderByOperandBuilder(this, Order.ASCENDING);
+ }
+
+ /**
+ * Begin specifying an order-by specification using {@link Order#DESCENDING
descending order}.
+ *
+ * @return the interface for specifying the operand that is to be ordered; never
null
+ */
+ public OrderByOperandBuilder descending() {
+ return new SingleOrderByOperandBuilder(this, Order.DESCENDING);
+ }
+
+ /**
+ * An optional convenience method that returns this builder, but which makes the
code using this builder more readable.
+ *
+ * @return this builder; never null
+ */
+ public OrderByBuilder then() {
+ return this;
+ }
+
+ /**
+ * Complete the order-by clause and return the QueryBuilder instance.
+ *
+ * @return the query builder instance; never null
+ */
+ public QueryBuilder end() {
+ return QueryBuilder.this;
+ }
+ }
+
+ protected class SingleOrderByOperandBuilder implements OrderByOperandBuilder {
+ private final Order order;
+ private final OrderByBuilder builder;
+
+ protected SingleOrderByOperandBuilder( OrderByBuilder builder,
+ Order order ) {
+ this.order = order;
+ this.builder = builder;
+ }
+
+ protected OrderByBuilder addOrdering( DynamicOperand operand ) {
+ Ordering ordering = new Ordering(operand, order);
+ QueryBuilder.this.orderings.add(ordering);
+ return builder;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.QueryBuilder.OrderByOperandBuilder#propertyValue(java.lang.String,
java.lang.String)
+ */
+ public OrderByBuilder propertyValue( String table,
+ String property ) {
+ return addOrdering(new PropertyValue(selector(table), property));
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.QueryBuilder.OrderByOperandBuilder#length(java.lang.String,
java.lang.String)
+ */
+ public OrderByBuilder length( String table,
+ String property ) {
+ return addOrdering(new Length(new PropertyValue(selector(table),
property)));
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.QueryBuilder.OrderByOperandBuilder#fullTextSearchScore(java.lang.String)
+ */
+ public OrderByBuilder fullTextSearchScore( String table ) {
+ return addOrdering(new FullTextSearchScore(selector(table)));
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.QueryBuilder.OrderByOperandBuilder#depth(java.lang.String)
+ */
+ public OrderByBuilder depth( String table ) {
+ return addOrdering(new NodeDepth(selector(table)));
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.QueryBuilder.OrderByOperandBuilder#path(java.lang.String)
+ */
+ public OrderByBuilder path( String table ) {
+ return addOrdering(new NodePath(selector(table)));
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.QueryBuilder.OrderByOperandBuilder#nodeName(java.lang.String)
+ */
+ public OrderByBuilder nodeName( String table ) {
+ return addOrdering(new NodeName(selector(table)));
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.QueryBuilder.OrderByOperandBuilder#nodeLocalName(java.lang.String)
+ */
+ public OrderByBuilder nodeLocalName( String table ) {
+ return addOrdering(new NodeLocalName(selector(table)));
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.QueryBuilder.OrderByOperandBuilder#lowerCaseOf()
+ */
+ public OrderByOperandBuilder lowerCaseOf() {
+ return new SingleOrderByOperandBuilder(builder, order) {
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.QueryBuilder.SingleOrderByOperandBuilder#addOrdering(org.jboss.dna.graph.query.model.DynamicOperand)
+ */
+ @Override
+ protected OrderByBuilder addOrdering( DynamicOperand operand ) {
+ return super.addOrdering(new LowerCase(operand));
+ }
+ };
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.QueryBuilder.OrderByOperandBuilder#upperCaseOf()
+ */
+ public OrderByOperandBuilder upperCaseOf() {
+ return new SingleOrderByOperandBuilder(builder, order) {
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.QueryBuilder.SingleOrderByOperandBuilder#addOrdering(org.jboss.dna.graph.query.model.DynamicOperand)
+ */
+ @Override
+ protected OrderByBuilder addOrdering( DynamicOperand operand ) {
+ return super.addOrdering(new UpperCase(operand));
+ }
+ };
+ }
+ }
+
+ /**
* Class used to specify a join clause of a query.
*
* @see QueryBuilder#join(String)
@@ -932,7 +1195,6 @@
* @return the interface for completing the criteria specification; never null
*/
public DynamicOperandBuilder lowerCaseOf();
-
}
public class ConstraintBuilder implements DynamicOperandBuilder {
@@ -975,9 +1237,12 @@
* Complete the specification of a constraint clause, and return the builder for
the parent constraint clause.
*
* @return the constraint builder that was used to create this parenthetical
constraint clause builder; never null
+ * @throws IllegalStateException if there was not an {@link #openParen() open
parenthesis} to close
*/
public ConstraintBuilder closeParen() {
- assert parent != null;
+ if (parent == null) {
+ throw new
IllegalStateException(GraphI18n.unexpectedClosingParenthesis.text());
+ }
buildLogicalConstraint();
return parent.setConstraint(constraint);
}
@@ -1125,6 +1390,10 @@
return setConstraint(new FullTextSearch(selector(table), propertyName,
searchExpression));
}
+ protected ComparisonBuilder comparisonBuilder( DynamicOperand operand ) {
+ return new ComparisonBuilder(this, operand);
+ }
+
/**
* {@inheritDoc}
*
@@ -1132,7 +1401,7 @@
*/
public ComparisonBuilder length( String table,
String property ) {
- return new ComparisonBuilder(this, new Length(new
PropertyValue(selector(table), property)));
+ return comparisonBuilder(new Length(new PropertyValue(selector(table),
property)));
}
/**
@@ -1142,7 +1411,7 @@
*/
public ComparisonBuilder propertyValue( String table,
String property ) {
- return new ComparisonBuilder(this, new PropertyValue(selector(table),
property));
+ return comparisonBuilder(new PropertyValue(selector(table), property));
}
/**
@@ -1151,7 +1420,7 @@
* @see
org.jboss.dna.graph.query.QueryBuilder.DynamicOperandBuilder#fullTextSearchScore(String)
*/
public ComparisonBuilder fullTextSearchScore( String table ) {
- return new ComparisonBuilder(this, new
FullTextSearchScore(selector(table)));
+ return comparisonBuilder(new FullTextSearchScore(selector(table)));
}
/**
@@ -1160,7 +1429,7 @@
* @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)));
+ return comparisonBuilder(new NodeDepth(selector(table)));
}
/**
@@ -1169,7 +1438,7 @@
* @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)));
+ return comparisonBuilder(new NodePath(selector(table)));
}
/**
@@ -1178,7 +1447,7 @@
* @see
org.jboss.dna.graph.query.QueryBuilder.DynamicOperandBuilder#nodeLocalName(String)
*/
public ComparisonBuilder nodeLocalName( String table ) {
- return new ComparisonBuilder(this, new NodeLocalName(selector(table)));
+ return comparisonBuilder(new NodeLocalName(selector(table)));
}
/**
@@ -1187,7 +1456,7 @@
* @see
org.jboss.dna.graph.query.QueryBuilder.DynamicOperandBuilder#nodeName(String)
*/
public ComparisonBuilder nodeName( String table ) {
- return new ComparisonBuilder(this, new NodeName(selector(table)));
+ return comparisonBuilder(new NodeName(selector(table)));
}
/**
@@ -1365,7 +1634,8 @@
*/
@Override
public ConstraintBuilder as( String type ) {
- return upperBoundary.comparisonBuilder.isBetween(upperBoundary.lowerBound,
typeSystem.getTypeFactory(type).create(value));
+ return upperBoundary.comparisonBuilder.isBetween(upperBoundary.lowerBound,
typeSystem.getTypeFactory(type)
+
.create(value));
}
}
@@ -2172,6 +2442,124 @@
}
}
+ public class ArithmeticBuilder {
+ protected final ArithmeticBuilder parent;
+ protected final ArithmeticOperator operator;
+ protected DynamicOperand left;
+ protected final ComparisonBuilder comparisonBuilder;
+
+ protected ArithmeticBuilder( ArithmeticOperator operator,
+ ComparisonBuilder comparisonBuilder,
+ DynamicOperand left,
+ ArithmeticBuilder parent ) {
+ this.operator = operator;
+ this.left = left;
+ this.comparisonBuilder = comparisonBuilder;
+ this.parent = parent; // may be null
+ }
+
+ /**
+ * Constrains the nodes in the the supplied table such that they must have a
property value whose length matches the
+ * criteria.
+ *
+ * @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
+ * @param property the name of the property; may not be null and must refer to a
valid property name
+ * @return the interface for completing the value portion of the criteria
specification; never null
+ */
+ public ComparisonBuilder length( String table,
+ String property ) {
+ return comparisonBuilder(new Length(new PropertyValue(selector(table),
property)));
+ }
+
+ /**
+ * Constrains the nodes in the the supplied table such that they must have a
matching 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
+ * @param property the name of the property; may not be null and must refer to a
valid property name
+ * @return the interface for completing the value portion of the criteria
specification; never null
+ */
+ public ComparisonBuilder propertyValue( String table,
+ String property ) {
+ return comparisonBuilder(new PropertyValue(selector(table), property));
+ }
+
+ /**
+ * Constrains the nodes in the the supplied table such that they must satisfy the
supplied full-text search on the nodes'
+ * property values.
+ *
+ * @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 fullTextSearchScore( String table ) {
+ return comparisonBuilder(new FullTextSearchScore(selector(table)));
+ }
+
+ /**
+ * 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 ) {
+ return comparisonBuilder(new NodeDepth(selector(table)));
+ }
+
+ // /**
+ // * Simulate the use of an open parenthesis in the constraint. The resulting
builder should be used to define the
+ // * constraint within the parenthesis, and should always be terminated with a
{@link #closeParen()}.
+ // *
+ // * @return the constraint builder that should be used to define the portion of
the constraint within the parenthesis;
+ // * never null
+ // * @see #closeParen()
+ // */
+ // public ArithmeticBuilder openParen() {
+ // return new ArithmeticBuilder(operator, comparisonBuilder, left, this);
+ // }
+ //
+ // /**
+ // * Complete the specification of a constraint clause, and return the builder
for the parent constraint clause.
+ // *
+ // * @return the constraint builder that was used to create this parenthetical
constraint clause builder; never null
+ // * @throws IllegalStateException if there was not an {@link #openParen() open
parenthesis} to close
+ // */
+ // public ComparisonBuilder closeParen() {
+ // if (parent == null) {
+ // throw new
IllegalStateException(GraphI18n.unexpectedClosingParenthesis.text());
+ // }
+ // buildLogicalConstraint();
+ // return parent.setLeft(left).comparisonBuilder;
+ // }
+ // protected ArithmeticBuilder setLeft( DynamicOperand left ) {
+ // this.left = left;
+ // return this;
+ // }
+
+ protected ComparisonBuilder comparisonBuilder( DynamicOperand right ) {
+ DynamicOperand leftOperand = null;
+ // If the left operand is an arithmetic operand, then we need to check the
operator precedence ...
+ if (left instanceof ArithmeticOperand) {
+ ArithmeticOperand leftArith = (ArithmeticOperand)left;
+ ArithmeticOperator operator = leftArith.getOperator();
+ if (this.operator.precedes(operator)) {
+ // Need to do create an operand with leftArith.right and right
+ DynamicOperand inner = new ArithmeticOperand(leftArith.getRight(),
this.operator, right);
+ leftOperand = new ArithmeticOperand(leftArith.getLeft(), operator,
inner);
+ } else {
+ // the left preceds this, so we can add the new operand on top ...
+ leftOperand = new ArithmeticOperand(leftArith, operator, right);
+ }
+ } else {
+ // The left isn't an arith ...
+ leftOperand = new ArithmeticOperand(left, operator, right);
+ }
+ return new ComparisonBuilder(comparisonBuilder.constraintBuilder,
leftOperand);
+ }
+ }
+
/**
* An interface used to set the right-hand side of a constraint.
*/
@@ -2197,6 +2585,26 @@
}
/**
+ * Create a comparison object based upon the addition of the
previously-constructed {@link DynamicOperand} and the next
+ * DynamicOperand to be created with the supplied builder.
+ *
+ * @return the builder that should be used to create the right-hand-side of the
operation; never null
+ */
+ public ArithmeticBuilder plus() {
+ return new ArithmeticBuilder(ArithmeticOperator.ADD, this, left, null);
+ }
+
+ /**
+ * Create a comparison object based upon the subtraction of the next {@link
DynamicOperand} (created using the builder
+ * returned from this method) from the the previously-constructed DynamicOperand
to be created with the supplied builder.
+ *
+ * @return the builder that should be used to create the right-hand-side of the
operation; never null
+ */
+ public ArithmeticBuilder minus() {
+ return new ArithmeticBuilder(ArithmeticOperator.SUBTRACT, this, left, 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.
*
Modified: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/QueryResults.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/QueryResults.java 2010-01-06
22:13:11 UTC (rev 1535)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/QueryResults.java 2010-01-06
23:48:32 UTC (rev 1536)
@@ -71,6 +71,13 @@
public int getRowCount();
/**
+ * Get a description of the query plan, if requested.
+ *
+ * @return the query plan, or null if the plan was not requested
+ */
+ public String getPlan();
+
+ /**
* Get the problems encountered during execution.
*
* @return the problems; never null but possibly empty
Copied:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/ArithmeticOperand.java (from
rev 1535,
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/PropertyValue.java)
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/ArithmeticOperand.java
(rev 0)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/ArithmeticOperand.java 2010-01-06
23:48:32 UTC (rev 1536)
@@ -0,0 +1,133 @@
+/*
+ * 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;
+import org.jboss.dna.common.util.HashCode;
+
+/**
+ * A dynamic operand that represents a (binary) arithmetic operation upon one or more
other operands, used in {@link Comparison}
+ * and {@link Ordering} components.
+ */
+@Immutable
+public class ArithmeticOperand extends DynamicOperand {
+ private static final long serialVersionUID = 1L;
+
+ private final ArithmeticOperator operator;
+ private final DynamicOperand left;
+ private final DynamicOperand right;
+ private final int hc;
+
+ /**
+ * Create a arithmetic dynamic operand that operates upon the supplied operand(s).
+ *
+ * @param left the left-hand-side operand
+ * @param operator the arithmetic operator; may not be null
+ * @param right the right-hand-side operand
+ * @throws IllegalArgumentException if any of the arguments is null
+ */
+ public ArithmeticOperand( DynamicOperand left,
+ ArithmeticOperator operator,
+ DynamicOperand right ) {
+ super(left, right);
+ CheckArg.isNotNull(operator, "operator");
+ this.operator = operator;
+ this.left = left;
+ this.right = right;
+ this.hc = HashCode.compute(left, operator, right);
+ }
+
+ /**
+ * Get the operator for this binary operand.
+ *
+ * @return the operator; never null
+ */
+ public ArithmeticOperator getOperator() {
+ return operator;
+ }
+
+ /**
+ * Get the left-hand operand.
+ *
+ * @return the left-hand operator; never null
+ */
+ public DynamicOperand getLeft() {
+ return left;
+ }
+
+ /**
+ * Get the right-hand operand.
+ *
+ * @return the right-hand operator; never null
+ */
+ public DynamicOperand getRight() {
+ return right;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return Visitors.readable(this);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#hashCode()
+ */
+ @Override
+ public int hashCode() {
+ return hc;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals( Object obj ) {
+ if (obj == this) return true;
+ if (obj instanceof ArithmeticOperand) {
+ ArithmeticOperand that = (ArithmeticOperand)obj;
+ return this.getOperator() == that.getOperator() &&
this.getLeft().equals(that.getLeft())
+ && this.getRight().equals(that.getRight());
+ }
+ 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);
+ }
+}
Added:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/ArithmeticOperator.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/ArithmeticOperator.java
(rev 0)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/ArithmeticOperator.java 2010-01-06
23:48:32 UTC (rev 1536)
@@ -0,0 +1,140 @@
+/*
+ * 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 java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import org.jboss.dna.common.util.CheckArg;
+
+/**
+ * The arithmetic operators.
+ */
+public enum ArithmeticOperator {
+
+ ADD("+", Arity.BINARY, 20),
+ SUBTRACT("-", Arity.BINARY, 19),
+ MULTIPLY("*", Arity.BINARY, 18),
+ DIVIDE("/", Arity.BINARY, 17);
+
+ public static enum Arity {
+ UNARY,
+ BINARY;
+ }
+
+ private static final Map<String, ArithmeticOperator> OPERATORS_BY_SYMBOL;
+ static {
+ Map<String, ArithmeticOperator> opsBySymbol = new HashMap<String,
ArithmeticOperator>();
+ for (ArithmeticOperator operator : ArithmeticOperator.values()) {
+ opsBySymbol.put(operator.getSymbol(), operator);
+ }
+ OPERATORS_BY_SYMBOL = Collections.unmodifiableMap(opsBySymbol);
+ }
+
+ private final String symbol;
+ private final Arity arity;
+ private final int precedence;
+
+ private ArithmeticOperator( String symbol,
+ Arity arity,
+ int precedence ) {
+ this.symbol = symbol;
+ this.arity = arity;
+ this.precedence = precedence;
+ }
+
+ /**
+ * Get the symbol for this operator
+ *
+ * @return the symbolic representation; never null
+ */
+ public String getSymbol() {
+ return symbol;
+ }
+
+ /**
+ * Get the 'arity' of the operator.
+ *
+ * @return the number of parameters required
+ * @see #isUnary()
+ * @see #isBinary()
+ */
+ public Arity getArity() {
+ return arity;
+ }
+
+ /**
+ * Return whether this is an unary operator.
+ *
+ * @return true if this operator is unary, or false otherwise
+ * @see #getArity()
+ * @see #isBinary()
+ */
+ public boolean isUnary() {
+ return arity == Arity.UNARY;
+ }
+
+ /**
+ * Return whether this is an binary operator.
+ *
+ * @return true if this operator is binary, or false otherwise
+ * @see #getArity()
+ * @see #isUnary()
+ */
+ public boolean isBinary() {
+ return arity == Arity.BINARY;
+ }
+
+ /**
+ * Determine whether this operator has a higher precedence than the supplied
operator.
+ *
+ * @param operator the other operator; may not be null
+ * @return true if this operator has a higher precedence, or false otherwise
+ */
+ public boolean precedes( ArithmeticOperator operator ) {
+ return this.precedence > operator.precedence;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Enum#toString()
+ */
+ @Override
+ public String toString() {
+ return symbol;
+ }
+
+ /**
+ * Attempt to find the Operator given a symbol. The matching is done independent of
case.
+ *
+ * @param symbol the symbol
+ * @return the Operator having the supplied symbol, or null if there is no Operator
with the supplied symbol
+ * @throws IllegalArgumentException if the symbol is null
+ */
+ public static ArithmeticOperator forSymbol( String symbol ) {
+ CheckArg.isNotNull(symbol, "symbol");
+ return OPERATORS_BY_SYMBOL.get(symbol.toUpperCase());
+ }
+}
Property changes on:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/ArithmeticOperator.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Modified:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/DynamicOperand.java
===================================================================
---
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/DynamicOperand.java 2010-01-06
22:13:11 UTC (rev 1535)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/DynamicOperand.java 2010-01-06
23:48:32 UTC (rev 1536)
@@ -23,7 +23,13 @@
*/
package org.jboss.dna.graph.query.model;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.Set;
import net.jcip.annotations.Immutable;
+import org.jboss.dna.common.util.CheckArg;
/**
* A dynamic operand used in a {@link Comparison} constraint.
@@ -32,10 +38,87 @@
public abstract class DynamicOperand implements LanguageObject {
private static final long serialVersionUID = 1L;
+ private final Set<SelectorName> selectorNames;
+
/**
- * Get the selector symbol to which this operand applies.
+ * Create a arithmetic dynamic operand that operates upon the supplied selector
name(s).
*
- * @return the selector name; never null
+ * @param selectorNames the selector names
+ * @throws IllegalArgumentException if the selector names array is null or empty, or
if any of the values are null
*/
- public abstract SelectorName getSelectorName();
+ protected DynamicOperand( SelectorName... selectorNames ) {
+ CheckArg.isNotNull(selectorNames, "selectorNames");
+ if (selectorNames.length == 1) {
+ CheckArg.isNotNull(selectorNames, "selectorNames[0]");
+ this.selectorNames = Collections.singleton(selectorNames[0]);
+ } else {
+ CheckArg.isNotNull(selectorNames, "selectorNames[0]");
+ this.selectorNames = Collections.unmodifiableSet(new
LinkedHashSet<SelectorName>(Arrays.asList(selectorNames)));
+ int i = 0;
+ for (SelectorName name : this.selectorNames) {
+ CheckArg.isNotNull(name, "selectorNames[" + i++ +
"]");
+ }
+ }
+ }
+
+ /**
+ * Create a arithmetic dynamic operand that operates upon the supplied selector
name(s).
+ *
+ * @param selectorNames the selector names
+ * @throws IllegalArgumentException if the name list is null or empty, or if any of
the values are null
+ */
+ protected DynamicOperand( Collection<SelectorName> selectorNames ) {
+ CheckArg.isNotNull(selectorNames, "selectorName");
+ this.selectorNames = Collections.unmodifiableSet(new
LinkedHashSet<SelectorName>(selectorNames));
+ }
+
+ /**
+ * Create a arithmetic dynamic operand that operates upon the selector names given by
the supplied dynamic operand(s).
+ *
+ * @param operand the operand defining the selector names
+ * @throws IllegalArgumentException if the operand is null
+ */
+ protected DynamicOperand( DynamicOperand operand ) {
+ CheckArg.isNotNull(operand, "operand");
+ this.selectorNames = operand.getSelectorNames(); // immutable, so we can
reference it directly
+ }
+
+ /**
+ * Create a arithmetic dynamic operand that operates upon the selector names given by
the supplied dynamic operand(s).
+ *
+ * @param operands the operands defining the selector names
+ * @throws IllegalArgumentException if the operand is null
+ */
+ protected DynamicOperand( Iterable<? extends DynamicOperand> operands ) {
+ CheckArg.isNotNull(operands, "operands");
+ Set<SelectorName> names = new LinkedHashSet<SelectorName>();
+ for (DynamicOperand operand : operands) {
+ names.addAll(operand.getSelectorNames());
+ }
+ this.selectorNames = Collections.unmodifiableSet(names);
+ }
+
+ /**
+ * Create a arithmetic dynamic operand that operates upon the selector names given by
the supplied dynamic operand(s).
+ *
+ * @param operands the operands defining the selector names
+ * @throws IllegalArgumentException if the operand is null
+ */
+ protected DynamicOperand( DynamicOperand... operands ) {
+ CheckArg.isNotNull(operands, "operands");
+ Set<SelectorName> names = new LinkedHashSet<SelectorName>();
+ for (DynamicOperand operand : operands) {
+ names.addAll(operand.getSelectorNames());
+ }
+ this.selectorNames = Collections.unmodifiableSet(names);
+ }
+
+ /**
+ * Get the selector symbols to which this operand applies.
+ *
+ * @return the immutable ordered set of non-null selector names used by this operand;
never null and never empty
+ */
+ public Set<SelectorName> getSelectorNames() {
+ return selectorNames;
+ }
}
Modified:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/FullTextSearchScore.java
===================================================================
---
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/FullTextSearchScore.java 2010-01-06
22:13:11 UTC (rev 1535)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/FullTextSearchScore.java 2010-01-06
23:48:32 UTC (rev 1536)
@@ -24,36 +24,31 @@
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 full-text search score of a node given by a
selector, used in a {@link Comparison}
- * constraint.
+ * constraint and {@link Ordering}s.
*/
@Immutable
public class FullTextSearchScore extends DynamicOperand {
private static final long serialVersionUID = 1L;
- private final SelectorName selectorName;
-
/**
* Create a dynamic operand that evaluates to the full-text search score of the node
identified by the selector.
*
* @param selectorName the name of the selector
*/
public FullTextSearchScore( SelectorName selectorName ) {
- CheckArg.isNotNull(selectorName, "selectorName");
- this.selectorName = selectorName;
+ super(selectorName);
}
/**
- * {@inheritDoc}
+ * Get the selector symbol upon which this operand applies.
*
- * @see org.jboss.dna.graph.query.model.DynamicOperand#getSelectorName()
+ * @return the one selector names used by this operand; never null
*/
- @Override
public SelectorName getSelectorName() {
- return selectorName;
+ return getSelectorNames().iterator().next();
}
/**
@@ -73,7 +68,7 @@
*/
@Override
public int hashCode() {
- return getSelectorName().hashCode();
+ return getSelectorNames().hashCode();
}
/**
@@ -86,7 +81,7 @@
if (obj == this) return true;
if (obj instanceof FullTextSearchScore) {
FullTextSearchScore that = (FullTextSearchScore)obj;
- if (!this.selectorName.equals(that.selectorName)) return false;
+ if (!this.getSelectorNames().equals(that.getSelectorNames())) return false;
return true;
}
return false;
Modified: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Length.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Length.java 2010-01-06
22:13:11 UTC (rev 1535)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Length.java 2010-01-06
23:48:32 UTC (rev 1536)
@@ -24,7 +24,6 @@
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 length of the supplied propety values, used in
a {@link Comparison} constraint.
@@ -41,27 +40,26 @@
* @param propertyValue the property value operand
*/
public Length( PropertyValue propertyValue ) {
- CheckArg.isNotNull(propertyValue, "propertyValue");
+ super(propertyValue);
this.propertyValue = propertyValue;
}
/**
- * {@inheritDoc}
+ * Get the property value whose length is being constrained.
*
- * @see org.jboss.dna.graph.query.model.DynamicOperand#getSelectorName()
+ * @return the property value being constrained; never null
*/
- @Override
- public SelectorName getSelectorName() {
- return propertyValue.getSelectorName();
+ public final PropertyValue getPropertyValue() {
+ return propertyValue;
}
/**
- * Get the property value whose length is being constrained.
+ * Get the selector symbol upon which this operand applies.
*
- * @return the property value being constrained; never null
+ * @return the one selector names used by this operand; never null
*/
- public final PropertyValue getPropertyValue() {
- return propertyValue;
+ public SelectorName getSelectorName() {
+ return getSelectorNames().iterator().next();
}
/**
Modified: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/LowerCase.java
===================================================================
---
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/LowerCase.java 2010-01-06
22:13:11 UTC (rev 1535)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/LowerCase.java 2010-01-06
23:48:32 UTC (rev 1536)
@@ -24,7 +24,6 @@
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 lower-case representation of the supplied
operand, used in a {@link Comparison}
@@ -42,7 +41,7 @@
* @param operand the operand that is to be lower-cased
*/
public LowerCase( DynamicOperand operand ) {
- CheckArg.isNotNull(operand, "operand");
+ super(operand);
this.operand = operand;
}
@@ -56,13 +55,12 @@
}
/**
- * {@inheritDoc}
+ * Get the selector symbol upon which this operand applies.
*
- * @see org.jboss.dna.graph.query.model.DynamicOperand#getSelectorName()
+ * @return the one selector names used by this operand; never null
*/
- @Override
public SelectorName getSelectorName() {
- return operand.getSelectorName();
+ return getSelectorNames().iterator().next();
}
/**
Modified: 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/NodeDepth.java 2010-01-06
22:13:11 UTC (rev 1535)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/NodeDepth.java 2010-01-06
23:48:32 UTC (rev 1536)
@@ -24,7 +24,6 @@
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.
@@ -33,8 +32,6 @@
public class NodeDepth extends DynamicOperand {
private static final long serialVersionUID = 1L;
- private final SelectorName selectorName;
-
/**
* Create a dynamic operand that evaluates to the depth of the node identified by the
selector.
*
@@ -42,18 +39,16 @@
* @throws IllegalArgumentException if the selector name or property name are null
*/
public NodeDepth( SelectorName selectorName ) {
- CheckArg.isNotNull(selectorName, "selectorName");
- this.selectorName = selectorName;
+ super(selectorName);
}
/**
- * {@inheritDoc}
+ * Get the selector symbol upon which this operand applies.
*
- * @see org.jboss.dna.graph.query.model.DynamicOperand#getSelectorName()
+ * @return the one selector names used by this operand; never null
*/
- @Override
- public final SelectorName getSelectorName() {
- return selectorName;
+ public SelectorName getSelectorName() {
+ return getSelectorNames().iterator().next();
}
/**
@@ -73,7 +68,7 @@
*/
@Override
public int hashCode() {
- return getSelectorName().hashCode();
+ return getSelectorNames().hashCode();
}
/**
@@ -86,7 +81,7 @@
if (obj == this) return true;
if (obj instanceof NodeDepth) {
NodeDepth that = (NodeDepth)obj;
- return this.selectorName.equals(that.selectorName);
+ return this.getSelectorNames().equals(that.getSelectorNames());
}
return false;
}
Modified:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/NodeLocalName.java
===================================================================
---
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/NodeLocalName.java 2010-01-06
22:13:11 UTC (rev 1535)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/NodeLocalName.java 2010-01-06
23:48:32 UTC (rev 1536)
@@ -24,7 +24,6 @@
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 local name of a node given by a selector, used
in a {@link Comparison} constraint.
@@ -33,26 +32,22 @@
public class NodeLocalName extends DynamicOperand {
private static final long serialVersionUID = 1L;
- private final SelectorName selectorName;
-
/**
* Create a dynamic operand that evaluates to the local name of the node identified
by the selector.
*
* @param selectorName the name of the selector
*/
public NodeLocalName( SelectorName selectorName ) {
- CheckArg.isNotNull(selectorName, "selectorName");
- this.selectorName = selectorName;
+ super(selectorName);
}
/**
- * {@inheritDoc}
+ * Get the selector symbol upon which this operand applies.
*
- * @see org.jboss.dna.graph.query.model.DynamicOperand#getSelectorName()
+ * @return the one selector names used by this operand; never null
*/
- @Override
- public final SelectorName getSelectorName() {
- return selectorName;
+ public SelectorName getSelectorName() {
+ return getSelectorNames().iterator().next();
}
/**
@@ -72,7 +67,7 @@
*/
@Override
public int hashCode() {
- return getSelectorName().hashCode();
+ return getSelectorNames().hashCode();
}
/**
@@ -85,7 +80,7 @@
if (obj == this) return true;
if (obj instanceof NodeLocalName) {
NodeLocalName that = (NodeLocalName)obj;
- return this.selectorName.equals(that.selectorName);
+ return this.getSelectorNames().equals(that.getSelectorNames());
}
return false;
}
Modified: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/NodeName.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/NodeName.java 2010-01-06
22:13:11 UTC (rev 1535)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/NodeName.java 2010-01-06
23:48:32 UTC (rev 1536)
@@ -24,7 +24,6 @@
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 qualified name of a node given by a selector,
used in a {@link Comparison} constraint.
@@ -33,26 +32,22 @@
public class NodeName extends DynamicOperand {
private static final long serialVersionUID = 1L;
- private final SelectorName selectorName;
-
/**
* Create a dynamic operand that evaluates to the qualified name of the node
identified by the selector.
*
* @param selectorName the name of the selector
*/
public NodeName( SelectorName selectorName ) {
- CheckArg.isNotNull(selectorName, "selectorName");
- this.selectorName = selectorName;
+ super(selectorName);
}
/**
- * {@inheritDoc}
+ * Get the selector symbol upon which this operand applies.
*
- * @see org.jboss.dna.graph.query.model.DynamicOperand#getSelectorName()
+ * @return the one selector names used by this operand; never null
*/
- @Override
- public final SelectorName getSelectorName() {
- return selectorName;
+ public SelectorName getSelectorName() {
+ return getSelectorNames().iterator().next();
}
/**
@@ -72,7 +67,7 @@
*/
@Override
public int hashCode() {
- return getSelectorName().hashCode();
+ return getSelectorNames().hashCode();
}
/**
@@ -85,7 +80,7 @@
if (obj == this) return true;
if (obj instanceof NodeName) {
NodeName that = (NodeName)obj;
- return this.selectorName.equals(that.selectorName);
+ return this.getSelectorNames().equals(that.getSelectorNames());
}
return false;
}
Modified: 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/model/NodePath.java 2010-01-06
22:13:11 UTC (rev 1535)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/NodePath.java 2010-01-06
23:48:32 UTC (rev 1536)
@@ -24,7 +24,6 @@
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.
@@ -33,8 +32,6 @@
public class NodePath extends DynamicOperand {
private static final long serialVersionUID = 1L;
- private final SelectorName selectorName;
-
/**
* Create a dynamic operand that evaluates to the path of the node identified by the
selector.
*
@@ -42,18 +39,16 @@
* @throws IllegalArgumentException if the selector name or property name are null
*/
public NodePath( SelectorName selectorName ) {
- CheckArg.isNotNull(selectorName, "selectorName");
- this.selectorName = selectorName;
+ super(selectorName);
}
/**
- * {@inheritDoc}
+ * Get the selector symbol upon which this operand applies.
*
- * @see org.jboss.dna.graph.query.model.DynamicOperand#getSelectorName()
+ * @return the one selector names used by this operand; never null
*/
- @Override
- public final SelectorName getSelectorName() {
- return selectorName;
+ public SelectorName getSelectorName() {
+ return getSelectorNames().iterator().next();
}
/**
@@ -73,7 +68,7 @@
*/
@Override
public int hashCode() {
- return getSelectorName().hashCode();
+ return getSelectorNames().hashCode();
}
/**
@@ -86,7 +81,7 @@
if (obj == this) return true;
if (obj instanceof NodePath) {
NodePath that = (NodePath)obj;
- return this.selectorName.equals(that.selectorName);
+ return this.getSelectorNames().equals(that.getSelectorNames());
}
return false;
}
Modified:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/PropertyValue.java
===================================================================
---
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/PropertyValue.java 2010-01-06
22:13:11 UTC (rev 1535)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/PropertyValue.java 2010-01-06
23:48:32 UTC (rev 1536)
@@ -34,7 +34,6 @@
public class PropertyValue extends DynamicOperand {
private static final long serialVersionUID = 1L;
- private final SelectorName selectorName;
private final String propertyName;
private final int hc;
@@ -47,21 +46,19 @@
*/
public PropertyValue( SelectorName selectorName,
String propertyName ) {
- CheckArg.isNotNull(selectorName, "selectorName");
+ super(selectorName);
CheckArg.isNotNull(propertyName, "propertyName");
- this.selectorName = selectorName;
this.propertyName = propertyName;
- this.hc = HashCode.compute(this.selectorName, this.propertyName);
+ this.hc = HashCode.compute(selectorName, this.propertyName);
}
/**
- * {@inheritDoc}
+ * Get the selector symbol upon which this operand applies.
*
- * @see org.jboss.dna.graph.query.model.DynamicOperand#getSelectorName()
+ * @return the one selector names used by this operand; never null
*/
- @Override
- public final SelectorName getSelectorName() {
- return selectorName;
+ public SelectorName getSelectorName() {
+ return getSelectorNames().iterator().next();
}
/**
@@ -104,7 +101,7 @@
if (obj instanceof PropertyValue) {
PropertyValue that = (PropertyValue)obj;
if (this.hc != that.hc) return false;
- return this.selectorName.equals(that.selectorName) &&
this.propertyName.equals(that.propertyName);
+ return this.getSelectorNames().equals(that.getSelectorNames()) &&
this.propertyName.equals(that.propertyName);
}
return false;
}
Modified: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/UpperCase.java
===================================================================
---
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/UpperCase.java 2010-01-06
22:13:11 UTC (rev 1535)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/UpperCase.java 2010-01-06
23:48:32 UTC (rev 1536)
@@ -24,7 +24,6 @@
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 upper-case representation of the supplied
operand, used in a {@link Comparison}
@@ -42,18 +41,17 @@
* @param operand the operand that is to be lower-cased
*/
public UpperCase( DynamicOperand operand ) {
- CheckArg.isNotNull(operand, "operand");
+ super(operand);
this.operand = operand;
}
/**
- * {@inheritDoc}
+ * Get the selector symbol upon which this operand applies.
*
- * @see org.jboss.dna.graph.query.model.DynamicOperand#getSelectorName()
+ * @return the one selector names used by this operand; never null
*/
- @Override
public SelectorName getSelectorName() {
- return operand.getSelectorName();
+ return getSelectorNames().iterator().next();
}
/**
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 2010-01-06
22:13:11 UTC (rev 1535)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Visitor.java 2010-01-06
23:48:32 UTC (rev 1536)
@@ -94,6 +94,8 @@
void visit( SetQuery obj );
+ void visit( ArithmeticOperand obj );
+
void visit( UpperCase 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 2010-01-06
22:13:11 UTC (rev 1535)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Visitors.java 2010-01-06
23:48:32 UTC (rev 1536)
@@ -138,6 +138,11 @@
}
@Override
+ public void visit( FullTextSearchScore fullTextSearchScore ) {
+ symbols.add(fullTextSearchScore.getSelectorName());
+ }
+
+ @Override
public void visit( Length length ) {
symbols.add(length.getSelectorName());
}
@@ -227,6 +232,14 @@
/**
* {@inheritDoc}
*
+ * @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.ArithmeticOperand)
+ */
+ public void visit( ArithmeticOperand obj ) {
+ }
+
+ /**
+ * {@inheritDoc}
+ *
* @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.Between)
*/
public void visit( Between obj ) {
@@ -569,6 +582,18 @@
/**
* {@inheritDoc}
*
+ * @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.ArithmeticOperand)
+ */
+ public void visit( ArithmeticOperand arithmeticOperation ) {
+ strategy.visit(arithmeticOperation);
+ enqueue(arithmeticOperation.getLeft());
+ enqueue(arithmeticOperation.getRight());
+ visitNext();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
* @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.Between)
*/
public void visit( Between between ) {
@@ -1013,6 +1038,21 @@
/**
* {@inheritDoc}
*
+ * @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.ArithmeticOperand)
+ */
+ public void visit( ArithmeticOperand arithmeticOperand ) {
+ append('(');
+ arithmeticOperand.getLeft().accept(this);
+ append(' ');
+ append(arithmeticOperand.getOperator().getSymbol());
+ append(' ');
+ arithmeticOperand.getRight().accept(this);
+ append(')');
+ }
+
+ /**
+ * {@inheritDoc}
+ *
* @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.Between)
*/
public void visit( Between between ) {
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 2010-01-06
22:13:11 UTC (rev 1535)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/parse/SqlQueryParser.java 2010-01-06
23:48:32 UTC (rev 1536)
@@ -40,6 +40,8 @@
import org.jboss.dna.graph.GraphI18n;
import org.jboss.dna.graph.property.ValueFormatException;
import org.jboss.dna.graph.query.model.And;
+import org.jboss.dna.graph.query.model.ArithmeticOperand;
+import org.jboss.dna.graph.query.model.ArithmeticOperator;
import org.jboss.dna.graph.query.model.Between;
import org.jboss.dna.graph.query.model.BindVariableName;
import org.jboss.dna.graph.query.model.ChildNode;
@@ -106,6 +108,9 @@
* <code><dynamicOperand> [NOT] IN (<staticOperand> {,
<staticOperand>})</code>"</li>
* <li>Support for the BETWEEN clause: "<code><dynamicOperand>
[NOT] BETWEEN <lowerBoundStaticOperand> [EXCLUSIVE] AND
* <upperBoundStaticOperand> [EXCLUSIVE]</code>"</i>
+ * <li>Support for arithmetic operations ('+', '-', '*',
'/') between dynamic operands used in <code>WHERE</code> criteria and
<code>ORDER BY</code>
+ * clauses: "<code>WHERE <dynamicOperand> +
<dynamicOperand> ...</code>" or "<code>ORDER BY
(<dynamicOperand> + <dynamicOperand>) [ASC]</code>".
+ * Note that standard operator precedence is used, but grouping by (potentially nested)
parentheses is also supported.</i>
* </ul>
* </p>
* <h3>SQL grammar</h3>
@@ -346,7 +351,8 @@
*
* <pre>
* DynamicOperand ::= PropertyValue | Length | NodeName | NodeLocalName | NodePath |
NodeDepth |
- * FullTextSearchScore | LowerCase | UpperCase
+ * FullTextSearchScore | LowerCase | UpperCase | Arithmetic |
+ * '(' DynamicOperand ')'
* </pre>
* <h5>Property value</h5>
* <pre>
@@ -396,6 +402,10 @@
* <pre>
* UpperCase ::= 'UPPER(' DynamicOperand ')'
* </pre>
+ * <h5>Arithmetic</h5>
+ * <pre>
+ * Arithmetic ::= DynamicOperand ('+'|'-'|'*'|'/')
DynamicOperand
+ * </pre>
*
* <h4>Ordering</h4>
*
@@ -1036,7 +1046,10 @@
Source source ) {
DynamicOperand result = null;
Position pos = tokens.nextPosition();
- if (tokens.canConsume("LENGTH", "(")) {
+ if (tokens.canConsume('(')) {
+ result = parseDynamicOperand(tokens, typeSystem, source);
+ tokens.consume(")");
+ } else if (tokens.canConsume("LENGTH", "(")) {
result = new Length(parsePropertyValue(tokens, typeSystem, source));
tokens.consume(")");
} else if (tokens.canConsume("LOWER", "(")) {
@@ -1098,6 +1111,44 @@
} else {
result = parsePropertyValue(tokens, typeSystem, source);
}
+
+ // Is this operand followed by an arithmetic operation ...
+ ArithmeticOperator arithmeticOperator = null;
+ if (tokens.canConsume('+')) {
+ arithmeticOperator = ArithmeticOperator.ADD;
+ } else if (tokens.canConsume('-')) {
+ arithmeticOperator = ArithmeticOperator.SUBTRACT;
+ } else if (tokens.canConsume('*')) {
+ arithmeticOperator = ArithmeticOperator.MULTIPLY;
+ } else if (tokens.canConsume('/')) {
+ arithmeticOperator = ArithmeticOperator.DIVIDE;
+ }
+ if (arithmeticOperator != null) {
+ if (tokens.matches('(')) {
+ // Don't use precendence, but instead use the next DynamicOperand as
the RHS ...
+ DynamicOperand right = parseDynamicOperand(tokens, typeSystem, source);
+ result = new ArithmeticOperand(result, arithmeticOperator, right);
+ } else {
+ // There is no parenthesis, so use operator precedence ...
+ DynamicOperand right = parseDynamicOperand(tokens, typeSystem, source);
+ if (right instanceof ArithmeticOperand) {
+ // But the RHS is an arithmetic operand, so we need to use operator
precedence ...
+ ArithmeticOperand arithRhs = (ArithmeticOperand)right;
+ ArithmeticOperator rhsOperator = arithRhs.getOperator();
+ if (arithmeticOperator.precedes(rhsOperator)) {
+ // This operand's operator does take precedence, so this must
be computed before working with the RHS ...
+ DynamicOperand newRhs = arithRhs.getRight();
+ DynamicOperand newLhs = new ArithmeticOperand(result,
arithmeticOperator, arithRhs.getLeft());
+ result = new ArithmeticOperand(newLhs, rhsOperator, newRhs);
+ } else {
+ result = new ArithmeticOperand(result, arithmeticOperator,
right);
+ }
+ } else {
+ // The RHS is just another DynamicOperand ...
+ result = new ArithmeticOperand(result, arithmeticOperator, right);
+ }
+ }
+ }
return result;
}
Modified:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/plan/CanonicalPlanner.java
===================================================================
---
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/plan/CanonicalPlanner.java 2010-01-06
22:13:11 UTC (rev 1535)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/plan/CanonicalPlanner.java 2010-01-06
23:48:32 UTC (rev 1536)
@@ -336,6 +336,9 @@
context.getHints().hasSort = true;
sortNode.setProperty(Property.SORT_ORDER_BY, orderings);
+ for (Ordering ordering : orderings) {
+ sortNode.addSelectors(Visitors.getSelectorsReferencedBy(ordering));
+ }
sortNode.addLastChild(plan);
return sortNode;
Modified: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/plan/PlanHints.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/plan/PlanHints.java 2010-01-06
22:13:11 UTC (rev 1535)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/plan/PlanHints.java 2010-01-06
23:48:32 UTC (rev 1536)
@@ -25,6 +25,7 @@
import java.io.Serializable;
import net.jcip.annotations.NotThreadSafe;
+import org.jboss.dna.graph.query.QueryResults;
@NotThreadSafe
public final class PlanHints implements Serializable {
@@ -61,6 +62,9 @@
/** Flag indicates that the plan has at least one view somewhere */
public boolean hasView = false;
+ /** Flag indicates whether the query plan should be included in the {@link
QueryResults} */
+ public boolean showPlan = false;
+
public PlanHints() {
}
@@ -75,6 +79,7 @@
sb.append(", hasLimit=").append(hasLimit);
sb.append(", hasOptionalJoin=").append(hasOptionalJoin);
sb.append(", hasFullTextSearch=").append(hasFullTextSearch);
+ sb.append(", showPlan=").append(showPlan);
sb.append('}');
return sb.toString();
}
Modified: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/plan/PlanUtil.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/plan/PlanUtil.java 2010-01-06
22:13:11 UTC (rev 1535)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/plan/PlanUtil.java 2010-01-06
23:48:32 UTC (rev 1536)
@@ -27,12 +27,15 @@
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
+import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.jboss.dna.graph.query.QueryContext;
import org.jboss.dna.graph.query.model.And;
+import org.jboss.dna.graph.query.model.ArithmeticOperand;
+import org.jboss.dna.graph.query.model.ArithmeticOperator;
import org.jboss.dna.graph.query.model.Between;
import org.jboss.dna.graph.query.model.ChildNode;
import org.jboss.dna.graph.query.model.ChildNodeJoinCondition;
@@ -558,6 +561,20 @@
// Nothing to do here, as the selector names are fixed elsewhere
break;
case SORT:
+ // The selector names and Ordering objects needs to be changed
...
+ List<Ordering> orderings =
node.getPropertyAsList(Property.SORT_ORDER_BY, Ordering.class);
+ List<Ordering> newOrderings = new
ArrayList<Ordering>(orderings.size());
+ node.getSelectors().clear();
+ for (Ordering ordering : orderings) {
+ DynamicOperand operand = ordering.getOperand();
+ DynamicOperand newOperand = replaceViewReferences(context,
operand, mappings, node);
+ if (newOperand != operand) {
+ ordering = new Ordering(newOperand,
ordering.getOrder());
+ }
+
node.addSelectors(Visitors.getSelectorsReferencedBy(ordering));
+ newOrderings.add(ordering);
+ }
+ node.setProperty(Property.SORT_ORDER_BY, newOrderings);
break;
case GROUP:
// Don't yet use GROUP BY
@@ -678,11 +695,29 @@
DynamicOperand operand,
ColumnMapping mapping,
PlanNode node ) {
+ if (operand instanceof ArithmeticOperand) {
+ ArithmeticOperand arith = (ArithmeticOperand)operand;
+ DynamicOperand newLeft = replaceViewReferences(context, arith.getLeft(),
mapping, node);
+ DynamicOperand newRight = replaceViewReferences(context, arith.getRight(),
mapping, node);
+ return new ArithmeticOperand(newLeft, arith.getOperator(), newRight);
+ }
if (operand instanceof FullTextSearchScore) {
FullTextSearchScore score = (FullTextSearchScore)operand;
if (!mapping.getOriginalName().equals(score.getSelectorName())) return
score;
- if (!mapping.isMappedToSingleSelector()) return score;
- return new FullTextSearchScore(mapping.getSingleMappedSelectorName());
+ if (mapping.isMappedToSingleSelector()) {
+ return new FullTextSearchScore(mapping.getSingleMappedSelectorName());
+ }
+ // There are multiple mappings, so we have to create a composite score ...
+ DynamicOperand composite = null;
+ for (SelectorName name : mapping.getMappedSelectorNames()) {
+ FullTextSearchScore mappedScore = new FullTextSearchScore(name);
+ if (composite == null) {
+ composite = mappedScore;
+ } else {
+ composite = new ArithmeticOperand(composite, ArithmeticOperator.ADD,
mappedScore);
+ }
+ }
+ return composite;
}
if (operand instanceof Length) {
Length operation = (Length)operand;
@@ -857,7 +892,7 @@
public static class ColumnMapping {
private final SelectorName originalName;
private final Map<String, Column> mappedColumnsByOriginalColumnName = new
HashMap<String, Column>();
- private final Set<SelectorName> mappedSelectorNames = new
HashSet<SelectorName>();
+ private final Set<SelectorName> mappedSelectorNames = new
LinkedHashSet<SelectorName>(); // maintains insertion order
public ColumnMapping( SelectorName originalName ) {
this.originalName = originalName;
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 2010-01-06
22:13:11 UTC (rev 1535)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/ProcessingComponent.java 2010-01-06
23:48:32 UTC (rev 1536)
@@ -32,6 +32,7 @@
import org.jboss.dna.graph.property.Path;
import org.jboss.dna.graph.query.QueryContext;
import org.jboss.dna.graph.query.QueryResults.Columns;
+import org.jboss.dna.graph.query.model.ArithmeticOperand;
import org.jboss.dna.graph.query.model.DynamicOperand;
import org.jboss.dna.graph.query.model.FullTextSearchScore;
import org.jboss.dna.graph.query.model.Length;
@@ -318,6 +319,132 @@
}
};
}
+ if (operand instanceof ArithmeticOperand) {
+ ArithmeticOperand arith = (ArithmeticOperand)operand;
+ final DynamicOperation leftOp = createDynamicOperation(typeSystem, schemata,
columns, arith.getLeft());
+ final DynamicOperation rightOp = createDynamicOperation(typeSystem, schemata,
columns, arith.getRight());
+ // compute the expected (common) type ...
+ String leftType = leftOp.getExpectedType();
+ String rightType = rightOp.getExpectedType();
+ final String commonType = typeSystem.getCompatibleType(leftType, rightType);
+ if (typeSystem.getDoubleFactory().getTypeName().equals(commonType)) {
+ final TypeFactory<Double> commonTypeFactory =
typeSystem.getDoubleFactory();
+ switch (arith.getOperator()) {
+ case ADD:
+ return new DynamicOperation() {
+ public String getExpectedType() {
+ return commonType;
+ }
+
+ public Object evaluate( Object[] tuple ) {
+ Double right =
commonTypeFactory.create(rightOp.evaluate(tuple));
+ Double left =
commonTypeFactory.create(leftOp.evaluate(tuple));
+ if (right == null) return left;
+ if (left == null) return right;
+ return left.doubleValue() / right.doubleValue();
+ }
+ };
+ case SUBTRACT:
+ return new DynamicOperation() {
+ public String getExpectedType() {
+ return commonType;
+ }
+
+ public Object evaluate( Object[] tuple ) {
+ Double right =
commonTypeFactory.create(rightOp.evaluate(tuple));
+ Double left =
commonTypeFactory.create(leftOp.evaluate(tuple));
+ if (right == null) return left;
+ if (left == null) left = 0.0d;
+ return left.doubleValue() * right.doubleValue();
+ }
+ };
+ case MULTIPLY:
+ return new DynamicOperation() {
+ public String getExpectedType() {
+ return commonType;
+ }
+
+ public Object evaluate( Object[] tuple ) {
+ Double right =
commonTypeFactory.create(rightOp.evaluate(tuple));
+ Double left =
commonTypeFactory.create(leftOp.evaluate(tuple));
+ if (right == null || left == null) return null;
+ return left.doubleValue() * right.doubleValue();
+ }
+ };
+ case DIVIDE:
+ return new DynamicOperation() {
+ public String getExpectedType() {
+ return commonType;
+ }
+
+ public Object evaluate( Object[] tuple ) {
+ Double right =
commonTypeFactory.create(rightOp.evaluate(tuple));
+ Double left =
commonTypeFactory.create(leftOp.evaluate(tuple));
+ if (right == null || left == null) return null;
+ return left.doubleValue() / right.doubleValue();
+ }
+ };
+ }
+ } else if (typeSystem.getLongFactory().getTypeName().equals(commonType)) {
+ final TypeFactory<Long> commonTypeFactory =
typeSystem.getLongFactory();
+ switch (arith.getOperator()) {
+ case ADD:
+ return new DynamicOperation() {
+ public String getExpectedType() {
+ return commonType;
+ }
+
+ public Object evaluate( Object[] tuple ) {
+ Long right =
commonTypeFactory.create(rightOp.evaluate(tuple));
+ Long left =
commonTypeFactory.create(leftOp.evaluate(tuple));
+ if (right == null) return left;
+ if (left == null) return right;
+ return left.longValue() / right.longValue();
+ }
+ };
+ case SUBTRACT:
+ return new DynamicOperation() {
+ public String getExpectedType() {
+ return commonType;
+ }
+
+ public Object evaluate( Object[] tuple ) {
+ Long right =
commonTypeFactory.create(rightOp.evaluate(tuple));
+ Long left =
commonTypeFactory.create(leftOp.evaluate(tuple));
+ if (right == null) return left;
+ if (left == null) left = 0L;
+ return left.longValue() * right.longValue();
+ }
+ };
+ case MULTIPLY:
+ return new DynamicOperation() {
+ public String getExpectedType() {
+ return commonType;
+ }
+
+ public Object evaluate( Object[] tuple ) {
+ Long right =
commonTypeFactory.create(rightOp.evaluate(tuple));
+ Long left =
commonTypeFactory.create(leftOp.evaluate(tuple));
+ if (right == null || left == null) return null;
+ return left.longValue() * right.longValue();
+ }
+ };
+ case DIVIDE:
+ return new DynamicOperation() {
+ public String getExpectedType() {
+ return commonType;
+ }
+
+ public Object evaluate( Object[] tuple ) {
+ Long right =
commonTypeFactory.create(rightOp.evaluate(tuple));
+ Long left =
commonTypeFactory.create(leftOp.evaluate(tuple));
+ if (right == null || left == null) return null;
+ return left.longValue() / right.longValue();
+ }
+ };
+ }
+ }
+ }
assert false;
return null;
}
Modified:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/QueryProcessor.java
===================================================================
---
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/QueryProcessor.java 2010-01-06
22:13:11 UTC (rev 1535)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/QueryProcessor.java 2010-01-06
23:48:32 UTC (rev 1536)
@@ -104,7 +104,8 @@
statistics = statistics.withExecutionTime(System.nanoTime() - nanos);
}
assert tuples != null;
- return new org.jboss.dna.graph.query.process.QueryResults(columns, statistics,
tuples, context.getProblems());
+ final String planDesc = context.getHints().showPlan ? plan.getString() : null;
+ return new org.jboss.dna.graph.query.process.QueryResults(columns, statistics,
tuples, context.getProblems(), planDesc);
}
/**
Modified:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/QueryResults.java
===================================================================
---
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/QueryResults.java 2010-01-06
22:13:11 UTC (rev 1535)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/QueryResults.java 2010-01-06
23:48:32 UTC (rev 1536)
@@ -49,6 +49,7 @@
private final Columns columns;
private final List<Object[]> tuples;
private final Statistics statistics;
+ private final String plan;
/**
* Create a results object for the supplied context, command, and result columns and
with the supplied tuples.
@@ -57,17 +58,20 @@
* @param statistics the statistics for this query; may not be null
* @param tuples the tuples
* @param problems the problems; may be null if there are no problems
+ * @param plan the text representation of the query plan, if the hints asked for it
*/
public QueryResults( Columns columns,
Statistics statistics,
List<Object[]> tuples,
- Problems problems ) {
+ Problems problems,
+ String plan ) {
assert columns != null;
assert statistics != null;
this.problems = problems != null ? problems : NO_PROBLEMS;
this.columns = columns;
this.tuples = tuples;
this.statistics = statistics;
+ this.plan = plan;
}
/**
@@ -80,7 +84,7 @@
public QueryResults( Columns columns,
Statistics statistics,
List<Object[]> tuples ) {
- this(columns, statistics, tuples, NO_PROBLEMS);
+ this(columns, statistics, tuples, NO_PROBLEMS, null);
}
/**
@@ -93,7 +97,7 @@
public QueryResults( Columns columns,
Statistics statistics,
Problems problems ) {
- this(columns, statistics, Collections.<Object[]>emptyList(), problems);
+ this(columns, statistics, Collections.<Object[]>emptyList(), problems,
null);
}
/**
@@ -104,7 +108,7 @@
*/
public QueryResults( Columns columns,
Statistics statistics ) {
- this(columns, statistics, Collections.<Object[]>emptyList(), null);
+ this(columns, statistics, Collections.<Object[]>emptyList(), null, null);
}
/**
@@ -146,6 +150,15 @@
/**
* {@inheritDoc}
*
+ * @see org.jboss.dna.graph.query.QueryResults#getPlan()
+ */
+ public String getPlan() {
+ return plan;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
* @see org.jboss.dna.graph.query.QueryResults#getProblems()
*/
public Problems getProblems() {
Modified:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/SortValuesComponent.java
===================================================================
---
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/SortValuesComponent.java 2010-01-06
22:13:11 UTC (rev 1535)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/SortValuesComponent.java 2010-01-06
23:48:32 UTC (rev 1536)
@@ -119,8 +119,8 @@
return new Comparator<Object[]>() {
public int compare( Object[] tuple1,
Object[] tuple2 ) {
- Object value1 = operation.evaluate(tuple1);
- Object value2 = operation.evaluate(tuple2);
+ Object value1 = typeFactory.create(operation.evaluate(tuple1));
+ Object value2 = typeFactory.create(operation.evaluate(tuple2));
return 0 - typeComparator.compare(value1, value2);
}
};
@@ -128,8 +128,8 @@
return new Comparator<Object[]>() {
public int compare( Object[] tuple1,
Object[] tuple2 ) {
- Object value1 = operation.evaluate(tuple1);
- Object value2 = operation.evaluate(tuple2);
+ Object value1 = typeFactory.create(operation.evaluate(tuple1));
+ Object value2 = typeFactory.create(operation.evaluate(tuple2));
return typeComparator.compare(value1, value2);
}
};
Modified: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/validate/Validator.java
===================================================================
---
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/validate/Validator.java 2010-01-06
22:13:11 UTC (rev 1535)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/validate/Validator.java 2010-01-06
23:48:32 UTC (rev 1536)
@@ -26,14 +26,17 @@
import java.util.HashMap;
import java.util.Map;
import org.jboss.dna.common.collection.Problems;
+import org.jboss.dna.common.i18n.I18n;
import org.jboss.dna.graph.GraphI18n;
import org.jboss.dna.graph.query.QueryContext;
import org.jboss.dna.graph.query.model.AllNodes;
+import org.jboss.dna.graph.query.model.ArithmeticOperand;
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.DescendantNode;
import org.jboss.dna.graph.query.model.DescendantNodeJoinCondition;
+import org.jboss.dna.graph.query.model.DynamicOperand;
import org.jboss.dna.graph.query.model.EquiJoinCondition;
import org.jboss.dna.graph.query.model.FullTextSearch;
import org.jboss.dna.graph.query.model.FullTextSearchScore;
@@ -49,6 +52,7 @@
import org.jboss.dna.graph.query.model.SameNode;
import org.jboss.dna.graph.query.model.SameNodeJoinCondition;
import org.jboss.dna.graph.query.model.SelectorName;
+import org.jboss.dna.graph.query.model.TypeSystem;
import org.jboss.dna.graph.query.model.Visitor;
import org.jboss.dna.graph.query.model.Visitors.AbstractVisitor;
import org.jboss.dna.graph.query.validate.Schemata.Table;
@@ -92,6 +96,53 @@
/**
* {@inheritDoc}
*
+ * @see
org.jboss.dna.graph.query.model.Visitors.AbstractVisitor#visit(org.jboss.dna.graph.query.model.ArithmeticOperand)
+ */
+ @Override
+ public void visit( ArithmeticOperand obj ) {
+ verifyArithmeticOperand(obj.getLeft());
+ verifyArithmeticOperand(obj.getRight());
+ }
+
+ protected void verifyArithmeticOperand( DynamicOperand operand ) {
+ // The left and right operands must have LONG or DOUBLE types ...
+ if (operand instanceof NodeDepth) {
+ // good to go
+ } else if (operand instanceof Length) {
+ // good to go
+ } else if (operand instanceof ArithmeticOperand) {
+ // good to go
+ } else if (operand instanceof FullTextSearchScore) {
+ // good to go
+ } else if (operand instanceof PropertyValue) {
+ PropertyValue value = (PropertyValue)operand;
+ SelectorName selector = value.getSelectorName();
+ String propertyName = value.getPropertyName();
+ Schemata.Column column = verify(selector, propertyName);
+ if (column != null) {
+ // Check the type ...
+ String columnType = column.getPropertyType();
+ TypeSystem types = context.getTypeSystem();
+ String longType = types.getLongFactory().getTypeName();
+ String doubleType = types.getDoubleFactory().getTypeName();
+ if (longType.equals(types.getCompatibleType(columnType, longType))) {
+ // Then the column type is long or can be converted to long ...
+ } else if (doubleType.equals(types.getCompatibleType(columnType,
doubleType))) {
+ // Then the column type is double or can be converted to double ...
+ } else {
+ I18n msg = GraphI18n.columnTypeCannotBeUsedInArithmeticOperation;
+ problems.addError(msg, selector, propertyName, columnType);
+ }
+ }
+ } else {
+ I18n msg = GraphI18n.dynamicOperandCannotBeUsedInArithmeticOperation;
+ problems.addError(msg, operand);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ *
* @see
org.jboss.dna.graph.query.model.Visitors.AbstractVisitor#visit(org.jboss.dna.graph.query.model.ChildNode)
*/
@Override
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 2010-01-06
22:13:11 UTC (rev 1535)
+++ trunk/dna-graph/src/main/resources/org/jboss/dna/graph/GraphI18n.properties 2010-01-06
23:48:32 UTC (rev 1536)
@@ -131,6 +131,8 @@
columnDoesNotExistInQuery = Column '{0}' does not exist in query
columnIsNotFullTextSearchable = Column '{0}' on the table '{1}' does not
support full-text searching
tableIsNotFullTextSearchable = Table '{0}' has no columns that support full-text
searching
+columnTypeCannotBeUsedInArithmeticOperation = Column '{0}' on table '{1}'
has a type of '{2}' and cannot be used in an arithmetic operation
+dynamicOperandCannotBeUsedInArithmeticOperation = '{0}' does not evaluate to a
long or double and cannot be used in an arithmetic operation
selectorDoesNotExistInQuery = Selector '{0}' does not exist in query
propertyOnSelectorIsNotUsedInQuery = Property '{0}' on selector '{1}' is
not used in query
errorResolvingNodesFromLocationsUsingSourceAndWorkspace = Error resolving nodes from
locations using '{1}' workspace in '{0}'
@@ -155,6 +157,7 @@
expectingLiteralAndUnableToParseAsLong = Expecting literal and unable to parse
'{0}' at line {1}, column {2} as a long
expectingLiteralAndUnableToParseAsDouble = Expecting literal and unable to parse
'{0}' at line {1}, column {2} as a double
expectingLiteralAndUnableToParseAsDate = Expecting literal and unable to parse
'{0}' at line {1}, column {2} as a date
+unexpectedClosingParenthesis = Unexpected closing parenthesis without a matching opening
parenthesis
# Search
interruptedWhileClosingChannel = Thread was interrupted while closing request processing
channel for source "{0}"
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 2010-01-06
22:13:11 UTC (rev 1535)
+++
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/QueryBuilderTest.java 2010-01-06
23:48:32 UTC (rev 1536)
@@ -466,6 +466,40 @@
}
@Test
+ public void shouldBuildQueryWithCriteriaUsingPlus() {
+ query = builder.selectStar()
+ .from("table AS nodes")
+ .where()
+ .depth("nodes")
+ .plus()
+ .depth("nodes")
+ .plus()
+ .depth("nodes")
+ .isEqualTo(3)
+ .end()
+ .query();
+ assertThatSql(query, is("SELECT * FROM table AS nodes " + //
+ "WHERE ((DEPTH(nodes) + DEPTH(nodes)) +
DEPTH(nodes)) = 3"));
+ }
+
+ @Test
+ public void shouldBuildQueryWithCriteriaUsingPlusAndMinus() {
+ query = builder.selectStar()
+ .from("table AS nodes")
+ .where()
+ .depth("nodes")
+ .minus()
+ .depth("nodes")
+ .plus()
+ .fullTextSearchScore("nodes")
+ .isEqualTo(3)
+ .end()
+ .query();
+ assertThatSql(query, is("SELECT * FROM table AS nodes " + //
+ "WHERE (DEPTH(nodes) - (DEPTH(nodes) +
SCORE(nodes))) = 3"));
+ }
+
+ @Test
public void shouldBuildQueryWithCriteriaUsingLengthEqualTo() {
query = builder.selectStar()
.from("table AS nodes")
@@ -1369,7 +1403,7 @@
}
@Test
- public void shouldBuilderQueryWithSetCriteria() {
+ public void shouldBuildQueryWithSetCriteria() {
query = builder.selectStar()
.from("table AS nodes")
.where()
@@ -1380,4 +1414,18 @@
assertThatSql(query, is("SELECT * FROM table AS nodes " + //
"WHERE NAME(nodes) IN
('value1','value2','value3')"));
}
+
+ @Test
+ public void shouldBuildQueryWithOneOrderByClause() {
+ query = builder.selectStar()
+ .from("table AS nodes")
+ .orderBy()
+ .ascending()
+ .fullTextSearchScore("nodes")
+ .then()
+ .descending()
+ .length("nodes", "column")
+ .end()
+ .query();
+ }
}
Modified:
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/optimize/RuleBasedOptimizerTest.java
===================================================================
---
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/optimize/RuleBasedOptimizerTest.java 2010-01-06
22:13:11 UTC (rev 1535)
+++
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/optimize/RuleBasedOptimizerTest.java 2010-01-06
23:48:32 UTC (rev 1536)
@@ -34,13 +34,19 @@
import org.jboss.dna.graph.GraphI18n;
import org.jboss.dna.graph.query.AbstractQueryTest;
import org.jboss.dna.graph.query.QueryContext;
+import org.jboss.dna.graph.query.model.ArithmeticOperand;
+import org.jboss.dna.graph.query.model.ArithmeticOperator;
import org.jboss.dna.graph.query.model.Column;
import org.jboss.dna.graph.query.model.Comparison;
+import org.jboss.dna.graph.query.model.DynamicOperand;
import org.jboss.dna.graph.query.model.EquiJoinCondition;
import org.jboss.dna.graph.query.model.FullTextSearch;
+import org.jboss.dna.graph.query.model.FullTextSearchScore;
import org.jboss.dna.graph.query.model.JoinType;
import org.jboss.dna.graph.query.model.Literal;
import org.jboss.dna.graph.query.model.Operator;
+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.QueryCommand;
import org.jboss.dna.graph.query.model.SelectorName;
@@ -507,6 +513,88 @@
assertThat(node.isSameAs(access), is(true));
}
+ @Test
+ public void shouldOptimizePlanForQueryWithOrderByClause() {
+ node = optimize("SELECT v2.c11 AS c1 FROM v2 WHERE v2.c11 = 'x' AND
v2.c12 = 'y' ORDER BY v2.c11, v2.c12 DESC");
+
+ // Create the expected plan ...
+ PlanNode sort = new PlanNode(Type.SORT, selector("t1"));
+ sort.setProperty(Property.SORT_ORDER_BY, orderings(ascending("t1",
"c11"), descending("t1", "c12")));
+ PlanNode project = new PlanNode(Type.PROJECT, sort, selector("t1"));
+ project.setProperty(Property.PROJECT_COLUMNS, columns(column("t1",
"c11", "c1")));
+ PlanNode join = new PlanNode(Type.JOIN, project, selector("t2"),
selector("t1"));
+ join.setProperty(Property.JOIN_ALGORITHM, JoinAlgorithm.NESTED_LOOP);
+ join.setProperty(Property.JOIN_TYPE, JoinType.INNER);
+ join.setProperty(Property.JOIN_CONDITION, new
EquiJoinCondition(selector("t1"), "c11", selector("t2"),
"c21"));
+
+ PlanNode leftAccess = new PlanNode(Type.ACCESS, join, selector("t1"));
+ PlanNode leftProject = new PlanNode(Type.PROJECT, leftAccess,
selector("t1"));
+ leftProject.setProperty(Property.PROJECT_COLUMNS, columns(column("t1",
"c11")));
+ PlanNode leftSelect1 = new PlanNode(Type.SELECT, leftProject,
selector("t1"));
+ leftSelect1.setProperty(Property.SELECT_CRITERIA, new Comparison(new
PropertyValue(selector("t1"), "c11"),
+
Operator.EQUAL_TO, new Literal("x")));
+ PlanNode leftSelect2 = new PlanNode(Type.SELECT, leftSelect1,
selector("t1"));
+ leftSelect2.setProperty(Property.SELECT_CRITERIA, new Comparison(new
PropertyValue(selector("t1"), "c12"),
+
Operator.EQUAL_TO, new Literal("y")));
+ PlanNode leftSource = new PlanNode(Type.SOURCE, leftSelect2,
selector("t1"));
+ leftSource.setProperty(Property.SOURCE_NAME, selector("t1"));
+ leftSource.setProperty(Property.SOURCE_COLUMNS,
context.getSchemata().getTable(selector("t1")).getColumns());
+
+ PlanNode rightAccess = new PlanNode(Type.ACCESS, join,
selector("t2"));
+ PlanNode rightProject = new PlanNode(Type.PROJECT, rightAccess,
selector("t2"));
+ rightProject.setProperty(Property.PROJECT_COLUMNS, columns(column("t2",
"c21")));
+ PlanNode rightSelect1 = new PlanNode(Type.SELECT, rightProject,
selector("t2"));
+ rightSelect1.setProperty(Property.SELECT_CRITERIA, new Comparison(new
PropertyValue(selector("t2"), "c21"),
+
Operator.EQUAL_TO, new Literal("x")));
+ PlanNode rightSource = new PlanNode(Type.SOURCE, rightSelect1,
selector("t2"));
+ rightSource.setProperty(Property.SOURCE_NAME, selector("t2"));
+ rightSource.setProperty(Property.SOURCE_COLUMNS,
context.getSchemata().getTable(selector("t2")).getColumns());
+
+ // Compare the expected and actual plan ...
+ assertThat(node.isSameAs(sort), is(true));
+ }
+
+ @Test
+ public void shouldOptimizePlanForQueryWithOrderByClauseThatUsesScoreFunction() {
+ node = optimize("SELECT v2.c11 AS c1 FROM v2 WHERE v2.c11 = 'x' AND
v2.c12 = 'y' ORDER BY SCORE(v2) ASC");
+
+ // Create the expected plan ...
+ PlanNode sort = new PlanNode(Type.SORT, selector("t1"),
selector("t2"));
+ sort.setProperty(Property.SORT_ORDER_BY, orderings(ascendingScore("t1",
"t2")));
+ PlanNode project = new PlanNode(Type.PROJECT, sort, selector("t1"));
+ project.setProperty(Property.PROJECT_COLUMNS, columns(column("t1",
"c11", "c1")));
+ PlanNode join = new PlanNode(Type.JOIN, project, selector("t2"),
selector("t1"));
+ join.setProperty(Property.JOIN_ALGORITHM, JoinAlgorithm.NESTED_LOOP);
+ join.setProperty(Property.JOIN_TYPE, JoinType.INNER);
+ join.setProperty(Property.JOIN_CONDITION, new
EquiJoinCondition(selector("t1"), "c11", selector("t2"),
"c21"));
+
+ PlanNode leftAccess = new PlanNode(Type.ACCESS, join, selector("t1"));
+ PlanNode leftProject = new PlanNode(Type.PROJECT, leftAccess,
selector("t1"));
+ leftProject.setProperty(Property.PROJECT_COLUMNS, columns(column("t1",
"c11")));
+ PlanNode leftSelect1 = new PlanNode(Type.SELECT, leftProject,
selector("t1"));
+ leftSelect1.setProperty(Property.SELECT_CRITERIA, new Comparison(new
PropertyValue(selector("t1"), "c11"),
+
Operator.EQUAL_TO, new Literal("x")));
+ PlanNode leftSelect2 = new PlanNode(Type.SELECT, leftSelect1,
selector("t1"));
+ leftSelect2.setProperty(Property.SELECT_CRITERIA, new Comparison(new
PropertyValue(selector("t1"), "c12"),
+
Operator.EQUAL_TO, new Literal("y")));
+ PlanNode leftSource = new PlanNode(Type.SOURCE, leftSelect2,
selector("t1"));
+ leftSource.setProperty(Property.SOURCE_NAME, selector("t1"));
+ leftSource.setProperty(Property.SOURCE_COLUMNS,
context.getSchemata().getTable(selector("t1")).getColumns());
+
+ PlanNode rightAccess = new PlanNode(Type.ACCESS, join,
selector("t2"));
+ PlanNode rightProject = new PlanNode(Type.PROJECT, rightAccess,
selector("t2"));
+ rightProject.setProperty(Property.PROJECT_COLUMNS, columns(column("t2",
"c21")));
+ PlanNode rightSelect1 = new PlanNode(Type.SELECT, rightProject,
selector("t2"));
+ rightSelect1.setProperty(Property.SELECT_CRITERIA, new Comparison(new
PropertyValue(selector("t2"), "c21"),
+
Operator.EQUAL_TO, new Literal("x")));
+ PlanNode rightSource = new PlanNode(Type.SOURCE, rightSelect1,
selector("t2"));
+ rightSource.setProperty(Property.SOURCE_NAME, selector("t2"));
+ rightSource.setProperty(Property.SOURCE_COLUMNS,
context.getSchemata().getTable(selector("t2")).getColumns());
+
+ // Compare the expected and actual plan ...
+ assertThat(node.isSameAs(sort), is(true));
+ }
+
//
----------------------------------------------------------------------------------------------------------------
// Utility methods ...
//
----------------------------------------------------------------------------------------------------------------
@@ -515,6 +603,39 @@
return Arrays.asList(columns);
}
+ protected List<Ordering> orderings( Ordering... orderings ) {
+ return Arrays.asList(orderings);
+ }
+
+ protected Ordering ascending( String table,
+ String columnName ) {
+ return new Ordering(new PropertyValue(new SelectorName(table), columnName),
Order.ASCENDING);
+ }
+
+ protected Ordering descending( String table,
+ String columnName ) {
+ return new Ordering(new PropertyValue(new SelectorName(table), columnName),
Order.DESCENDING);
+ }
+
+ protected Ordering ascendingScore( String... tableNames ) {
+ return new Ordering(score(tableNames), Order.ASCENDING);
+ }
+
+ protected Ordering descendingScore( String... tableNames ) {
+ return new Ordering(score(tableNames), Order.DESCENDING);
+ }
+
+ protected DynamicOperand score( String... tableNames ) {
+ DynamicOperand operand = null;
+ for (String tableName : tableNames) {
+ DynamicOperand right = new FullTextSearchScore(new SelectorName(tableName));
+ if (operand == null) operand = right;
+ else operand = new ArithmeticOperand(operand, ArithmeticOperator.ADD,
right);
+ }
+ assert operand != null;
+ return operand;
+ }
+
protected Column column( String table,
String columnName ) {
return new Column(new SelectorName(table), columnName, columnName);
Modified:
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/plan/CanonicalPlannerTest.java
===================================================================
---
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/plan/CanonicalPlannerTest.java 2010-01-06
22:13:11 UTC (rev 1535)
+++
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/plan/CanonicalPlannerTest.java 2010-01-06
23:48:32 UTC (rev 1536)
@@ -60,6 +60,7 @@
private Schemata schemata;
private ImmutableSchemata.Builder schemataBuilder;
private QueryContext queryContext;
+ private boolean print;
@Before
public void beforeEach() {
@@ -69,8 +70,13 @@
builder = new QueryBuilder(typeSystem);
problems = new SimpleProblems();
schemataBuilder = ImmutableSchemata.createBuilder(typeSystem);
+ print = false;
}
+ protected void print( PlanNode plan ) {
+ if (print) System.out.println(plan);
+ }
+
protected SelectorName selector( String name ) {
return new SelectorName(name);
}
@@ -132,7 +138,7 @@
PlanNode source = plan.getFirstChild();
assertSourceNode(source, "__ALLNODES__", null, "column1",
"column2", "column3");
assertThat(source.getChildCount(), is(0));
- System.out.println(plan);
+ print(plan);
}
@Test
@@ -159,7 +165,7 @@
query = builder.selectStar().from("someTable").query();
queryContext = new QueryContext(schemata, typeSystem, hints, problems);
plan = planner.createPlan(queryContext, query);
- System.out.println(plan);
+ print(plan);
assertThat(problems.hasErrors(), is(false));
assertThat(problems.isEmpty(), is(true));
assertProjectNode(plan, "column1", "column2",
"column3");
@@ -188,7 +194,7 @@
queryContext = new QueryContext(schemata, typeSystem, hints, problems);
plan = planner.createPlan(queryContext, query);
assertThat(problems.hasErrors(), is(false));
- System.out.println(plan);
+ print(plan);
assertThat(plan.getType(), is(PlanNode.Type.PROJECT));
assertThat(plan.getSelectors(), is(selectors("t1")));
}
@@ -200,7 +206,7 @@
queryContext = new QueryContext(schemata, typeSystem, hints, problems);
plan = planner.createPlan(queryContext, query);
assertThat(problems.hasErrors(), is(false));
- System.out.println(plan);
+ print(plan);
assertThat(plan.getType(), is(PlanNode.Type.PROJECT));
assertThat(plan.getSelectors(), is(selectors("t1")));
}
@@ -267,4 +273,48 @@
assertThat(problems.hasErrors(), is(true));
}
+ @Test
+ public void shouldProducePlanWhenOrderByClauseIsUsed() {
+ schemata = schemataBuilder.addTable("dna:someTable",
"column1", "column2", "column3").build();
+ query = builder.selectStar()
+ .from("dna:someTable AS t1")
+ .where()
+ .path("t1")
+ .isEqualTo(1L)
+ .end()
+ .orderBy()
+ .ascending()
+ .propertyValue("t1", "column1")
+ .end()
+ .query();
+ queryContext = new QueryContext(schemata, typeSystem, hints, problems);
+ plan = planner.createPlan(queryContext, query);
+ assertThat(problems.hasErrors(), is(false));
+ print(plan);
+ assertThat(plan.getType(), is(PlanNode.Type.SORT));
+ assertThat(plan.getSelectors(), is(selectors("t1")));
+ }
+
+ @Test
+ public void shouldProducePlanWhenOrderByClauseWithScoreIsUsed() {
+ schemata = schemataBuilder.addTable("dna:someTable",
"column1", "column2", "column3").build();
+ query = builder.selectStar()
+ .from("dna:someTable AS t1")
+ .where()
+ .path("t1")
+ .isEqualTo(1L)
+ .end()
+ .orderBy()
+ .ascending()
+ .fullTextSearchScore("t1")
+ .end()
+ .query();
+ queryContext = new QueryContext(schemata, typeSystem, hints, problems);
+ plan = planner.createPlan(queryContext, query);
+ assertThat(problems.hasErrors(), is(false));
+ print(plan);
+ assertThat(plan.getType(), is(PlanNode.Type.SORT));
+ assertThat(plan.getSelectors(), is(selectors("t1")));
+ }
+
}
Modified:
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/process/QueryResultColumnsTest.java
===================================================================
---
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/process/QueryResultColumnsTest.java 2010-01-06
22:13:11 UTC (rev 1535)
+++
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/process/QueryResultColumnsTest.java 2010-01-06
23:48:32 UTC (rev 1536)
@@ -92,7 +92,7 @@
@Test
public void shouldReturnSameTuplesListPassedIntoConstructor() {
- results = new QueryResults(columns, statistics, tuples, context.getProblems());
+ results = new QueryResults(columns, statistics, tuples, context.getProblems(),
null);
assertThat(results.getTuples(), is(sameInstance(tuples)));
}
@@ -105,14 +105,14 @@
@Test
public void shouldHaveNoTuplesIfConstructedWithEmptyTuplesList() {
tuples.clear();
- results = new QueryResults(columns, statistics, tuples, context.getProblems());
+ results = new QueryResults(columns, statistics, tuples, context.getProblems(),
null);
assertThat(results.getTuples().isEmpty(), is(true));
assertThat(results.getCursor().hasNext(), is(false));
}
@Test
public void shouldReturnMutableTuplesList() {
- results = new QueryResults(columns, statistics, tuples, context.getProblems());
+ results = new QueryResults(columns, statistics, tuples, context.getProblems(),
null);
assertThat(results.getTuples().isEmpty(), is(false));
results.getTuples().clear();
assertThat(results.getTuples().isEmpty(), is(true));
@@ -121,7 +121,7 @@
@Test
public void shouldReturnCursorThatAccessesTuples() {
- results = new QueryResults(columns, statistics, tuples, context.getProblems());
+ results = new QueryResults(columns, statistics, tuples, context.getProblems(),
null);
Cursor cursor = results.getCursor();
Iterator<Object[]> expectedIter = tuples.iterator();
int rowNumber = 0;
@@ -155,7 +155,7 @@
@Test( expected = IllegalStateException.class )
public void shouldRequireNextOnCursorToBeCalledBeforeGettingValueUsingColumnIndex()
{
- results = new QueryResults(columns, statistics, tuples, context.getProblems());
+ results = new QueryResults(columns, statistics, tuples, context.getProblems(),
null);
Cursor cursor = results.getCursor();
assertThat(cursor.hasNext(), is(true));
cursor.getValue(0);
@@ -163,7 +163,7 @@
@Test( expected = IllegalStateException.class )
public void shouldRequireNextOnCursorToBeCalledBeforeGettingValueUsingColumnName() {
- results = new QueryResults(columns, statistics, tuples, context.getProblems());
+ results = new QueryResults(columns, statistics, tuples, context.getProblems(),
null);
Cursor cursor = results.getCursor();
assertThat(cursor.hasNext(), is(true));
cursor.getValue("colA");
@@ -171,14 +171,14 @@
@Test
public void shouldPrintToStringAllResults() {
- results = new QueryResults(columns, statistics, tuples, context.getProblems());
+ results = new QueryResults(columns, statistics, tuples, context.getProblems(),
null);
List<String> lines = StringUtil.splitLines(results.toString());
assertThat(lines.size(), is(tuples.size() + 4)); // = delim + header + delim +
(...lines...) + delim
}
@Test
public void shouldPrintToStringBuilderAllResults() {
- results = new QueryResults(columns, statistics, tuples, context.getProblems());
+ results = new QueryResults(columns, statistics, tuples, context.getProblems(),
null);
StringBuilder sb = new StringBuilder();
results.toString(typeSystem, sb);
List<String> lines = StringUtil.splitLines(sb.toString());
@@ -188,7 +188,7 @@
@Test
public void shouldPrintToStringBuilderAllResultsEvenWhenNoTuples() {
tuples.clear();
- results = new QueryResults(columns, statistics, tuples, context.getProblems());
+ results = new QueryResults(columns, statistics, tuples, context.getProblems(),
null);
StringBuilder sb = new StringBuilder();
results.toString(typeSystem, sb);
List<String> lines = StringUtil.splitLines(sb.toString());
@@ -197,7 +197,7 @@
@Test
public void shouldPrintToStringBuilderOnlyFirstLinesOfResults() {
- results = new QueryResults(columns, statistics, tuples, context.getProblems());
+ results = new QueryResults(columns, statistics, tuples, context.getProblems(),
null);
StringBuilder sb = new StringBuilder();
results.toString(typeSystem, sb, 1);
List<String> lines = StringUtil.splitLines(sb.toString());
@@ -207,7 +207,7 @@
@Test
public void shouldPrintToStringBuilderOnlyFirstLinesOfResultsEvenWhenNoTuples() {
tuples.clear();
- results = new QueryResults(columns, statistics, tuples, context.getProblems());
+ results = new QueryResults(columns, statistics, tuples, context.getProblems(),
null);
StringBuilder sb = new StringBuilder();
results.toString(typeSystem, sb, 3);
List<String> lines = StringUtil.splitLines(sb.toString());
@@ -217,7 +217,7 @@
@Test
public void
shouldPrintToStringBuilderAllResultsWhenMaxRowParameterIsLargerThanNumberOfTuples() {
tuples.clear();
- results = new QueryResults(columns, statistics, tuples, context.getProblems());
+ results = new QueryResults(columns, statistics, tuples, context.getProblems(),
null);
StringBuilder sb = new StringBuilder();
results.toString(typeSystem, sb, 3);
List<String> lines = StringUtil.splitLines(sb.toString());
Modified: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrQueryManager.java
===================================================================
--- trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrQueryManager.java 2010-01-06 22:13:11
UTC (rev 1535)
+++ trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrQueryManager.java 2010-01-06 23:48:32
UTC (rev 1536)
@@ -46,8 +46,12 @@
import javax.jcr.query.RowIterator;
import net.jcip.annotations.Immutable;
import net.jcip.annotations.NotThreadSafe;
+import org.jboss.dna.common.collection.Problem;
+import org.jboss.dna.common.collection.Problems;
+import org.jboss.dna.common.collection.Problem.Status;
import org.jboss.dna.common.text.ParsingException;
import org.jboss.dna.common.util.CheckArg;
+import org.jboss.dna.common.util.StringUtil;
import org.jboss.dna.graph.Location;
import org.jboss.dna.graph.property.NamespaceRegistry;
import org.jboss.dna.graph.property.Path;
@@ -119,6 +123,7 @@
throw new
InvalidQueryException(JcrI18n.queryCannotBeParsedUsingLanguage.text(language,
expression));
}
PlanHints hints = new PlanHints();
+ hints.showPlan = true;
// If using XPath, we need to add a few hints ...
if (Query.XPATH.equals(language)) {
hints.hasFullTextSearch = true; // requires 'jcr:score' to exist
@@ -260,6 +265,18 @@
return queryNode;
}
+
+ protected void checkForProblems( Problems problems ) throws RepositoryException
{
+ if (problems.hasErrors()) {
+ // Build a message with the problems ...
+ StringBuilder msg = new StringBuilder();
+ for (Problem problem : problems) {
+ if (problem.getStatus() != Status.ERROR) continue;
+ msg.append(problem.getMessageString()).append("\n");
+ }
+ throw new RepositoryException(msg.toString());
+ }
+ }
}
/**
@@ -297,7 +314,12 @@
this.variables = null;
}
- protected QueryCommand getCommand() {
+ /**
+ * Get the underlying and immutable Abstract Query Model representation of this
query.
+ *
+ * @return the AQM representation; never null
+ */
+ public QueryCommand getAbstractQueryModel() {
return query;
}
@@ -314,12 +336,23 @@
schemata,
hints,
variables);
+ checkForProblems(result.getProblems());
if (Query.XPATH.equals(language)) {
return new XPathQueryResult(session, result);
}
return new JcrQueryResult(session, result);
}
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return language + " -> " + statement + "\n" +
StringUtil.createString(' ', Math.min(language.length() - 3, 0))
+ + "AQM -> " + query;
+ }
}
@NotThreadSafe
@@ -354,8 +387,19 @@
statement,
MAXIMUM_RESULTS_FOR_FULL_TEXT_SEARCH_QUERIES,
0);
+ checkForProblems(result.getProblems());
return new JcrQueryResult(session, result);
}
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return language + " -> " + statement;
+ }
}
protected static final String JCR_SCORE_COLUMN_NAME = "jcr:score";
@@ -430,6 +474,15 @@
}
/**
+ * Get a description of the query plan, if requested.
+ *
+ * @return the query plan, or null if the plan was not requested
+ */
+ public String getPlan() {
+ return results.getPlan();
+ }
+
+ /**
* {@inheritDoc}
*
* @see java.lang.Object#toString()
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 2010-01-06 22:13:11 UTC
(rev 1535)
+++ trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/xpath/XPath.java 2010-01-06 23:48:32 UTC
(rev 1536)
@@ -26,10 +26,12 @@
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
+import org.jboss.dna.common.collection.ReadOnlyIterator;
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;
+import org.jboss.dna.graph.query.model.Order;
/**
* Abstract syntax components of an XPath query. The supported grammar is defined by JCR
1.0, and is a subset of what is allowed
@@ -642,11 +644,14 @@
public static class PathExpression extends Component implements
Iterable<StepExpression> {
private final List<StepExpression> steps;
private final boolean relative;
+ private final OrderBy orderBy;
public PathExpression( boolean relative,
- List<StepExpression> steps ) {
+ List<StepExpression> steps,
+ OrderBy orderBy ) {
this.steps = steps;
this.relative = relative;
+ this.orderBy = orderBy;
}
/**
@@ -657,6 +662,15 @@
}
/**
+ * Get the order-by clause.
+ *
+ * @return the order-by clause, or null if there is no such clause
+ */
+ public OrderBy getOrderBy() {
+ return orderBy;
+ }
+
+ /**
* @return steps
*/
public List<StepExpression> getSteps() {
@@ -669,12 +683,12 @@
public PathExpression withoutLast() {
assert !steps.isEmpty();
- return new PathExpression(relative, steps.subList(0, steps.size() - 1));
+ return new PathExpression(relative, steps.subList(0, steps.size() - 1),
orderBy);
}
public PathExpression withoutFirst() {
assert !steps.isEmpty();
- return new PathExpression(relative, steps.subList(1, steps.size()));
+ return new PathExpression(relative, steps.subList(1, steps.size()),
orderBy);
}
/**
@@ -701,7 +715,10 @@
if (obj == this) return true;
if (obj instanceof PathExpression) {
PathExpression that = (PathExpression)obj;
- return this.relative == that.relative &&
this.steps.equals(that.steps);
+ if (this.relative != that.relative) return false;
+ if (this.orderBy != null && !this.orderBy.equals(that.orderBy))
return false;
+ if (this.orderBy == null && that.orderBy != null) return false;
+ return this.steps.equals(that.steps);
}
return false;
}
@@ -1382,4 +1399,166 @@
return "schema-attribute(" + attributeDeclarationName +
")";
}
}
+
+ public static class OrderBy extends Component implements Iterable<OrderBySpec>
{
+ private final List<OrderBySpec> orderBySpecifications;
+
+ public OrderBy( List<OrderBySpec> orderBySpecifications ) {
+ this.orderBySpecifications = orderBySpecifications;
+ }
+
+ /**
+ *@return the list of order-by specifications; never null
+ */
+ public List<OrderBySpec> getOrderBySpecifications() {
+ return orderBySpecifications;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.jcr.xpath.XPath.Component#collapse()
+ */
+ @Override
+ public Component collapse() {
+ return super.collapse();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Iterable#iterator()
+ */
+ public Iterator<OrderBySpec> iterator() {
+ return new
ReadOnlyIterator<OrderBySpec>(orderBySpecifications.iterator());
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals( Object obj ) {
+ if (obj == this) return true;
+ if (obj instanceof OrderBy) {
+ OrderBy that = (OrderBy)obj;
+ return this.orderBySpecifications.equals(that.orderBySpecifications);
+ }
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("order-by(");
+ boolean first = true;
+ for (OrderBySpec spec : orderBySpecifications) {
+ if (first) first = false;
+ else sb.append(',');
+ sb.append(spec);
+ }
+ sb.append(')');
+ return sb.toString();
+ }
+ }
+
+ public static class OrderBySpec {
+ private final Order order;
+ private final FunctionCall scoreFunction;
+ private final NameTest attributeName;
+
+ public OrderBySpec( Order order,
+ FunctionCall scoreFunction ) {
+ assert order != null;
+ assert scoreFunction != null;
+ this.order = order;
+ this.scoreFunction = scoreFunction;
+ this.attributeName = null;
+ }
+
+ public OrderBySpec( Order order,
+ NameTest attributeName ) {
+ assert order != null;
+ assert attributeName != null;
+ this.order = order;
+ this.scoreFunction = null;
+ this.attributeName = attributeName;
+ }
+
+ /**
+ * Get the attribute name for this order specification.
+ *
+ * @return the attribute name, or null if the order is defined by the {@link
#getScoreFunction() score function}
+ */
+ public NameTest getAttributeName() {
+ return attributeName;
+ }
+
+ /**
+ * Get the score function for this order specification.
+ *
+ * @return the score function with its parameters, or null if the order is
defined by the {@link #getAttributeName()
+ * attribute name}
+ */
+ public FunctionCall getScoreFunction() {
+ return scoreFunction;
+ }
+
+ /**
+ * The order for this specification
+ *
+ * @return the order; never null
+ */
+ public Order getOrder() {
+ return order;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals( Object obj ) {
+ if (obj == this) return true;
+ if (obj instanceof OrderBySpec) {
+ OrderBySpec that = (OrderBySpec)obj;
+ if (this.order != that.order) return false;
+ if (this.attributeName != null &&
!this.attributeName.equals(that.attributeName)) return false;
+ if (this.scoreFunction != null &&
!this.scoreFunction.equals(that.scoreFunction)) return false;
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ if (scoreFunction != null) {
+ sb.append(scoreFunction.toString());
+ } else {
+ sb.append('(a)').append(attributeName.toString());
+ }
+ switch (order) {
+ case ASCENDING:
+ sb.append(" ascending");
+ break;
+ case DESCENDING:
+ sb.append(" descending");
+ break;
+ }
+ return sb.toString();
+ }
+ }
}
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 2010-01-06
22:13:11 UTC (rev 1535)
+++ trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/xpath/XPathParser.java 2010-01-06
23:48:32 UTC (rev 1536)
@@ -37,6 +37,7 @@
import org.jboss.dna.graph.GraphI18n;
import org.jboss.dna.graph.property.ValueFormatException;
import org.jboss.dna.graph.query.model.Operator;
+import org.jboss.dna.graph.query.model.Order;
import org.jboss.dna.graph.query.model.TypeSystem;
import org.jboss.dna.jcr.xpath.XPath.Add;
import org.jboss.dna.jcr.xpath.XPath.And;
@@ -63,6 +64,8 @@
import org.jboss.dna.jcr.xpath.XPath.NodeComparisonOperator;
import org.jboss.dna.jcr.xpath.XPath.NodeTest;
import org.jboss.dna.jcr.xpath.XPath.Or;
+import org.jboss.dna.jcr.xpath.XPath.OrderBy;
+import org.jboss.dna.jcr.xpath.XPath.OrderBySpec;
import org.jboss.dna.jcr.xpath.XPath.ParenthesizedExpression;
import org.jboss.dna.jcr.xpath.XPath.PathExpression;
import org.jboss.dna.jcr.xpath.XPath.ProcessingInstructionTest;
@@ -281,7 +284,8 @@
}
relative = false;
}
- PathExpression result = new PathExpression(relative,
parseRelativePathExpr(tokens).getSteps());
+ PathExpression relativeExpr = parseRelativePathExpr(tokens);
+ PathExpression result = new PathExpression(relative, relativeExpr.getSteps(),
relativeExpr.getOrderBy());
if (prependDependentOrSelf) {
result.getSteps().add(0, new DescendantOrSelf());
}
@@ -299,7 +303,8 @@
steps.add(parseStepExpr(tokens));
}
}
- return new PathExpression(true, steps);
+ OrderBy orderBy = parseOrderBy(tokens); // may be null
+ return new PathExpression(true, steps, orderBy);
}
protected StepExpression parseStepExpr( TokenStream tokens ) {
@@ -658,6 +663,37 @@
protected void parseSequenceType( TokenStream tokens ) {
}
+ protected OrderBy parseOrderBy( TokenStream tokens ) {
+ if (tokens.canConsume("order", "by")) {
+ List<OrderBySpec> specs = new ArrayList<OrderBySpec>();
+ do {
+ OrderBySpec spec = parseOrderBySpec(tokens);
+ specs.add(spec);
+ } while (tokens.canConsume(','));
+ if (!specs.isEmpty()) return new OrderBy(specs);
+ }
+ return null;
+ }
+
+ protected OrderBySpec parseOrderBySpec( TokenStream tokens ) {
+ if (tokens.canConsume('@')) {
+ NameTest attributeName = parseQName(tokens);
+ Order order = Order.ASCENDING;
+ if (tokens.canConsume("ascending")) order = Order.ASCENDING;
+ else if (tokens.canConsume("descending")) order =
Order.DESCENDING;
+ return new OrderBySpec(order, attributeName);
+ }
+ if (tokens.matches("jcr", ":", "score",
"(")) {
+ FunctionCall scoreFunction = parseFunctionCall(tokens);
+ Order order = Order.ASCENDING;
+ if (tokens.canConsume("ascending")) order = Order.ASCENDING;
+ else if (tokens.canConsume("descending")) order =
Order.DESCENDING;
+ return new OrderBySpec(order, scoreFunction);
+ }
+ throw new ParsingException(tokens.nextPosition(),
+ "Expected either 'jcr:score(tableName)'
or '@<propertyName>' but found " + tokens.consume());
+ }
+
/**
* Remove any leading and trailing single-quotes or double-quotes from the supplied
text.
*
Modified: 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 2010-01-06
22:13:11 UTC (rev 1535)
+++
trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/xpath/XPathToQueryTranslator.java 2010-01-06
23:48:32 UTC (rev 1536)
@@ -33,7 +33,11 @@
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.QueryBuilder.OrderByBuilder;
+import org.jboss.dna.graph.query.QueryBuilder.OrderByOperandBuilder;
+import org.jboss.dna.graph.query.model.AllNodes;
import org.jboss.dna.graph.query.model.Operator;
+import org.jboss.dna.graph.query.model.Query;
import org.jboss.dna.graph.query.model.QueryCommand;
import org.jboss.dna.graph.query.model.TypeSystem;
import org.jboss.dna.graph.query.parse.InvalidQueryException;
@@ -54,6 +58,8 @@
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.OrderBy;
+import org.jboss.dna.jcr.xpath.XPath.OrderBySpec;
import org.jboss.dna.jcr.xpath.XPath.ParenthesizedExpression;
import org.jboss.dna.jcr.xpath.XPath.PathExpression;
import org.jboss.dna.jcr.xpath.XPath.StepExpression;
@@ -221,6 +227,55 @@
translateSource(tableName, path, where);
}
where.end();
+
+ // Process the order-by clause ...
+ OrderBy orderBy = pathExpression.getOrderBy();
+ if (orderBy != null) {
+ OrderByBuilder orderByBuilder = builder.orderBy();
+ for (OrderBySpec spec : orderBy) {
+ OrderByOperandBuilder operandBuilder = null;
+ switch (spec.getOrder()) {
+ case ASCENDING:
+ operandBuilder = orderByBuilder.ascending();
+ break;
+ case DESCENDING:
+ operandBuilder = orderByBuilder.descending();
+ break;
+ }
+ assert operandBuilder != null;
+ if (spec.getAttributeName() != null) {
+ // This order by is defined by an attribute ...
+ NameTest attribute = spec.getAttributeName();
+ assert !attribute.isWildcard();
+ operandBuilder.propertyValue(tableName, attribute.toString());
+ builder.select(tableName + "." + attribute.toString());
+ } else {
+ // This order-by is defined by a "jcr:score" function ...
+ FunctionCall scoreFunction = spec.getScoreFunction();
+ assert scoreFunction != null;
+ List<Component> args = scoreFunction.getParameters();
+ String nameOfTableToScore = tableName;
+ if (!args.isEmpty()) {
+ if (args.size() == 1 && args.get(0) instanceof NameTest)
{
+ // Just the table name ...
+ NameTest tableNameTest = (NameTest)args.get(0);
+ nameOfTableToScore = tableNameTest.toString();
+ }
+ }
+ operandBuilder.fullTextSearchScore(nameOfTableToScore);
+ }
+ }
+ orderByBuilder.end();
+ }
+ // Try building this query, because we need to check the # of columns selected
and the # of sources ...
+ Query query = (Query)builder.query();
+ if (query.getColumns().isEmpty() && query.getSource() instanceof
AllNodes) {
+ // This is basically 'SELECT * FROM __ALLNODES__", which means that
no type was explicitly specified and
+ // nothing was selected from that type. According to JCR 1.0 Section 6.6.3.1,
this equates to
+ // 'SELECT * FROM [nt:base]', and since there is just one property on
nt:base (but many on __ALLNODES__)
+ // this really equates to 'SELECT [jcr:primaryType] FROM
__ALLNODES__'.
+ builder.select("jcr:primaryType");
+ }
}
/**
@@ -301,17 +356,17 @@
if (path.size() == 0) {
// This is a query against the root node ...
String alias = newAlias();
- builder.from("nt:base AS " + alias);
+ builder.fromAllNodesAs(alias);
where.path(alias).isEqualTo("/");
return alias;
}
String alias = newAlias();
if (tableName != null) {
// This is after some element(...) steps, so we need to join ...
- builder.join("nt:base AS " + alias);
+ builder.joinAllNodesAs(alias);
} else {
// This is the only part of the query ...
- builder.from("nt:base AS " + alias);
+ builder.fromAllNodesAs(alias);
}
tableName = alias;
if (path.size() == 1 && path.get(0).collapse() instanceof NameTest) {
@@ -326,7 +381,7 @@
}
} else {
// Must be just a bunch of descendant-or-self, axis and filter steps ...
- translatePathExpressionConstraint(new PathExpression(true, path), where,
alias);
+ translatePathExpressionConstraint(new PathExpression(true, path, null),
where, alias);
}
return tableName;
}
@@ -338,7 +393,7 @@
NameTest typeName = elementTest.getTypeName();
if (typeName.isWildcard()) {
tableName = newAlias();
- builder.from("nt:base AS " + tableName);
+ builder.fromAllNodesAs(tableName);
} else {
if (typeName.getLocalTest() == null) {
throw new InvalidQueryException(
@@ -414,7 +469,7 @@
// This adds the criteria that the child node exists ...
NameTest childName = (NameTest)predicate;
String alias = newAlias();
- builder.join("nt:base AS " + alias).onChildNode(tableName, alias);
+ builder.joinAllNodesAs(alias).onChildNode(tableName, alias);
if (!childName.isWildcard())
where.nodeName(alias).isEqualTo(nameFrom(childName));
tableName = alias;
} else if (predicate instanceof Comparison) {
@@ -532,7 +587,7 @@
} else if (param1 instanceof NameTest) {
// refers to child node, so we need to add a join ...
String alias = newAlias();
- builder.join("nt:base AS " + alias).onChildNode(tableName,
alias);
+ builder.joinAllNodesAs(alias).onChildNode(tableName, alias);
// Now add the criteria ...
where.search(alias, value);
tableName = alias;
@@ -570,31 +625,32 @@
// Requires that the descendant node with the relative path does exist ...
PathExpression pathExpr = (PathExpression)predicate;
List<StepExpression> steps = pathExpr.getSteps();
+ OrderBy orderBy = pathExpr.getOrderBy();
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);
+ return translatePredicate(new PathExpression(true, steps.subList(1,
steps.size()), orderBy), tableName, where);
}
if (firstStep instanceof NameTest) {
// Special case where this is similar to '[a/@id]'
NameTest childName = (NameTest)firstStep;
String alias = newAlias();
- builder.join("nt:base AS " + alias).onChildNode(tableName,
alias);
+ 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);
+ return translatePredicate(new PathExpression(true, steps.subList(1,
steps.size()), orderBy), alias, where);
}
if (firstStep instanceof DescendantOrSelf) {
// Special case where this is similar to '[a/@id]'
String alias = newAlias();
- builder.join("nt:base AS " + alias).onDescendant(tableName,
alias);
- return translatePredicate(new PathExpression(true, steps.subList(1,
steps.size())), alias, where);
+ builder.joinAllNodesAs(alias).onDescendant(tableName, alias);
+ return translatePredicate(new PathExpression(true, steps.subList(1,
steps.size()), orderBy), alias, where);
}
// Add the join ...
String alias = newAlias();
- builder.join("nt:base AS " + alias).onDescendant(tableName,
alias);
+ builder.joinAllNodesAs(alias).onDescendant(tableName, alias);
// Now add the criteria ...
translatePathExpressionConstraint(pathExpr, where, alias);
} else {
Modified: trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/JcrQueryManagerTest.java
===================================================================
--- trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/JcrQueryManagerTest.java 2010-01-06
22:13:11 UTC (rev 1535)
+++ trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/JcrQueryManagerTest.java 2010-01-06
23:48:32 UTC (rev 1536)
@@ -44,7 +44,7 @@
import org.jboss.dna.graph.connector.inmemory.InMemoryRepositorySource;
import org.jboss.dna.graph.property.Name;
import org.jboss.dna.graph.property.Path.Segment;
-import org.jboss.dna.jcr.JcrQueryManager.JcrQuery;
+import org.jboss.dna.jcr.JcrQueryManager.JcrQueryResult;
import org.jboss.dna.jcr.JcrRepository.Option;
import org.jboss.dna.jcr.JcrRepository.QueryLanguage;
import org.junit.After;
@@ -179,8 +179,8 @@
assertThat(result, is(notNullValue()));
if (print) {
System.out.println();
- System.out.println(query.getLanguage() + ": " +
query.getStatement());
- System.out.println(" --> : " + ((JcrQuery)query).getCommand());
+ System.out.println(query);
+ System.out.println(" plan -> " +
((JcrQueryResult)result).getPlan());
System.out.println(result);
}
assertThat(result.getNodes().getSize(), is(numberOfResults));
@@ -267,6 +267,30 @@
}
@Test
+ public void
shouldBeAbleToExecuteXPathQueryToFindAllUnstructuredNodesOrderedByPropertyValue() throws
RepositoryException {
+ Query query = session.getWorkspace()
+ .getQueryManager()
+ .createQuery("//element(*,nt:unstructured) order by
@jcr:primaryType", Query.XPATH);
+ assertThat(query, is(notNullValue()));
+ QueryResult result = query.execute();
+ print = true;
+ assertResults(query, result, 21);
+ assertThat(result, is(notNullValue()));
+ assertResultsHaveColumns(result, "jcr:primaryType",
"jcr:path", "jcr:score");
+ }
+
+ @Test
+ public void shouldBeAbleToExecuteXPathQueryToFindAllUnstructuredNodesOrderedByScore()
throws RepositoryException {
+ Query query =
session.getWorkspace().getQueryManager().createQuery("//element(*,nt:unstructured)
order by jcr:score()",
+ Query.XPATH);
+ assertThat(query, is(notNullValue()));
+ QueryResult result = query.execute();
+ assertResults(query, result, 21);
+ assertThat(result, is(notNullValue()));
+ assertResultsHaveColumns(result, "jcr:primaryType",
"jcr:path", "jcr:score");
+ }
+
+ @Test
public void shouldBeAbleToExecuteXPathQueryToFindSameNameSiblingsByIndex() throws
RepositoryException {
Query query =
session.getWorkspace().getQueryManager().createQuery("/jcr:root/Other/NodeA",
Query.XPATH);
assertThat(query, is(notNullValue()));
Modified: trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/JcrTckTest.java
===================================================================
--- trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/JcrTckTest.java 2010-01-06 22:13:11 UTC
(rev 1535)
+++ trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/JcrTckTest.java 2010-01-06 23:48:32 UTC
(rev 1536)
@@ -147,8 +147,6 @@
private static class LevelOneFeatureTests extends TestSuite {
protected LevelOneFeatureTests() {
super("JCR Level 1 API Tests");
- // We currently don't pass the tests in those suites that are commented
out
- // See
https://jira.jboss.org/jira/browse/DNA-285
addTestSuite(org.apache.jackrabbit.test.api.RootNodeTest.class);
addTestSuite(org.apache.jackrabbit.test.api.NodeReadMethodsTest.class);
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 2010-01-06
22:13:11 UTC (rev 1535)
+++ trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/xpath/XPathParserTest.java 2010-01-06
23:48:32 UTC (rev 1536)
@@ -36,6 +36,7 @@
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.graph.query.model.Order;
import org.jboss.dna.graph.query.model.TypeSystem;
import org.jboss.dna.jcr.xpath.XPath.And;
import org.jboss.dna.jcr.xpath.XPath.AnyKindTest;
@@ -55,6 +56,8 @@
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.OrderBy;
+import org.jboss.dna.jcr.xpath.XPath.OrderBySpec;
import org.jboss.dna.jcr.xpath.XPath.ParenthesizedExpression;
import org.jboss.dna.jcr.xpath.XPath.PathExpression;
import org.jboss.dna.jcr.xpath.XPath.SchemaAttributeTest;
@@ -137,6 +140,22 @@
literal("1"))));
}
+ @Test
+ public void shouldParseXPathExpressionWithOrderBy() {
+ assertParsable("//element(*, my:type) order by @a1,@a2",
pathExpr(orderBy(asc(nameTest("a1")), asc(nameTest("a2"))),
+
descendantOrSelf(),
+
axisStep(element(wildcard(), nameTest("my", "type")))));
+ assertParsable("//element(*, my:type) order by @p:a1, @a2",
+ pathExpr(orderBy(asc(nameTest("p", "a1")),
asc(nameTest("a2"))),
+ descendantOrSelf(),
+ axisStep(element(wildcard(), nameTest("my",
"type")))));
+ assertParsable("/jcr:root/element(name, my:type) order by @p:a1",
pathExpr(orderBy(asc(nameTest("p", "a1"))),
+
axisStep(nameTest("jcr", "root")),
+
axisStep(element(nameTest("name"),
+
nameTest("my", "type")))));
+ assertParsable("/jcr:root order by @p:a1",
pathExpr(orderBy(asc(nameTest("p", "a1"))),
axisStep(nameTest("jcr", "root"))));
+ }
+
@Ignore
@Test
public void shouldParseXPathExpressionsThatCombineSeparateExpressions() {
@@ -851,9 +870,69 @@
}
//
----------------------------------------------------------------------------------------------------------------
+ // order-by
+ //
----------------------------------------------------------------------------------------------------------------
+
+ @Test
+ public void shouldParseOrderByOneAttributeNameClause() {
+ assertThat(parser.parseOrderBy(tokenize("order by @a1")),
is(orderBy(asc(nameTest("a1")))));
+ assertThat(parser.parseOrderBy(tokenize("order by @a1 ascending")),
is(orderBy(asc(nameTest("a1")))));
+ assertThat(parser.parseOrderBy(tokenize("order by @a1 descending")),
is(orderBy(desc(nameTest("a1")))));
+ assertThat(parser.parseOrderBy(tokenize("order by @pre:a1")),
is(orderBy(asc(nameTest("pre", "a1")))));
+ assertThat(parser.parseOrderBy(tokenize("order by @pre:a1 ascending")),
is(orderBy(asc(nameTest("pre", "a1")))));
+ assertThat(parser.parseOrderBy(tokenize("order by @pre:a1
descending")), is(orderBy(desc(nameTest("pre", "a1")))));
+ }
+
+ @Test
+ public void shouldParseOrderByMultipleAttributeNameClauses() {
+ assertThat(parser.parseOrderBy(tokenize("order by @a1,@a2")),
is(orderBy(asc(nameTest("a1")), asc(nameTest("a2")))));
+ assertThat(parser.parseOrderBy(tokenize("order by @a1 ascending , @a2
ascending")), is(orderBy(asc(nameTest("a1")),
+
asc(nameTest("a2")))));
+ assertThat(parser.parseOrderBy(tokenize("order by @a1 descending, @a2
ascending")), is(orderBy(desc(nameTest("a1")),
+
asc(nameTest("a2")))));
+ assertThat(parser.parseOrderBy(tokenize("order by @a1 ascending , @a2
descending")), is(orderBy(asc(nameTest("a1")),
+
desc(nameTest("a2")))));
+ assertThat(parser.parseOrderBy(tokenize("order by @a1 descending, @a2
descending")), is(orderBy(desc(nameTest("a1")),
+
desc(nameTest("a2")))));
+ assertThat(parser.parseOrderBy(tokenize("order by @pre:a1, @pre:a2")),
is(orderBy(asc(nameTest("pre", "a1")),
+
asc(nameTest("pre", "a2")))));
+ assertThat(parser.parseOrderBy(tokenize("order by @a1 ascending, @pre:a2
ascending")), is(orderBy(asc(nameTest("a1")),
+
asc(nameTest("pre",
+
"a2")))));
+ assertThat(parser.parseOrderBy(tokenize("order by @a1 descending, @pre:a2
ascending")), is(orderBy(desc(nameTest("a1")),
+
asc(nameTest("pre",
+
"a2")))));
+ assertThat(parser.parseOrderBy(tokenize("order by @a1 ascending, @pre:a2
descending")), is(orderBy(asc(nameTest("a1")),
+
desc(nameTest("pre",
+
"a2")))));
+ assertThat(parser.parseOrderBy(tokenize("order by @a1 descending, @pre:a2
descending")),
+ is(orderBy(desc(nameTest("a1")),
desc(nameTest("pre", "a2")))));
+ }
+
+ //
----------------------------------------------------------------------------------------------------------------
// utility methods
//
----------------------------------------------------------------------------------------------------------------
+ protected XPath.OrderBy orderBy( OrderBySpec... specs ) {
+ return new XPath.OrderBy(Arrays.asList(specs));
+ }
+
+ protected OrderBySpec asc( FunctionCall scoreFunction ) {
+ return new OrderBySpec(Order.ASCENDING, scoreFunction);
+ }
+
+ protected OrderBySpec desc( FunctionCall scoreFunction ) {
+ return new OrderBySpec(Order.DESCENDING, scoreFunction);
+ }
+
+ protected OrderBySpec asc( NameTest attributeName ) {
+ return new OrderBySpec(Order.ASCENDING, attributeName);
+ }
+
+ protected OrderBySpec desc( NameTest attributeName ) {
+ return new OrderBySpec(Order.DESCENDING, attributeName);
+ }
+
protected TokenStream tokenize( String xpath ) {
Tokenizer tokenizer = new XPathParser.XPathTokenizer(false); // skip comments
return new TokenStream(xpath, tokenizer, true).start(); // case sensitive!!
@@ -878,17 +957,32 @@
}
protected PathExpression pathExpr( List<StepExpression> steps ) {
- return new PathExpression(false, steps);
+ return new PathExpression(false, steps, null);
}
+ protected PathExpression pathExpr( List<StepExpression> steps,
+ OrderBy orderBy ) {
+ return new PathExpression(false, steps, orderBy);
+ }
+
+ protected PathExpression pathExpr( OrderBy orderBy,
+ StepExpression... steps ) {
+ return pathExpr(Arrays.asList(steps), orderBy);
+ }
+
protected PathExpression relativePathExpr( StepExpression... steps ) {
return relativePathExpr(Arrays.asList(steps));
}
protected PathExpression relativePathExpr( List<StepExpression> steps ) {
- return new PathExpression(true, steps);
+ return new PathExpression(true, steps, null);
}
+ protected PathExpression relativePathExpr( List<StepExpression> steps,
+ OrderBy orderBy ) {
+ return new PathExpression(true, steps, orderBy);
+ }
+
protected Literal literal( String value ) {
return new Literal(value);
}
Modified:
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 2010-01-06
22:13:11 UTC (rev 1535)
+++
trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/xpath/XPathToQueryTranslatorTest.java 2010-01-06
23:48:32 UTC (rev 1536)
@@ -65,85 +65,96 @@
@Test
public void shouldTranslateFromXPathOfAnyNode() {
- assertThat(xpath("//element(*)"), isSql("SELECT * FROM [nt:base]
AS nodeSet1"));
- assertThat(xpath("/jcr:root//element(*)"), isSql("SELECT * FROM
[nt:base] AS nodeSet1"));
- assertThat(xpath("//*"), isSql("SELECT * FROM [nt:base] AS
nodeSet1"));
- assertThat(xpath("/jcr:root//*"), isSql("SELECT * FROM [nt:base]
AS nodeSet1"));
- assertThat(xpath("//."), isSql("SELECT * FROM [nt:base] AS
nodeSet1"));
- assertThat(xpath("/jcr:root//."), isSql("SELECT * FROM [nt:base]
AS nodeSet1"));
+ assertThat(xpath("//element(*)"), isSql("SELECT
nodeSet1.[jcr:primaryType] FROM __ALLNODES__ AS nodeSet1"));
+ assertThat(xpath("/jcr:root//element(*)"), isSql("SELECT
nodeSet1.[jcr:primaryType] FROM __ALLNODES__ AS nodeSet1"));
+ assertThat(xpath("//*"), isSql("SELECT nodeSet1.[jcr:primaryType]
FROM __ALLNODES__ AS nodeSet1"));
+ assertThat(xpath("/jcr:root//*"), isSql("SELECT
nodeSet1.[jcr:primaryType] FROM __ALLNODES__ AS nodeSet1"));
+ assertThat(xpath("//."), isSql("SELECT nodeSet1.[jcr:primaryType]
FROM __ALLNODES__ AS nodeSet1"));
+ assertThat(xpath("/jcr:root//."), isSql("SELECT
nodeSet1.[jcr:primaryType] FROM __ALLNODES__ AS nodeSet1"));
}
@Test
public void shouldTranslateFromXPathContainingExplicitRootPath() {
- assertThat(xpath("/jcr:root"), isSql("SELECT * FROM [nt:base] AS
nodeSet1 WHERE PATH(nodeSet1) = '/'"));
+ assertThat(xpath("/jcr:root"),
+ isSql("SELECT nodeSet1.[jcr:primaryType] FROM __ALLNODES__ AS
nodeSet1 WHERE PATH(nodeSet1) = '/'"));
}
@Test
public void shouldTranslateFromXPathContainingExplicitPath() {
- assertThat(xpath("/jcr:root/a"), isSql("SELECT * FROM [nt:base] AS
nodeSet1 WHERE PATH(nodeSet1) = '/a'"));
- assertThat(xpath("/jcr:root/a/b"), isSql("SELECT * FROM [nt:base]
AS nodeSet1 WHERE PATH(nodeSet1) = '/a/b'"));
- assertThat(xpath("/jcr:root/a/b/c"), isSql("SELECT * FROM
[nt:base] AS nodeSet1 WHERE PATH(nodeSet1) = '/a/b/c'"));
- assertThat(xpath("/jcr:root/a/b/c/d"), isSql("SELECT * FROM
[nt:base] AS nodeSet1 WHERE PATH(nodeSet1) = '/a/b/c/d'"));
+ assertThat(xpath("/jcr:root/a"),
+ isSql("SELECT nodeSet1.[jcr:primaryType] FROM __ALLNODES__ AS
nodeSet1 WHERE PATH(nodeSet1) = '/a'"));
+ assertThat(xpath("/jcr:root/a/b"),
+ isSql("SELECT nodeSet1.[jcr:primaryType] FROM __ALLNODES__ AS
nodeSet1 WHERE PATH(nodeSet1) = '/a/b'"));
+ assertThat(xpath("/jcr:root/a/b/c"),
+ isSql("SELECT nodeSet1.[jcr:primaryType] FROM __ALLNODES__ AS
nodeSet1 WHERE PATH(nodeSet1) = '/a/b/c'"));
+ assertThat(xpath("/jcr:root/a/b/c/d"),
+ isSql("SELECT nodeSet1.[jcr:primaryType] 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
[nt:base] AS nodeSet1 WHERE PATH(nodeSet1) = '/a[2]/b'"));
- assertThat(xpath("/jcr:root/a/b[3]"), isSql("SELECT * FROM
[nt:base] AS nodeSet1 WHERE PATH(nodeSet1) = '/a/b[3]'"));
- assertThat(xpath("/jcr:root/a[2]/b[3]"), isSql("SELECT * FROM
[nt:base] AS nodeSet1 WHERE PATH(nodeSet1) = '/a[2]/b[3]'"));
+ assertThat(xpath("/jcr:root/a[2]/b"),
+ isSql("SELECT nodeSet1.[jcr:primaryType] FROM __ALLNODES__ AS
nodeSet1 WHERE PATH(nodeSet1) = '/a[2]/b'"));
+ assertThat(xpath("/jcr:root/a/b[3]"),
+ isSql("SELECT nodeSet1.[jcr:primaryType] FROM __ALLNODES__ AS
nodeSet1 WHERE PATH(nodeSet1) = '/a/b[3]'"));
+ assertThat(xpath("/jcr:root/a[2]/b[3]"),
+ isSql("SELECT nodeSet1.[jcr:primaryType] FROM __ALLNODES__ AS
nodeSet1 WHERE PATH(nodeSet1) = '/a[2]/b[3]'"));
}
@Test
public void shouldTranslateFromXPathContainingExplicitPathWithWildcardChildNumbers()
{
- assertThat(xpath("/jcr:root/a[*]/b"), isSql("SELECT * FROM
[nt:base] AS nodeSet1 WHERE PATH(nodeSet1) = '/a/b'"));
- assertThat(xpath("/jcr:root/a/b[*]"), isSql("SELECT * FROM
[nt:base] AS nodeSet1 WHERE PATH(nodeSet1) = '/a/b'"));
- assertThat(xpath("/jcr:root/a[*]/b[*]"), isSql("SELECT * FROM
[nt:base] AS nodeSet1 WHERE PATH(nodeSet1) = '/a/b'"));
+ assertThat(xpath("/jcr:root/a[*]/b"),
+ isSql("SELECT nodeSet1.[jcr:primaryType] FROM __ALLNODES__ AS
nodeSet1 WHERE PATH(nodeSet1) = '/a/b'"));
+ assertThat(xpath("/jcr:root/a/b[*]"),
+ isSql("SELECT nodeSet1.[jcr:primaryType] FROM __ALLNODES__ AS
nodeSet1 WHERE PATH(nodeSet1) = '/a/b'"));
+ assertThat(xpath("/jcr:root/a[*]/b[*]"),
+ isSql("SELECT nodeSet1.[jcr:primaryType] FROM __ALLNODES__ AS
nodeSet1 WHERE PATH(nodeSet1) = '/a/b'"));
}
@Test
public void shouldTranslateFromXPathContainingPathWithDescendantOrSelf() {
assertThat(xpath("/jcr:root/a/b//c"),
- isSql("SELECT * FROM [nt:base] AS nodeSet1 WHERE PATH(nodeSet1) =
'/a/b/c' OR PATH(nodeSet1) LIKE '/a/b/%/c'"));
+ isSql("SELECT nodeSet1.[jcr:primaryType] 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 [nt:base] AS nodeSet1 WHERE PATH(nodeSet1) =
'/a/b[2]/c' OR PATH(nodeSet1) LIKE '/a/b[2]/%/c'"));
+ isSql("SELECT nodeSet1.[jcr:primaryType] 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 [nt:base] AS nodeSet1 WHERE PATH(nodeSet1) =
'/a/b/c[4]' OR PATH(nodeSet1) LIKE '/a/b/%/c[4]'"));
+ isSql("SELECT nodeSet1.[jcr:primaryType] 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 [nt:base] AS nodeSet1 WHERE PATH(nodeSet1) =
'/a/b[2]/c[4]' OR PATH(nodeSet1) LIKE '/a/b[2]/%/c[4]'"));
+ isSql("SELECT nodeSet1.[jcr:primaryType] 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 [nt:base] 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')"));
+ isSql("SELECT nodeSet1.[jcr:primaryType] 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 [nt:base] as nodeSet1 ON
ISCHILDNODE(nodeSet1,[my:type]) WHERE NAME(nodeSet1) = 'a' AND nodeSet1.id IS NOT
NULL"));
+ 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 [nt:base] as nodeSet1 ON
ISCHILDNODE(nodeSet1,[my:type]) JOIN [nt:base] as nodeSet2 ON
ISCHILDNODE(nodeSet2,nodeSet1) WHERE (NAME(nodeSet1) = 'a' AND NAME(nodeSet2) =
'b') AND nodeSet2.id IS NOT NULL"));
+ 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 [nt:base] as nodeSet1 ON
ISCHILDNODE(nodeSet1,[my:type]) JOIN [nt:base] 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)))"));
+ 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 [nt:base] as nodeSet1 ON
ISCHILDNODE(nodeSet1,[my:type]) JOIN [nt:base] 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)))"));
+ 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 [nt:base] as nodeSet1 ON
ISCHILDNODE(nodeSet1,[my:type]) JOIN [nt:base] 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'))))"));
+ 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 [nt:base] as nodeSet1 ON
ISCHILDNODE(nodeSet1,[my:type]) WHERE nodeSet1.id IS NOT NULL"));
+ 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 [nt:base] as nodeSet1 ON
ISCHILDNODE(nodeSet1,[my:type]) JOIN [nt:base] as nodeSet2 ON
ISCHILDNODE(nodeSet2,nodeSet1) WHERE nodeSet2.id IS NOT NULL"));
+ 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 [nt:base] as nodeSet1 ON
ISCHILDNODE(nodeSet1,[my:type]) JOIN [nt:base] as nodeSet2 ON
ISCHILDNODE(nodeSet2,nodeSet1) WHERE nodeSet2.id IS NOT NULL"));
+ 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 [nt:base] as nodeSet1 ON
ISDESCENDANTNODE(nodeSet1,[my:type]) WHERE nodeSet1.id IS NOT NULL"));
+ 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("/jcr:root/testroot/serializationNode[@jcr:primaryType]"),
- isSql("SELECT * FROM [nt:base] AS nodeSet1 WHERE PATH(nodeSet1) =
'/testroot/serializationNode' AND nodeSet1.[jcr:primaryType] IS NOT NULL"));
+ isSql("SELECT nodeSet1.[jcr:primaryType] FROM __ALLNODES__ AS
nodeSet1 WHERE PATH(nodeSet1) = '/testroot/serializationNode' AND
nodeSet1.[jcr:primaryType] IS NOT NULL"));
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"));
@@ -192,8 +203,8 @@
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 [nt:base] AS nodeSet1"));
- assertThat(xpath("//./(@id|@name)"), isSql("SELECT nodeSet1.id,
nodeSet1.name FROM [nt:base] AS nodeSet1"));
+ 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
@@ -208,53 +219,67 @@
@Test
public void shouldTranslateFromXPathOfAnyNodeWithName() {
- assertThat(xpath("//element(nodeName,*)"), isSql("SELECT * FROM
[nt:base] AS nodeSet1 WHERE NAME(nodeSet1) = 'nodeName'"));
+ assertThat(xpath("//element(nodeName,*)"),
+ isSql("SELECT nodeSet1.[jcr:primaryType] FROM __ALLNODES__ AS
nodeSet1 WHERE NAME(nodeSet1) = 'nodeName'"));
- assertThat(xpath("//element(nodeName,*)"), isSql("SELECT * FROM
[nt:base] AS nodeSet1 WHERE NAME(nodeSet1) = 'nodeName'"));
+ assertThat(xpath("//element(nodeName,*)"),
+ isSql("SELECT nodeSet1.[jcr:primaryType] FROM __ALLNODES__ AS
nodeSet1 WHERE NAME(nodeSet1) = 'nodeName'"));
- assertThat(xpath("//nodeName"), isSql("SELECT * FROM [nt:base] AS
nodeSet1 WHERE NAME(nodeSet1) = 'nodeName'"));
+ assertThat(xpath("//nodeName"),
+ isSql("SELECT nodeSet1.[jcr:primaryType] FROM __ALLNODES__ AS
nodeSet1 WHERE NAME(nodeSet1) = 'nodeName'"));
assertThat(xpath("/jcr:root//element(nodeName,*)"),
- isSql("SELECT * FROM [nt:base] AS nodeSet1 WHERE NAME(nodeSet1) =
'nodeName'"));
+ isSql("SELECT nodeSet1.[jcr:primaryType] FROM __ALLNODES__ AS
nodeSet1 WHERE NAME(nodeSet1) = 'nodeName'"));
- assertThat(xpath("/jcr:root//nodeName"), isSql("SELECT * FROM
[nt:base] AS nodeSet1 WHERE NAME(nodeSet1) = 'nodeName'"));
+ assertThat(xpath("/jcr:root//nodeName"),
+ isSql("SELECT nodeSet1.[jcr:primaryType] FROM __ALLNODES__ AS
nodeSet1 WHERE NAME(nodeSet1) = 'nodeName'"));
}
@Test
public void shouldTranslateFromXPathOfNodeWithNameUnderRoot() {
assertThat(xpath("/jcr:root/element(nodeName,*)"),
- isSql("SELECT * FROM [nt:base] AS nodeSet1 WHERE NAME(nodeSet1) =
'nodeName' AND DEPTH(nodeSet1) = CAST(1 AS LONG)"));
+ isSql("SELECT nodeSet1.[jcr:primaryType] FROM __ALLNODES__ AS
nodeSet1 WHERE NAME(nodeSet1) = 'nodeName' AND DEPTH(nodeSet1) = CAST(1 AS
LONG)"));
- assertThat(xpath("/jcr:root/nodeName"), isSql("SELECT * FROM
[nt:base] AS nodeSet1 WHERE PATH(nodeSet1) = '/nodeName'"));
+ assertThat(xpath("/jcr:root/nodeName"),
+ isSql("SELECT nodeSet1.[jcr:primaryType] FROM __ALLNODES__ AS
nodeSet1 WHERE PATH(nodeSet1) = '/nodeName'"));
- assertThat(xpath("nodeName"), isSql("SELECT * FROM [nt:base] AS
nodeSet1 WHERE PATH(nodeSet1) = '/nodeName'"));
+ assertThat(xpath("nodeName"),
+ isSql("SELECT nodeSet1.[jcr:primaryType] FROM __ALLNODES__ AS
nodeSet1 WHERE PATH(nodeSet1) = '/nodeName'"));
}
@Test
public void shouldTranslateFromXPathOfAnyNodeUsingPredicate() {
assertThat(xpath("//.[jcr:contains(.,'bar')]"),
- isSql("SELECT * FROM [nt:base] AS nodeSet1 WHERE
CONTAINS(nodeSet1.*,'bar')"));
+ isSql("SELECT nodeSet1.[jcr:primaryType] FROM __ALLNODES__ AS
nodeSet1 WHERE CONTAINS(nodeSet1.*,'bar')"));
assertThat(xpath("//.[jcr:contains(a,'bar')]"),
- isSql("SELECT * FROM [nt:base] AS nodeSet1 JOIN [nt:base] AS
nodeSet2 ON ISCHILDNODE(nodeSet2,nodeSet1) WHERE
CONTAINS(nodeSet2.*,'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 [nt:base] AS nodeSet1 WHERE
CONTAINS(nodeSet1.*,'bar')"));
+ isSql("SELECT nodeSet1.[jcr:primaryType] FROM __ALLNODES__ AS
nodeSet1 WHERE CONTAINS(nodeSet1.*,'bar')"));
assertThat(xpath("//*[jcr:contains(a,'bar')]"),
- isSql("SELECT * FROM [nt:base] AS nodeSet1 JOIN [nt:base] AS
nodeSet2 ON ISCHILDNODE(nodeSet2,nodeSet1) WHERE
CONTAINS(nodeSet2.*,'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 [nt:base] AS nodeSet1 JOIN [nt:base] AS
nodeSet2 ON ISCHILDNODE(nodeSet2,nodeSet1) WHERE NAME(nodeSet2) = 'a' AND
CONTAINS(nodeSet2.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 [nt:base] AS nodeSet1 JOIN [nt:base] AS
nodeSet2 ON ISCHILDNODE(nodeSet2,nodeSet1) JOIN [nt:base] AS nodeSet3 ON
ISCHILDNODE(nodeSet3,nodeSet2) WHERE NAME(nodeSet2) = 'a' AND
CONTAINS(nodeSet3.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 [nt:base] AS nodeSet1 JOIN [nt:base] AS
nodeSet2 ON ISCHILDNODE(nodeSet2,nodeSet1) WHERE NAME(nodeSet2) = 'a' AND
CONTAINS(nodeSet2.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 [nt:base] AS nodeSet1 JOIN [nt:base] AS
nodeSet2 ON ISCHILDNODE(nodeSet2,nodeSet1) JOIN [nt:base] AS nodeSet3 ON
ISCHILDNODE(nodeSet3,nodeSet2) WHERE NAME(nodeSet2) = 'a' AND
CONTAINS(nodeSet3.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 [nt:base] AS nodeSet1 JOIN [nt:base] AS
nodeSet2 ON ISCHILDNODE(nodeSet2,nodeSet1) WHERE NAME(nodeSet2) = 'a' AND
CONTAINS(nodeSet2.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 [nt:base] AS nodeSet1 JOIN [nt:base] AS
nodeSet2 ON ISCHILDNODE(nodeSet2,nodeSet1) JOIN [nt:base] AS nodeSet3 ON
ISCHILDNODE(nodeSet3,nodeSet2) WHERE NAME(nodeSet2) = 'a' AND
CONTAINS(nodeSet3.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 shouldTranslateFromXPathUsingOrderBy() {
+ assertThat(xpath("//element(*,*) order by @title"),
+ isSql("SELECT nodeSet1.title FROM __ALLNODES__ AS nodeSet1 ORDER
BY nodeSet1.title"));
+ assertThat(xpath("/jcr:root/testroot/*[@prop1] order by @prop1
ascending"),
+ isSql("SELECT nodeSet1.prop1 FROM __ALLNODES__ as nodeSet1 WHERE
PATH(nodeSet1) = '/testroot' AND nodeSet1.prop1 IS NOT NULL ORDER BY
nodeSet1.prop1"));
+ }
+
+ @Test
public void shouldParseXPathExpressions() {
xpath("/jcr:root/a/b/c");
xpath("/jcr:root/a/b/c[*]");
Modified: trunk/dna-jcr/src/test/resources/tck/repositoryForTckTests.xml
===================================================================
--- trunk/dna-jcr/src/test/resources/tck/repositoryForTckTests.xml 2010-01-06 22:13:11 UTC
(rev 1535)
+++ trunk/dna-jcr/src/test/resources/tck/repositoryForTckTests.xml 2010-01-06 23:48:32 UTC
(rev 1536)
@@ -55,7 +55,14 @@
<nt:unstructured jcr:name="node1"
prop1="<foo&foo>">
<nt:unstructured jcr:name="jcr:xmltext" jcr:xmlcharacters="This
is the text for node1[2]!" />
</nt:unstructured>
- <nt:unstructured jcr:name="node1"
prop1="<foo&foo>">
+ <nt:unstructured jcr:name="node1" prop1="prop1 value for
node1[3]">
<nt:unstructured jcr:name="jcr:xmltext" jcr:xmlcharacters="This
is the text for node1[3]!" />
</nt:unstructured>
+ <!--
+ XPathOrderByTest requires multiple nodes of type 'nt:unstructured' with
distinct 'prop1' values
+ -->
+ <nt:unstructured jcr:name="nodeForOrderBy"
prop1="propValue3"/>
+ <nt:unstructured jcr:name="anotherNodeForOrderBy"
prop1="propValue1">
+ <nt:unstructured jcr:name="aNestedNodeForOrderBy"
prop1="propValue2"/>
+ </nt:unstructured>
</testroot>
\ No newline at end of file