[dna-commits] DNA SVN: r1323 - in trunk: dna-graph/src/main/java/org/jboss/dna/graph/query/model and 5 other directories.

dna-commits at lists.jboss.org dna-commits at lists.jboss.org
Tue Nov 17 13:46:21 EST 2009


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.
+ */
+ at 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();



More information about the dna-commits mailing list