Author: rhauch
Date: 2009-11-17 13:46:21 -0500 (Tue, 17 Nov 2009)
New Revision: 1323
Added:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Between.java
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/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/PlanUtil.java
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/QueryBuilderTest.java
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/parse/SqlQueryParserTest.java
trunk/dna-search/src/main/java/org/jboss/dna/search/DualIndexLayout.java
trunk/dna-search/src/main/java/org/jboss/dna/search/KitchenSinkIndexLayout.java
Log:
DNA-552 Added to the abstract query model a new Constraint subclass 'Between' that
represents a constraint on a DynamicOperand such that the values are within a certain
range. Also added the corresponding support to the SQL parser for 'BETWEEN x AND
y' and 'NOT BETWEEN x AND y', which is the syntax commonly used in other SQL
grammars. (Note that this is an extension beyond JCRSQL2.) Also added support for
'BETWEEN x EXCLUSIVE AND y' and 'BETWEEN x AND y EXCLUSIVE' to be able to
specify that the ranges do or do not include the boundary value. This feature allows the
Lucene search/query implementation to apply a more efficient query to the indexes than
does an AND of two Comparison constraints.
Modified: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/QueryBuilder.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/QueryBuilder.java 2009-11-16
23:57:21 UTC (rev 1322)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/QueryBuilder.java 2009-11-17
18:46:21 UTC (rev 1323)
@@ -44,6 +44,7 @@
import org.jboss.dna.graph.property.ValueFormatException;
import org.jboss.dna.graph.query.model.AllNodes;
import org.jboss.dna.graph.query.model.And;
+import org.jboss.dna.graph.query.model.Between;
import org.jboss.dna.graph.query.model.BindVariableName;
import org.jboss.dna.graph.query.model.ChildNode;
import org.jboss.dna.graph.query.model.ChildNodeJoinCondition;
@@ -1290,36 +1291,27 @@
}
}
- public class CastAs {
- private final RightHandSide rhs;
- private final Object value;
+ public abstract class CastAs<ReturnType> {
+ protected final Object value;
- protected CastAs( RightHandSide rhs,
- Object value ) {
- this.rhs = rhs;
+ protected CastAs( Object value ) {
this.value = value;
}
- private ValueFactories factories() {
- return QueryBuilder.this.context.getValueFactories();
- }
-
/**
* Define the right-hand side literal value cast as the specified type.
*
* @param type the property type; may not be null
* @return the constraint builder; never null
*/
- public ConstraintBuilder as( PropertyType type ) {
- return rhs.comparisonBuilder.is(rhs.operator,
factories().getValueFactory(type).create(value));
- }
+ public abstract ReturnType as( PropertyType type );
/**
* Define the right-hand side literal value cast as a {@link
PropertyType#STRING}.
*
* @return the constraint builder; never null
*/
- public ConstraintBuilder asString() {
+ public ReturnType asString() {
return as(PropertyType.STRING);
}
@@ -1328,7 +1320,7 @@
*
* @return the constraint builder; never null
*/
- public ConstraintBuilder asBoolean() {
+ public ReturnType asBoolean() {
return as(PropertyType.BOOLEAN);
}
@@ -1337,7 +1329,7 @@
*
* @return the constraint builder; never null
*/
- public ConstraintBuilder asLong() {
+ public ReturnType asLong() {
return as(PropertyType.LONG);
}
@@ -1346,7 +1338,7 @@
*
* @return the constraint builder; never null
*/
- public ConstraintBuilder asDouble() {
+ public ReturnType asDouble() {
return as(PropertyType.DOUBLE);
}
@@ -1355,7 +1347,7 @@
*
* @return the constraint builder; never null
*/
- public ConstraintBuilder asDecimal() {
+ public ReturnType asDecimal() {
return as(PropertyType.DECIMAL);
}
@@ -1364,7 +1356,7 @@
*
* @return the constraint builder; never null
*/
- public ConstraintBuilder asDate() {
+ public ReturnType asDate() {
return as(PropertyType.DATE);
}
@@ -1373,7 +1365,7 @@
*
* @return the constraint builder; never null
*/
- public ConstraintBuilder asName() {
+ public ReturnType asName() {
return as(PropertyType.NAME);
}
@@ -1382,7 +1374,7 @@
*
* @return the constraint builder; never null
*/
- public ConstraintBuilder asPath() {
+ public ReturnType asPath() {
return as(PropertyType.PATH);
}
@@ -1391,7 +1383,7 @@
*
* @return the constraint builder; never null
*/
- public ConstraintBuilder asBinary() {
+ public ReturnType asBinary() {
return as(PropertyType.BINARY);
}
@@ -1400,7 +1392,7 @@
*
* @return the constraint builder; never null
*/
- public ConstraintBuilder asReference() {
+ public ReturnType asReference() {
return as(PropertyType.REFERENCE);
}
@@ -1409,7 +1401,7 @@
*
* @return the constraint builder; never null
*/
- public ConstraintBuilder asUri() {
+ public ReturnType asUri() {
return as(PropertyType.URI);
}
@@ -1418,11 +1410,88 @@
*
* @return the constraint builder; never null
*/
- public ConstraintBuilder asUuid() {
+ public ReturnType asUuid() {
return as(PropertyType.UUID);
}
}
+ public class CastAsRightHandSide extends CastAs<ConstraintBuilder> {
+ private final RightHandSide rhs;
+
+ protected CastAsRightHandSide( RightHandSide rhs,
+ Object value ) {
+ super(value);
+ this.rhs = rhs;
+ }
+
+ private ValueFactories factories() {
+ return QueryBuilder.this.context.getValueFactories();
+ }
+
+ /**
+ * Define the right-hand side literal value cast as the specified type.
+ *
+ * @param type the property type; may not be null
+ * @return the constraint builder; never null
+ */
+ @Override
+ public ConstraintBuilder as( PropertyType type ) {
+ return rhs.comparisonBuilder.is(rhs.operator,
factories().getValueFactory(type).create(value));
+ }
+ }
+
+ public class CastAsUpperBoundary extends CastAs<ConstraintBuilder> {
+ private final UpperBoundary upperBoundary;
+
+ protected CastAsUpperBoundary( UpperBoundary upperBoundary,
+ Object value ) {
+ super(value);
+ this.upperBoundary = upperBoundary;
+ }
+
+ private ValueFactories factories() {
+ return QueryBuilder.this.context.getValueFactories();
+ }
+
+ /**
+ * Define the right-hand side literal value cast as the specified type.
+ *
+ * @param type the property type; may not be null
+ * @return the constraint builder; never null
+ */
+ @Override
+ public ConstraintBuilder as( PropertyType type ) {
+ return upperBoundary.comparisonBuilder.isBetween(upperBoundary.lowerBound,
factories().getValueFactory(type)
+
.create(value));
+ }
+ }
+
+ public class CastAsLowerBoundary extends
CastAs<AndBuilder<UpperBoundary>> {
+ private final ComparisonBuilder builder;
+
+ protected CastAsLowerBoundary( ComparisonBuilder builder,
+ Object value ) {
+ super(value);
+ this.builder = builder;
+ }
+
+ private ValueFactories factories() {
+ return QueryBuilder.this.context.getValueFactories();
+ }
+
+ /**
+ * Define the left-hand side literal value cast as the specified type.
+ *
+ * @param type the property type; may not be null
+ * @return the builder to complete the constraint; never null
+ */
+ @Override
+ public AndBuilder<UpperBoundary> as( PropertyType type ) {
+ Object literal = factories().getValueFactory(type).create(value);
+ return new AndBuilder<UpperBoundary>(new UpperBoundary(builder, new
Literal(literal)));
+ }
+ }
+
public class RightHandSide {
protected final Operator operator;
protected final ComparisonBuilder comparisonBuilder;
@@ -1579,8 +1648,8 @@
* @param literal the literal value that is to be cast
* @return the constraint builder; never null
*/
- public CastAs cast( int literal ) {
- return new CastAs(this, literal);
+ public CastAs<ConstraintBuilder> cast( int literal ) {
+ return new CastAsRightHandSide(this, literal);
}
/**
@@ -1589,8 +1658,8 @@
* @param literal the literal value that is to be cast
* @return the constraint builder; never null
*/
- public CastAs cast( String literal ) {
- return new CastAs(this, literal);
+ public CastAs<ConstraintBuilder> cast( String literal ) {
+ return new CastAsRightHandSide(this, literal);
}
/**
@@ -1599,8 +1668,8 @@
* @param literal the literal value that is to be cast
* @return the constraint builder; never null
*/
- public CastAs cast( boolean literal ) {
- return new CastAs(this, literal);
+ public CastAs<ConstraintBuilder> cast( boolean literal ) {
+ return new CastAsRightHandSide(this, literal);
}
/**
@@ -1609,8 +1678,8 @@
* @param literal the literal value that is to be cast
* @return the constraint builder; never null
*/
- public CastAs cast( long literal ) {
- return new CastAs(this, literal);
+ public CastAs<ConstraintBuilder> cast( long literal ) {
+ return new CastAsRightHandSide(this, literal);
}
/**
@@ -1619,8 +1688,8 @@
* @param literal the literal value that is to be cast
* @return the constraint builder; never null
*/
- public CastAs cast( double literal ) {
- return new CastAs(this, literal);
+ public CastAs<ConstraintBuilder> cast( double literal ) {
+ return new CastAsRightHandSide(this, literal);
}
/**
@@ -1629,8 +1698,8 @@
* @param literal the literal value that is to be cast
* @return the constraint builder; never null
*/
- public CastAs cast( BigDecimal literal ) {
- return new CastAs(this, literal);
+ public CastAs<ConstraintBuilder> cast( BigDecimal literal ) {
+ return new CastAsRightHandSide(this, literal);
}
/**
@@ -1639,8 +1708,8 @@
* @param literal the literal value that is to be cast
* @return the constraint builder; never null
*/
- public CastAs cast( DateTime literal ) {
- return new CastAs(this, literal.toUtcTimeZone());
+ public CastAs<ConstraintBuilder> cast( DateTime literal ) {
+ return new CastAsRightHandSide(this, literal.toUtcTimeZone());
}
/**
@@ -1649,8 +1718,8 @@
* @param literal the literal value that is to be cast
* @return the constraint builder; never null
*/
- public CastAs cast( Name literal ) {
- return new CastAs(this, literal);
+ public CastAs<ConstraintBuilder> cast( Name literal ) {
+ return new CastAsRightHandSide(this, literal);
}
/**
@@ -1659,8 +1728,8 @@
* @param literal the literal value that is to be cast
* @return the constraint builder; never null
*/
- public CastAs cast( Path literal ) {
- return new CastAs(this, literal);
+ public CastAs<ConstraintBuilder> cast( Path literal ) {
+ return new CastAsRightHandSide(this, literal);
}
/**
@@ -1669,8 +1738,8 @@
* @param literal the literal value that is to be cast
* @return the constraint builder; never null
*/
- public CastAs cast( UUID literal ) {
- return new CastAs(this, literal);
+ public CastAs<ConstraintBuilder> cast( UUID literal ) {
+ return new CastAsRightHandSide(this, literal);
}
/**
@@ -1679,17 +1748,537 @@
* @param literal the literal value that is to be cast
* @return the constraint builder; never null
*/
- public CastAs cast( URI literal ) {
- return new CastAs(this, literal);
+ public CastAs<ConstraintBuilder> cast( URI literal ) {
+ return new CastAsRightHandSide(this, literal);
}
}
+ public class UpperBoundary {
+ protected final StaticOperand lowerBound;
+ protected final ComparisonBuilder comparisonBuilder;
+
+ protected UpperBoundary( ComparisonBuilder comparisonBuilder,
+ StaticOperand lowerBound ) {
+ this.lowerBound = lowerBound;
+ this.comparisonBuilder = comparisonBuilder;
+ }
+
+ /**
+ * Define the upper boundary value of a range.
+ *
+ * @param literal the literal value;
+ * @return the constraint builder; never null
+ */
+ public ConstraintBuilder literal( String literal ) {
+ return comparisonBuilder.isBetween(lowerBound, literal);
+ }
+
+ /**
+ * Define the upper boundary value of a range.
+ *
+ * @param literal the literal value;
+ * @return the constraint builder; never null
+ */
+ public ConstraintBuilder literal( int literal ) {
+ return comparisonBuilder.isBetween(lowerBound, literal);
+ }
+
+ /**
+ * Define the upper boundary value of a range.
+ *
+ * @param literal the literal value;
+ * @return the constraint builder; never null
+ */
+ public ConstraintBuilder literal( long literal ) {
+ return comparisonBuilder.isBetween(lowerBound, literal);
+ }
+
+ /**
+ * Define the upper boundary value of a range.
+ *
+ * @param literal the literal value;
+ * @return the constraint builder; never null
+ */
+ public ConstraintBuilder literal( float literal ) {
+ return comparisonBuilder.isBetween(lowerBound, literal);
+ }
+
+ /**
+ * Define the upper boundary value of a range.
+ *
+ * @param literal the literal value;
+ * @return the constraint builder; never null
+ */
+ public ConstraintBuilder literal( double literal ) {
+ return comparisonBuilder.isBetween(lowerBound, literal);
+ }
+
+ /**
+ * Define the upper boundary value of a range.
+ *
+ * @param literal the literal value;
+ * @return the constraint builder; never null
+ */
+ public ConstraintBuilder literal( DateTime literal ) {
+ return comparisonBuilder.isBetween(lowerBound, literal);
+ }
+
+ /**
+ * Define the upper boundary value of a range.
+ *
+ * @param literal the literal value;
+ * @return the constraint builder; never null
+ */
+ public ConstraintBuilder literal( Path literal ) {
+ return comparisonBuilder.isBetween(lowerBound, literal);
+ }
+
+ /**
+ * Define the upper boundary value of a range.
+ *
+ * @param literal the literal value;
+ * @return the constraint builder; never null
+ */
+ public ConstraintBuilder literal( Name literal ) {
+ return comparisonBuilder.isBetween(lowerBound, literal);
+ }
+
+ /**
+ * Define the upper boundary value of a range.
+ *
+ * @param literal the literal value;
+ * @return the constraint builder; never null
+ */
+ public ConstraintBuilder literal( URI literal ) {
+ return comparisonBuilder.isBetween(lowerBound, literal);
+ }
+
+ /**
+ * Define the upper boundary value of a range.
+ *
+ * @param literal the literal value;
+ * @return the constraint builder; never null
+ */
+ public ConstraintBuilder literal( UUID literal ) {
+ return comparisonBuilder.isBetween(lowerBound, literal);
+ }
+
+ /**
+ * Define the upper boundary value of a range.
+ *
+ * @param literal the literal value;
+ * @return the constraint builder; never null
+ */
+ public ConstraintBuilder literal( Binary literal ) {
+ return comparisonBuilder.isBetween(lowerBound, literal);
+ }
+
+ /**
+ * Define the upper boundary value of a range.
+ *
+ * @param literal the literal value;
+ * @return the constraint builder; never null
+ */
+ public ConstraintBuilder literal( BigDecimal literal ) {
+ return comparisonBuilder.isBetween(lowerBound, literal);
+ }
+
+ /**
+ * Define the upper boundary value of a range.
+ *
+ * @param literal the literal value;
+ * @return the constraint builder; never null
+ */
+ public ConstraintBuilder literal( boolean literal ) {
+ return comparisonBuilder.isBetween(lowerBound, literal);
+ }
+
+ /**
+ * Define the upper boundary value of a range.
+ *
+ * @param variableName the name of the variable
+ * @return the constraint builder; never null
+ */
+ public ConstraintBuilder variable( String variableName ) {
+ return comparisonBuilder.constraintBuilder.setConstraint(new
Between(comparisonBuilder.left, lowerBound,
+ new
BindVariableName(variableName)));
+ }
+
+ /**
+ * Define the upper boundary value of a range.
+ *
+ * @param literal the literal value that is to be cast
+ * @return the constraint builder; never null
+ */
+ public CastAs<ConstraintBuilder> cast( int literal ) {
+ return new CastAsUpperBoundary(this, literal);
+ }
+
+ /**
+ * Define the upper boundary value of a range.
+ *
+ * @param literal the literal value that is to be cast
+ * @return the constraint builder; never null
+ */
+ public CastAs<ConstraintBuilder> cast( String literal ) {
+ return new CastAsUpperBoundary(this, literal);
+ }
+
+ /**
+ * Define the upper boundary value of a range.
+ *
+ * @param literal the literal value that is to be cast
+ * @return the constraint builder; never null
+ */
+ public CastAs<ConstraintBuilder> cast( boolean literal ) {
+ return new CastAsUpperBoundary(this, literal);
+ }
+
+ /**
+ * Define the upper boundary value of a range.
+ *
+ * @param literal the literal value that is to be cast
+ * @return the constraint builder; never null
+ */
+ public CastAs<ConstraintBuilder> cast( long literal ) {
+ return new CastAsUpperBoundary(this, literal);
+ }
+
+ /**
+ * Define the upper boundary value of a range.
+ *
+ * @param literal the literal value that is to be cast
+ * @return the constraint builder; never null
+ */
+ public CastAs<ConstraintBuilder> cast( double literal ) {
+ return new CastAsUpperBoundary(this, literal);
+ }
+
+ /**
+ * Define the upper boundary value of a range.
+ *
+ * @param literal the literal value that is to be cast
+ * @return the constraint builder; never null
+ */
+ public CastAs<ConstraintBuilder> cast( BigDecimal literal ) {
+ return new CastAsUpperBoundary(this, literal);
+ }
+
+ /**
+ * Define the upper boundary value of a range.
+ *
+ * @param literal the literal value that is to be cast
+ * @return the constraint builder; never null
+ */
+ public CastAs<ConstraintBuilder> cast( DateTime literal ) {
+ return new CastAsUpperBoundary(this, literal.toUtcTimeZone());
+ }
+
+ /**
+ * Define the upper boundary value of a range.
+ *
+ * @param literal the literal value that is to be cast
+ * @return the constraint builder; never null
+ */
+ public CastAs<ConstraintBuilder> cast( Name literal ) {
+ return new CastAsUpperBoundary(this, literal);
+ }
+
+ /**
+ * Define the upper boundary value of a range.
+ *
+ * @param literal the literal value that is to be cast
+ * @return the constraint builder; never null
+ */
+ public CastAs<ConstraintBuilder> cast( Path literal ) {
+ return new CastAsUpperBoundary(this, literal);
+ }
+
+ /**
+ * Define the upper boundary value of a range.
+ *
+ * @param literal the literal value that is to be cast
+ * @return the constraint builder; never null
+ */
+ public CastAs<ConstraintBuilder> cast( UUID literal ) {
+ return new CastAsUpperBoundary(this, literal);
+ }
+
+ /**
+ * Define the upper boundary value of a range.
+ *
+ * @param literal the literal value that is to be cast
+ * @return the constraint builder; never null
+ */
+ public CastAs<ConstraintBuilder> cast( URI literal ) {
+ return new CastAsUpperBoundary(this, literal);
+ }
+ }
+
+ public class LowerBoundary {
+ protected final ComparisonBuilder comparisonBuilder;
+
+ protected LowerBoundary( ComparisonBuilder comparisonBuilder ) {
+ this.comparisonBuilder = comparisonBuilder;
+ }
+
+ /**
+ * Define the lower boundary value of a range.
+ *
+ * @param literal the literal value;
+ * @return the constraint builder; never null
+ */
+ public AndBuilder<UpperBoundary> literal( String literal ) {
+ return new AndBuilder<UpperBoundary>(new
UpperBoundary(comparisonBuilder, new Literal(literal)));
+ }
+
+ /**
+ * Define the lower boundary value of a range.
+ *
+ * @param literal the literal value;
+ * @return the constraint builder; never null
+ */
+ public AndBuilder<UpperBoundary> literal( int literal ) {
+ return new AndBuilder<UpperBoundary>(new
UpperBoundary(comparisonBuilder, new Literal(literal)));
+ }
+
+ /**
+ * Define the lower boundary value of a range.
+ *
+ * @param literal the literal value;
+ * @return the constraint builder; never null
+ */
+ public AndBuilder<UpperBoundary> literal( long literal ) {
+ return new AndBuilder<UpperBoundary>(new
UpperBoundary(comparisonBuilder, new Literal(literal)));
+ }
+
+ /**
+ * Define the lower boundary value of a range.
+ *
+ * @param literal the literal value;
+ * @return the constraint builder; never null
+ */
+ public AndBuilder<UpperBoundary> literal( float literal ) {
+ return new AndBuilder<UpperBoundary>(new
UpperBoundary(comparisonBuilder, new Literal(literal)));
+ }
+
+ /**
+ * Define the lower boundary value of a range.
+ *
+ * @param literal the literal value;
+ * @return the constraint builder; never null
+ */
+ public AndBuilder<UpperBoundary> literal( double literal ) {
+ return new AndBuilder<UpperBoundary>(new
UpperBoundary(comparisonBuilder, new Literal(literal)));
+ }
+
+ /**
+ * Define the lower boundary value of a range.
+ *
+ * @param literal the literal value;
+ * @return the constraint builder; never null
+ */
+ public AndBuilder<UpperBoundary> literal( DateTime literal ) {
+ return new AndBuilder<UpperBoundary>(new
UpperBoundary(comparisonBuilder, new Literal(literal)));
+ }
+
+ /**
+ * Define the lower boundary value of a range.
+ *
+ * @param literal the literal value;
+ * @return the constraint builder; never null
+ */
+ public AndBuilder<UpperBoundary> literal( Path literal ) {
+ return new AndBuilder<UpperBoundary>(new
UpperBoundary(comparisonBuilder, new Literal(literal)));
+ }
+
+ /**
+ * Define the lower boundary value of a range.
+ *
+ * @param literal the literal value;
+ * @return the constraint builder; never null
+ */
+ public AndBuilder<UpperBoundary> literal( Name literal ) {
+ return new AndBuilder<UpperBoundary>(new
UpperBoundary(comparisonBuilder, new Literal(literal)));
+ }
+
+ /**
+ * Define the lower boundary value of a range.
+ *
+ * @param literal the literal value;
+ * @return the constraint builder; never null
+ */
+ public AndBuilder<UpperBoundary> literal( URI literal ) {
+ return new AndBuilder<UpperBoundary>(new
UpperBoundary(comparisonBuilder, new Literal(literal)));
+ }
+
+ /**
+ * Define the lower boundary value of a range.
+ *
+ * @param literal the literal value;
+ * @return the constraint builder; never null
+ */
+ public AndBuilder<UpperBoundary> literal( UUID literal ) {
+ return new AndBuilder<UpperBoundary>(new
UpperBoundary(comparisonBuilder, new Literal(literal)));
+ }
+
+ /**
+ * Define the lower boundary value of a range.
+ *
+ * @param literal the literal value;
+ * @return the constraint builder; never null
+ */
+ public AndBuilder<UpperBoundary> literal( Binary literal ) {
+ return new AndBuilder<UpperBoundary>(new
UpperBoundary(comparisonBuilder, new Literal(literal)));
+ }
+
+ /**
+ * Define the lower boundary value of a range.
+ *
+ * @param literal the literal value;
+ * @return the constraint builder; never null
+ */
+ public AndBuilder<UpperBoundary> literal( BigDecimal literal ) {
+ return new AndBuilder<UpperBoundary>(new
UpperBoundary(comparisonBuilder, new Literal(literal)));
+ }
+
+ /**
+ * Define the lower boundary value of a range.
+ *
+ * @param literal the literal value;
+ * @return the constraint builder; never null
+ */
+ public AndBuilder<UpperBoundary> literal( boolean literal ) {
+ return new AndBuilder<UpperBoundary>(new
UpperBoundary(comparisonBuilder, new Literal(literal)));
+ }
+
+ /**
+ * Define the lower boundary value of a range.
+ *
+ * @param variableName the name of the variable
+ * @return the constraint builder; never null
+ */
+ public AndBuilder<UpperBoundary> variable( String variableName ) {
+ return new AndBuilder<UpperBoundary>(new
UpperBoundary(comparisonBuilder, new BindVariableName(variableName)));
+ }
+
+ /**
+ * Define the lower boundary value of a range.
+ *
+ * @param literal the literal value that is to be cast
+ * @return the constraint builder; never null
+ */
+ public CastAs<AndBuilder<UpperBoundary>> cast( int literal ) {
+ return new CastAsLowerBoundary(comparisonBuilder, literal);
+ }
+
+ /**
+ * Define the lower boundary value of a range.
+ *
+ * @param literal the literal value that is to be cast
+ * @return the constraint builder; never null
+ */
+ public CastAs<AndBuilder<UpperBoundary>> cast( String literal ) {
+ return new CastAsLowerBoundary(comparisonBuilder, literal);
+ }
+
+ /**
+ * Define the lower boundary value of a range.
+ *
+ * @param literal the literal value that is to be cast
+ * @return the constraint builder; never null
+ */
+ public CastAs<AndBuilder<UpperBoundary>> cast( boolean literal ) {
+ return new CastAsLowerBoundary(comparisonBuilder, literal);
+ }
+
+ /**
+ * Define the lower boundary value of a range.
+ *
+ * @param literal the literal value that is to be cast
+ * @return the constraint builder; never null
+ */
+ public CastAs<AndBuilder<UpperBoundary>> cast( long literal ) {
+ return new CastAsLowerBoundary(comparisonBuilder, literal);
+ }
+
+ /**
+ * Define the lower boundary value of a range.
+ *
+ * @param literal the literal value that is to be cast
+ * @return the constraint builder; never null
+ */
+ public CastAs<AndBuilder<UpperBoundary>> cast( double literal ) {
+ return new CastAsLowerBoundary(comparisonBuilder, literal);
+ }
+
+ /**
+ * Define the lower boundary value of a range.
+ *
+ * @param literal the literal value that is to be cast
+ * @return the constraint builder; never null
+ */
+ public CastAs<AndBuilder<UpperBoundary>> cast( BigDecimal literal )
{
+ return new CastAsLowerBoundary(comparisonBuilder, literal);
+ }
+
+ /**
+ * Define the lower boundary value of a range.
+ *
+ * @param literal the literal value that is to be cast
+ * @return the constraint builder; never null
+ */
+ public CastAs<AndBuilder<UpperBoundary>> cast( DateTime literal ) {
+ return new CastAsLowerBoundary(comparisonBuilder, literal);
+ }
+
+ /**
+ * Define the lower boundary value of a range.
+ *
+ * @param literal the literal value that is to be cast
+ * @return the constraint builder; never null
+ */
+ public CastAs<AndBuilder<UpperBoundary>> cast( Name literal ) {
+ return new CastAsLowerBoundary(comparisonBuilder, literal);
+ }
+
+ /**
+ * Define the lower boundary value of a range.
+ *
+ * @param literal the literal value that is to be cast
+ * @return the constraint builder; never null
+ */
+ public CastAs<AndBuilder<UpperBoundary>> cast( Path literal ) {
+ return new CastAsLowerBoundary(comparisonBuilder, literal);
+ }
+
+ /**
+ * Define the lower boundary value of a range.
+ *
+ * @param literal the literal value that is to be cast
+ * @return the constraint builder; never null
+ */
+ public CastAs<AndBuilder<UpperBoundary>> cast( UUID literal ) {
+ return new CastAsLowerBoundary(comparisonBuilder, literal);
+ }
+
+ /**
+ * Define the lower boundary value of a range.
+ *
+ * @param literal the literal value that is to be cast
+ * @return the constraint builder; never null
+ */
+ public CastAs<AndBuilder<UpperBoundary>> cast( URI literal ) {
+ return new CastAsLowerBoundary(comparisonBuilder, literal);
+ }
+ }
+
/**
* An interface used to set the right-hand side of a constraint.
*/
public class ComparisonBuilder {
- private final DynamicOperand left;
- private final ConstraintBuilder constraintBuilder;
+ protected final DynamicOperand left;
+ protected final ConstraintBuilder constraintBuilder;
protected ComparisonBuilder( ConstraintBuilder constraintBuilder,
DynamicOperand left ) {
@@ -1815,10 +2404,28 @@
public ConstraintBuilder is( Operator operator,
Object literal ) {
assert operator != null;
- return this.constraintBuilder.setConstraint(new Comparison(left, operator,
new Literal(literal)));
+ Literal value = literal instanceof Literal ? (Literal)literal : new
Literal(literal);
+ return this.constraintBuilder.setConstraint(new Comparison(left, operator,
value));
}
/**
+ * Define the right-hand-side of the constraint using the supplied operator.
+ *
+ * @param lowerBoundLiteral the literal value that represents the lower bound of
the range (inclusive)
+ * @param upperBoundLiteral the literal value that represents the upper bound of
the range (inclusive)
+ * @return the builder used to create the constraint clause, ready to be used to
create other constraints clauses or
+ * complete already-started clauses; never null
+ */
+ public ConstraintBuilder isBetween( Object lowerBoundLiteral,
+ Object upperBoundLiteral ) {
+ assert lowerBoundLiteral != null;
+ assert upperBoundLiteral != null;
+ Literal lower = lowerBoundLiteral instanceof Literal ?
(Literal)lowerBoundLiteral : new Literal(lowerBoundLiteral);
+ Literal upper = upperBoundLiteral instanceof Literal ?
(Literal)upperBoundLiteral : new Literal(upperBoundLiteral);
+ return this.constraintBuilder.setConstraint(new Between(left, lower,
upper));
+ }
+
+ /**
* Define the right-hand-side of the constraint to be equivalent to the value of
the supplied variable.
*
* @param variableName the name of the variable
@@ -1971,5 +2578,33 @@
public ConstraintBuilder isNotEqualTo( Object literal ) {
return is(Operator.NOT_EQUAL_TO, literal);
}
+
+ /**
+ * Define the constraint as a range between a lower boundary and an upper
boundary.
+ *
+ * @return the interface used to specify the lower boundary boundary, the upper
boundary, and which will return the
+ * builder interface; never null
+ */
+ public LowerBoundary isBetween() {
+ return new LowerBoundary(this);
+ }
}
+
+ public class AndBuilder<T> {
+ private final T object;
+
+ protected AndBuilder( T object ) {
+ assert object != null;
+ this.object = object;
+ }
+
+ /**
+ * Return the component
+ *
+ * @return the component; never null
+ */
+ public T and() {
+ return this.object;
+ }
+ }
}
Added: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Between.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Between.java
(rev 0)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Between.java 2009-11-17
18:46:21 UTC (rev 1323)
@@ -0,0 +1,174 @@
+/*
+ * 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 constraint that evaluates to true when the value defined by the dynamic operand
evaluates to be within the specified range.
+ */
+@Immutable
+public class Between extends Constraint {
+
+ private final DynamicOperand operand;
+ private final StaticOperand lowerBound;
+ private final StaticOperand upperBound;
+ private final boolean includeLowerBound;
+ private final boolean includeUpperBound;
+ private final int hc;
+
+ /**
+ * Create a constraint that the values of the supplied dynamic operand are between
the lower and upper bounds (inclusive).
+ *
+ * @param operand the dynamic operand describing the values that are to be
constrained
+ * @param lowerBound the lower bound of the range
+ * @param upperBound the upper bound of the range
+ * @throws IllegalArgumentException if any of the arguments are null
+ */
+ public Between( DynamicOperand operand,
+ StaticOperand lowerBound,
+ StaticOperand upperBound ) {
+ this(operand, lowerBound, upperBound, true, true);
+ }
+
+ /**
+ * Create a constraint that the values of the supplied dynamic operand are between
the lower and upper bounds, specifying
+ * whether the boundary values are to be included in the range.
+ *
+ * @param operand the dynamic operand describing the values that are to be
constrained
+ * @param lowerBound the lower bound of the range
+ * @param upperBound the upper bound of the range
+ * @param includeLowerBound true if the lower boundary value is not be included
+ * @param includeUpperBound true if the upper boundary value is not be included
+ * @throws IllegalArgumentException if any of the arguments are null
+ */
+ public Between( DynamicOperand operand,
+ StaticOperand lowerBound,
+ StaticOperand upperBound,
+ boolean includeLowerBound,
+ boolean includeUpperBound ) {
+ CheckArg.isNotNull(operand, "operand");
+ CheckArg.isNotNull(lowerBound, "lowerBound");
+ CheckArg.isNotNull(upperBound, "upperBound");
+ this.operand = operand;
+ this.lowerBound = lowerBound;
+ this.upperBound = upperBound;
+ this.includeLowerBound = includeLowerBound;
+ this.includeUpperBound = includeUpperBound;
+ this.hc = HashCode.compute(this.operand, this.lowerBound, this.upperBound);
+ }
+
+ /**
+ * Get the dynamic operand specification.
+ *
+ * @return the dynamic operand; never null
+ */
+ public final DynamicOperand getOperand() {
+ return operand;
+ }
+
+ /**
+ * @return lowerBound
+ */
+ public StaticOperand getLowerBound() {
+ return lowerBound;
+ }
+
+ /**
+ * @return upperBound
+ */
+ public StaticOperand getUpperBound() {
+ return upperBound;
+ }
+
+ /**
+ * Return whether the lower bound is to be included in the results.
+ *
+ * @return true if the {@link #getLowerBound() lower bound} is to be included, or
false otherwise
+ */
+ public boolean isLowerBoundIncluded() {
+ return includeLowerBound;
+ }
+
+ /**
+ * Return whether the upper bound is to be included in the results.
+ *
+ * @return true if the {@link #getUpperBound() upper bound} is to be included, or
false otherwise
+ */
+ public boolean isUpperBoundIncluded() {
+ return includeUpperBound;
+ }
+
+ /**
+ * {@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 Between) {
+ Between that = (Between)obj;
+ if (this.hc != that.hc) return false;
+ if (!this.operand.equals(that.operand)) return false;
+ if (!this.lowerBound.equals(that.lowerBound)) return false;
+ if (!this.upperBound.equals(that.upperBound)) return false;
+ if (this.includeLowerBound != that.includeLowerBound) return false;
+ if (this.includeUpperBound != that.includeUpperBound) return false;
+ return true;
+ }
+ 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);
+ }
+}
Property changes on:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Between.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Modified: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Visitor.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Visitor.java 2009-11-16
23:57:21 UTC (rev 1322)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Visitor.java 2009-11-17
18:46:21 UTC (rev 1323)
@@ -32,6 +32,8 @@
void visit( And obj );
+ void visit( Between obj );
+
void visit( BindVariableName obj );
void visit( ChildNode obj );
Modified: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Visitors.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Visitors.java 2009-11-16
23:57:21 UTC (rev 1322)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Visitors.java 2009-11-17
18:46:21 UTC (rev 1323)
@@ -239,6 +239,14 @@
/**
* {@inheritDoc}
*
+ * @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.Between)
+ */
+ public void visit( Between obj ) {
+ }
+
+ /**
+ * {@inheritDoc}
+ *
* @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.BindVariableName)
*/
public void visit( BindVariableName obj ) {
@@ -573,6 +581,19 @@
/**
* {@inheritDoc}
*
+ * @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.Between)
+ */
+ public void visit( Between between ) {
+ strategy.visit(between);
+ enqueue(between.getOperand());
+ enqueue(between.getLowerBound());
+ enqueue(between.getUpperBound());
+ visitNext();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
* @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.BindVariableName)
*/
public void visit( BindVariableName variableName ) {
@@ -1004,6 +1025,21 @@
/**
* {@inheritDoc}
*
+ * @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.Between)
+ */
+ public void visit( Between between ) {
+ between.getOperand().accept(this);
+ append(" BETWEEN ");
+ between.getLowerBound().accept(this);
+ if (!between.isLowerBoundIncluded()) append(" EXCLUSIVE");
+ append(" AND ");
+ between.getUpperBound().accept(this);
+ if (!between.isUpperBoundIncluded()) append(" EXCLUSIVE");
+ }
+
+ /**
+ * {@inheritDoc}
+ *
* @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.BindVariableName)
*/
public void visit( BindVariableName variable ) {
Modified:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/parse/SqlQueryParser.java
===================================================================
---
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/parse/SqlQueryParser.java 2009-11-16
23:57:21 UTC (rev 1322)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/parse/SqlQueryParser.java 2009-11-17
18:46:21 UTC (rev 1323)
@@ -48,6 +48,7 @@
import org.jboss.dna.graph.property.ValueFactory;
import org.jboss.dna.graph.property.ValueFormatException;
import org.jboss.dna.graph.query.model.And;
+import org.jboss.dna.graph.query.model.Between;
import org.jboss.dna.graph.query.model.BindVariableName;
import org.jboss.dna.graph.query.model.ChildNode;
import org.jboss.dna.graph.query.model.ChildNodeJoinCondition;
@@ -416,13 +417,21 @@
String msg = GraphI18n.expectingConstraintCondition.text(name,
pos2.getLine(), pos2.getColumn());
throw new ParsingException(pos, msg);
}
- if (tokens.matches("IN", "(")) {
+ if (tokens.matches("IN", "(") ||
tokens.matches("NOT", "IN", "(")) {
+ boolean not = tokens.canConsume("NOT");
Collection<StaticOperand> staticOperands =
parseInClause(tokens, context);
constraint = new SetCriteria(left, staticOperands);
- } else if (tokens.matches("NOT", "IN",
"(")) {
- tokens.consume("NOT");
- Collection<StaticOperand> staticOperands =
parseInClause(tokens, context);
- constraint = new Not(new SetCriteria(left, staticOperands));
+ if (not) constraint = new Not(constraint);
+ } else if (tokens.matches("BETWEEN") ||
tokens.matches("NOT", "BETWEEN")) {
+ boolean not = tokens.canConsume("NOT");
+ tokens.consume("BETWEEN");
+ StaticOperand lowerBound = parseStaticOperand(tokens, context);
+ boolean lowerInclusive =
!tokens.canConsume("EXCLUSIVE");
+ tokens.consume("AND");
+ StaticOperand upperBound = parseStaticOperand(tokens, context);
+ boolean upperInclusive =
!tokens.canConsume("EXCLUSIVE");
+ constraint = new Between(left, lowerBound, upperBound,
lowerInclusive, upperInclusive);
+ if (not) constraint = new Not(constraint);
} else {
Operator operator = parseComparisonOperator(tokens);
StaticOperand right = parseStaticOperand(tokens, context);
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 2009-11-16
23:57:21 UTC (rev 1322)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/plan/PlanUtil.java 2009-11-17
18:46:21 UTC (rev 1323)
@@ -36,6 +36,7 @@
import org.jboss.dna.graph.property.ValueFactory;
import org.jboss.dna.graph.query.QueryContext;
import org.jboss.dna.graph.query.model.And;
+import org.jboss.dna.graph.query.model.Between;
import org.jboss.dna.graph.query.model.ChildNode;
import org.jboss.dna.graph.query.model.ChildNodeJoinCondition;
import org.jboss.dna.graph.query.model.Column;
@@ -433,6 +434,15 @@
if (replacement == null) return search;
return new FullTextSearch(replacement, search.getPropertyName(),
search.getFullTextSearchExpression());
}
+ if (constraint instanceof Between) {
+ Between between = (Between)constraint;
+ DynamicOperand lhs = between.getOperand();
+ StaticOperand lower = between.getLowerBound(); // Current only a literal;
therefore, no reference to selector
+ StaticOperand upper = between.getUpperBound(); // Current only a literal;
therefore, no reference to selector
+ DynamicOperand newLhs = replaceReferencesToRemovedSource(context, lhs,
rewrittenSelectors);
+ if (lhs == newLhs) return between;
+ return new Between(newLhs, lower, upper, between.isLowerBoundIncluded(),
between.isUpperBoundIncluded());
+ }
if (constraint instanceof Comparison) {
Comparison comparison = (Comparison)constraint;
DynamicOperand lhs = comparison.getOperand1();
@@ -653,6 +663,15 @@
return new FullTextSearch(sourceColumn.getSelectorName(),
sourceColumn.getPropertyName(),
search.getFullTextSearchExpression());
}
+ if (constraint instanceof Between) {
+ Between between = (Between)constraint;
+ DynamicOperand lhs = between.getOperand();
+ StaticOperand lower = between.getLowerBound(); // Current only a literal;
therefore, no reference to selector
+ StaticOperand upper = between.getUpperBound(); // Current only a literal;
therefore, no reference to selector
+ DynamicOperand newLhs = replaceViewReferences(context, lhs, mapping, node);
+ if (lhs == newLhs) return between;
+ return new Between(newLhs, lower, upper, between.isLowerBoundIncluded(),
between.isUpperBoundIncluded());
+ }
if (constraint instanceof Comparison) {
Comparison comparison = (Comparison)constraint;
DynamicOperand lhs = comparison.getOperand1();
Modified: trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/QueryBuilderTest.java
===================================================================
---
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/QueryBuilderTest.java 2009-11-16
23:57:21 UTC (rev 1322)
+++
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/QueryBuilderTest.java 2009-11-17
18:46:21 UTC (rev 1323)
@@ -280,6 +280,50 @@
}
@Test
+ public void shouldBuildQueryWithBetweenRange() {
+ query = builder.selectStar()
+ .from("table AS nodes")
+ .where()
+ .propertyValue("nodes", "col1")
+ .isBetween()
+ .literal("lower")
+ .and()
+ .literal(true)
+ .end()
+ .query();
+ assertThatSql(query, is("SELECT * FROM table AS nodes WHERE nodes.col1
BETWEEN 'lower' AND true"));
+
+ query = builder.selectStar()
+ .from("table AS nodes")
+ .where()
+ .propertyValue("nodes", "col1")
+ .isBetween()
+ .literal("lower")
+ .and()
+ .literal("upper")
+ .end()
+ .query();
+ assertThatSql(query, is("SELECT * FROM table AS nodes WHERE nodes.col1
BETWEEN 'lower' AND 'upper'"));
+ }
+
+ @Test
+ public void shouldBuildQueryWithBetweenRangeWithCast() {
+ query = builder.selectStar()
+ .from("table AS nodes")
+ .where()
+ .propertyValue("nodes", "col1")
+ .isBetween()
+ .cast("true")
+ .asBoolean()
+ .and()
+ .cast("false")
+ .asBoolean()
+ .end()
+ .query();
+ assertThatSql(query, is("SELECT * FROM table AS nodes WHERE nodes.col1
BETWEEN true AND false"));
+ }
+
+ @Test
public void shouldBuildQueryWithOneHasPropertyConstraint() {
query = builder.selectStar().from("table AS
nodes").where().hasProperty("nodes", "col1").end().query();
assertThatSql(query, is("SELECT * FROM table AS nodes WHERE nodes.col1 IS
NOT NULL"));
Modified:
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/parse/SqlQueryParserTest.java
===================================================================
---
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/parse/SqlQueryParserTest.java 2009-11-16
23:57:21 UTC (rev 1322)
+++
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/parse/SqlQueryParserTest.java 2009-11-17
18:46:21 UTC (rev 1323)
@@ -39,6 +39,7 @@
import org.jboss.dna.graph.property.Name;
import org.jboss.dna.graph.property.Path;
import org.jboss.dna.graph.query.model.And;
+import org.jboss.dna.graph.query.model.Between;
import org.jboss.dna.graph.query.model.BindVariableName;
import org.jboss.dna.graph.query.model.ChildNode;
import org.jboss.dna.graph.query.model.Constraint;
@@ -268,6 +269,108 @@
}
//
----------------------------------------------------------------------------------------------------------------
+ // parseConstraint - between
+ //
----------------------------------------------------------------------------------------------------------------
+
+ @Test
+ public void shouldParseConstraintFromStringWithValidBetweenExpressionUsing() {
+ NamedSelector selector = new NamedSelector(selectorName("tableA"));
+ Constraint constraint = parser.parseConstraint(tokens("tableA.id BETWEEN
'lower' AND 'upper'"), context, selector);
+ assertThat(constraint, is(instanceOf(Between.class)));
+ Between between = (Between)constraint;
+ assertThat(between.isLowerBoundIncluded(), is(true));
+ assertThat(between.isUpperBoundIncluded(), is(true));
+ assertThat(between.getOperand(), is(instanceOf(PropertyValue.class)));
+ PropertyValue operand = (PropertyValue)between.getOperand();
+ assertThat(operand.getSelectorName(), is(selector.getName()));
+ assertThat(operand.getPropertyName(), is(name("id")));
+ assertThat(between.getLowerBound(), is(instanceOf(Literal.class)));
+ assertThat(between.getLowerBound(), is(instanceOf(Literal.class)));
+ assertThat((Literal)between.getLowerBound(), is(literal("lower")));
+ assertThat((Literal)between.getUpperBound(), is(literal("upper")));
+ }
+
+ @Test
+ public void
shouldParseConstraintFromStringWithValidBetweenExpressionUsingExclusiveAndExclusive() {
+ NamedSelector selector = new NamedSelector(selectorName("tableA"));
+ Constraint constraint = parser.parseConstraint(tokens("tableA.id BETWEEN
'lower' EXCLUSIVE AND 'upper' EXCLUSIVE"),
+ context,
+ selector);
+ assertThat(constraint, is(instanceOf(Between.class)));
+ Between between = (Between)constraint;
+ assertThat(between.isLowerBoundIncluded(), is(false));
+ assertThat(between.isUpperBoundIncluded(), is(false));
+ assertThat(between.getOperand(), is(instanceOf(PropertyValue.class)));
+ PropertyValue operand = (PropertyValue)between.getOperand();
+ assertThat(operand.getSelectorName(), is(selector.getName()));
+ assertThat(operand.getPropertyName(), is(name("id")));
+ assertThat(between.getLowerBound(), is(instanceOf(Literal.class)));
+ assertThat(between.getLowerBound(), is(instanceOf(Literal.class)));
+ assertThat((Literal)between.getLowerBound(), is(literal("lower")));
+ assertThat((Literal)between.getUpperBound(), is(literal("upper")));
+ }
+
+ @Test
+ public void
shouldParseConstraintFromStringWithValidBetweenExpressionUsingInclusiveAndExclusive() {
+ NamedSelector selector = new NamedSelector(selectorName("tableA"));
+ Constraint constraint = parser.parseConstraint(tokens("tableA.id BETWEEN
'lower' AND 'upper' EXCLUSIVE"),
+ context,
+ selector);
+ assertThat(constraint, is(instanceOf(Between.class)));
+ Between between = (Between)constraint;
+ assertThat(between.isLowerBoundIncluded(), is(true));
+ assertThat(between.isUpperBoundIncluded(), is(false));
+ assertThat(between.getOperand(), is(instanceOf(PropertyValue.class)));
+ PropertyValue operand = (PropertyValue)between.getOperand();
+ assertThat(operand.getSelectorName(), is(selector.getName()));
+ assertThat(operand.getPropertyName(), is(name("id")));
+ assertThat(between.getLowerBound(), is(instanceOf(Literal.class)));
+ assertThat(between.getLowerBound(), is(instanceOf(Literal.class)));
+ assertThat((Literal)between.getLowerBound(), is(literal("lower")));
+ assertThat((Literal)between.getUpperBound(), is(literal("upper")));
+ }
+
+ @Test
+ public void
shouldParseConstraintFromStringWithValidBetweenExpressionUsingExclusiveAndInclusive() {
+ NamedSelector selector = new NamedSelector(selectorName("tableA"));
+ Constraint constraint = parser.parseConstraint(tokens("tableA.id BETWEEN
'lower' EXCLUSIVE AND 'upper'"),
+ context,
+ selector);
+ assertThat(constraint, is(instanceOf(Between.class)));
+ Between between = (Between)constraint;
+ assertThat(between.isLowerBoundIncluded(), is(false));
+ assertThat(between.isUpperBoundIncluded(), is(true));
+ assertThat(between.getOperand(), is(instanceOf(PropertyValue.class)));
+ PropertyValue operand = (PropertyValue)between.getOperand();
+ assertThat(operand.getSelectorName(), is(selector.getName()));
+ assertThat(operand.getPropertyName(), is(name("id")));
+ assertThat(between.getLowerBound(), is(instanceOf(Literal.class)));
+ assertThat(between.getLowerBound(), is(instanceOf(Literal.class)));
+ assertThat((Literal)between.getLowerBound(), is(literal("lower")));
+ assertThat((Literal)between.getUpperBound(), is(literal("upper")));
+ }
+
+ @Test
+ public void shouldParseConstraintFromStringWithValidNotBetweenExpression() {
+ NamedSelector selector = new NamedSelector(selectorName("tableA"));
+ Constraint constraint = parser.parseConstraint(tokens("tableA.id NOT BETWEEN
'lower' AND 'upper'"), context, selector);
+ assertThat(constraint, is(instanceOf(Not.class)));
+ constraint = ((Not)constraint).getConstraint();
+ assertThat(constraint, is(instanceOf(Between.class)));
+ Between between = (Between)constraint;
+ assertThat(between.isLowerBoundIncluded(), is(true));
+ assertThat(between.isUpperBoundIncluded(), is(true));
+ assertThat(between.getOperand(), is(instanceOf(PropertyValue.class)));
+ PropertyValue operand = (PropertyValue)between.getOperand();
+ assertThat(operand.getSelectorName(), is(selector.getName()));
+ assertThat(operand.getPropertyName(), is(name("id")));
+ assertThat(between.getLowerBound(), is(instanceOf(Literal.class)));
+ assertThat(between.getLowerBound(), is(instanceOf(Literal.class)));
+ assertThat((Literal)between.getLowerBound(), is(literal("lower")));
+ assertThat((Literal)between.getUpperBound(), is(literal("upper")));
+ }
+
+ //
----------------------------------------------------------------------------------------------------------------
// parseConstraint - parentheses
//
----------------------------------------------------------------------------------------------------------------
Modified: trunk/dna-search/src/main/java/org/jboss/dna/search/DualIndexLayout.java
===================================================================
--- trunk/dna-search/src/main/java/org/jboss/dna/search/DualIndexLayout.java 2009-11-16
23:57:21 UTC (rev 1322)
+++ trunk/dna-search/src/main/java/org/jboss/dna/search/DualIndexLayout.java 2009-11-17
18:46:21 UTC (rev 1323)
@@ -1114,6 +1114,52 @@
}
+ protected Query findNodesWithNumericRange( PropertyValue propertyValue,
+ Object lowerValue,
+ Object upperValue,
+ boolean includesLower,
+ boolean includesUpper ) {
+ String field = stringFactory.create(propertyValue.getPropertyName());
+ return findNodesWithNumericRange(field, lowerValue, upperValue,
includesLower, includesUpper);
+ }
+
+ protected Query findNodesWithNumericRange( NodeDepth depth,
+ Object lowerValue,
+ Object upperValue,
+ boolean includesLower,
+ boolean includesUpper ) {
+ return findNodesWithNumericRange(PathIndex.DEPTH, lowerValue, upperValue,
includesLower, includesUpper);
+ }
+
+ protected Query findNodesWithNumericRange( String field,
+ Object lowerValue,
+ Object upperValue,
+ boolean includesLower,
+ boolean includesUpper ) {
+ PropertyType type = PropertyType.discoverType(lowerValue);
+ assert type == PropertyType.discoverType(upperValue);
+ ValueFactories factories = context.getValueFactories();
+ switch (type) {
+ case DATE:
+ long lowerDate = factories.getLongFactory().create(lowerValue);
+ long upperDate = factories.getLongFactory().create(upperValue);
+ return NumericRangeQuery.newLongRange(field, lowerDate, upperDate,
includesLower, includesUpper);
+ case LONG:
+ long lowerLong = factories.getLongFactory().create(lowerValue);
+ long upperLong = factories.getLongFactory().create(upperValue);
+ return NumericRangeQuery.newLongRange(field, lowerLong, upperLong,
includesLower, includesUpper);
+ case DECIMAL:
+ case DOUBLE:
+ double lowerDouble =
factories.getDoubleFactory().create(lowerValue);
+ double upperDouble =
factories.getDoubleFactory().create(upperValue);
+ return NumericRangeQuery.newDoubleRange(field, lowerDouble,
upperDouble, includesLower, includesUpper);
+ default:
+ // This is not allowed ...
+ assert false;
+ return null;
+ }
+ }
+
protected Query findNodesWith( NodePath nodePath,
Operator operator,
Object value,
Modified: trunk/dna-search/src/main/java/org/jboss/dna/search/KitchenSinkIndexLayout.java
===================================================================
---
trunk/dna-search/src/main/java/org/jboss/dna/search/KitchenSinkIndexLayout.java 2009-11-16
23:57:21 UTC (rev 1322)
+++
trunk/dna-search/src/main/java/org/jboss/dna/search/KitchenSinkIndexLayout.java 2009-11-17
18:46:21 UTC (rev 1323)
@@ -52,10 +52,12 @@
import org.jboss.dna.graph.Location;
import org.jboss.dna.graph.property.Binary;
import org.jboss.dna.graph.property.Name;
+import org.jboss.dna.graph.property.PropertyType;
import org.jboss.dna.graph.property.ValueFactory;
import org.jboss.dna.graph.query.QueryContext;
import org.jboss.dna.graph.query.QueryResults.Columns;
import org.jboss.dna.graph.query.model.And;
+import org.jboss.dna.graph.query.model.Between;
import org.jboss.dna.graph.query.model.BindVariableName;
import org.jboss.dna.graph.query.model.ChildNode;
import org.jboss.dna.graph.query.model.Comparison;
@@ -381,6 +383,10 @@
PropertyExistence existence = (PropertyExistence)constraint;
return createQuery(existence.getSelectorName(),
existence.getPropertyName());
}
+ if (constraint instanceof Between) {
+ Between between = (Between)constraint;
+ return createQuery(between);
+ }
if (constraint instanceof Comparison) {
Comparison comparison = (Comparison)constraint;
return createQuery(comparison.getOperand1(), comparison.getOperator(),
comparison.getOperand2());
@@ -433,20 +439,8 @@
StaticOperand right,
boolean caseSensitive ) throws IOException {
// Handle the static operand ...
- Object value = null;
- if (right instanceof Literal) {
- Literal literal = (Literal)right;
- value = literal.getValue();
- if (!caseSensitive) value = lowerCase(value);
- } else if (right instanceof BindVariableName) {
- BindVariableName variable = (BindVariableName)right;
- String variableName = variable.getVariableName();
- value = getContext().getVariables().get(variableName);
- if (!caseSensitive) value = lowerCase(value);
- } else {
- assert false;
- return null;
- }
+ Object value = createOperand(right, caseSensitive);
+ assert value != null;
// Address the dynamic operand ...
if (left instanceof FullTextSearchScore) {
@@ -481,6 +475,71 @@
}
}
+ protected Object createOperand( StaticOperand operand,
+ boolean caseSensitive ) {
+ Object value = null;
+ if (operand instanceof Literal) {
+ Literal literal = (Literal)operand;
+ value = literal.getValue();
+ if (!caseSensitive) value = lowerCase(value);
+ } else if (operand instanceof BindVariableName) {
+ BindVariableName variable = (BindVariableName)operand;
+ String variableName = variable.getVariableName();
+ value = getContext().getVariables().get(variableName);
+ if (!caseSensitive) value = lowerCase(value);
+ } else {
+ assert false;
+ }
+ return value;
+ }
+
+ protected Query createQuery( DynamicOperand left,
+ StaticOperand lower,
+ StaticOperand upper,
+ boolean includesLower,
+ boolean includesUpper,
+ boolean caseSensitive ) throws IOException {
+ // Handle the static operands ...
+ Object lowerValue = createOperand(lower, caseSensitive);
+ Object upperValue = createOperand(upper, caseSensitive);
+ assert lowerValue != null;
+ assert upperValue != null;
+
+ // Only in the case of a PropertyValue and Depth will we need to do something
special ...
+ if (left instanceof NodeDepth) {
+ return session.findNodesWithNumericRange((NodeDepth)left, lowerValue,
upperValue, includesLower, includesUpper);
+ } else if (left instanceof PropertyValue) {
+ PropertyType lowerType = PropertyType.discoverType(lowerValue);
+ PropertyType upperType = PropertyType.discoverType(upperValue);
+ if (upperType == lowerType) {
+ switch (upperType) {
+ case DATE:
+ case LONG:
+ case DOUBLE:
+ case DECIMAL:
+ return
session.findNodesWithNumericRange((PropertyValue)left,
+ lowerValue,
+ upperValue,
+ includesLower,
+ includesUpper);
+ default:
+ // continue on and handle as boolean query ...
+ }
+ }
+ }
+
+ // Otherwise, just create a boolean query ...
+ BooleanQuery query = new BooleanQuery();
+ Operator lowerOp = includesLower ? Operator.GREATER_THAN_OR_EQUAL_TO :
Operator.GREATER_THAN;
+ Operator upperOp = includesUpper ? Operator.LESS_THAN_OR_EQUAL_TO :
Operator.LESS_THAN;
+ Query lowerQuery = createQuery(left, lowerOp, lower, caseSensitive);
+ Query upperQuery = createQuery(left, upperOp, upper, caseSensitive);
+ if (lowerQuery == null || upperQuery == null) return null;
+ query.add(lowerQuery, Occur.MUST);
+ query.add(upperQuery, Occur.MUST);
+ return query;
+ }
+
protected Object lowerCase( Object value ) {
if (value instanceof String) {
return ((String)value).toLowerCase();