Repository: phoenix
Updated Branches:
  refs/heads/master 0e42f3a1f -> 29c610cfd


PHOENIX-761 Implement getKeyPart for round/ceil/floor of a DECIMAL value (Kyle 
Buzsaki)


Project: http://git-wip-us.apache.org/repos/asf/phoenix/repo
Commit: http://git-wip-us.apache.org/repos/asf/phoenix/commit/29c610cf
Tree: http://git-wip-us.apache.org/repos/asf/phoenix/tree/29c610cf
Diff: http://git-wip-us.apache.org/repos/asf/phoenix/diff/29c610cf

Branch: refs/heads/master
Commit: 29c610cfdec8414d0b35f2e66b7090f540c48eb7
Parents: 0e42f3a
Author: James Taylor <jtay...@salesforce.com>
Authored: Tue Aug 26 22:43:57 2014 -0700
Committer: James Taylor <jtay...@salesforce.com>
Committed: Tue Aug 26 22:43:57 2014 -0700

----------------------------------------------------------------------
 .../function/CeilDecimalExpression.java         |  89 +--
 .../function/FloorDecimalExpression.java        |  40 +-
 .../function/RoundDecimalExpression.java        | 250 +++++++-
 .../RoundFloorCeilExpressionsTest.java          | 582 +++++++++++++++----
 4 files changed, 800 insertions(+), 161 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/phoenix/blob/29c610cf/phoenix-core/src/main/java/org/apache/phoenix/expression/function/CeilDecimalExpression.java
----------------------------------------------------------------------
diff --git 
a/phoenix-core/src/main/java/org/apache/phoenix/expression/function/CeilDecimalExpression.java
 
b/phoenix-core/src/main/java/org/apache/phoenix/expression/function/CeilDecimalExpression.java
index 5e3f2f6..d6b4e45 100644
--- 
a/phoenix-core/src/main/java/org/apache/phoenix/expression/function/CeilDecimalExpression.java
+++ 
b/phoenix-core/src/main/java/org/apache/phoenix/expression/function/CeilDecimalExpression.java
@@ -26,63 +26,80 @@ import org.apache.phoenix.expression.LiteralExpression;
 import org.apache.phoenix.schema.PDataType;
 
 import com.google.common.collect.Lists;
+import java.math.BigDecimal;
+import org.apache.phoenix.query.KeyRange;
+import static org.apache.phoenix.schema.PDataType.DECIMAL;
 
 /**
- * 
+ *
  * Class encapsulating the CEIL operation on a {@link 
org.apache.phoenix.schema.PDataType#DECIMAL}
  *
- * 
+ *
  * @since 3.0.0
  */
 public class CeilDecimalExpression extends RoundDecimalExpression {
-    
+
     public CeilDecimalExpression() {}
-    
+
     private CeilDecimalExpression(List<Expression> children) {
         super(children);
     }
-    
-  /**
-   * Creates a {@link CeilDecimalExpression} with rounding scale given by 
@param scale.
-   *
-   */
-   public static Expression create(Expression expr, int scale) throws 
SQLException {
+
+    /**
+     * Creates a {@link CeilDecimalExpression} with rounding scale given by 
@param scale.
+     *
+     */
+    public static Expression create(Expression expr, int scale) throws 
SQLException {
        if (expr.getDataType().isCoercibleTo(PDataType.LONG)) {
-           return expr;
-       }
-       Expression scaleExpr = LiteralExpression.newConstant(scale, 
PDataType.INTEGER, true);
-       List<Expression> expressions = Lists.newArrayList(expr, scaleExpr);
-       return new CeilDecimalExpression(expressions);
-   }
-   
-   public static Expression create(List<Expression> exprs) throws SQLException 
{
-       Expression expr = exprs.get(0);
+            return expr;
+        }
+        Expression scaleExpr = LiteralExpression.newConstant(scale, 
PDataType.INTEGER, true);
+        List<Expression> expressions = Lists.newArrayList(expr, scaleExpr);
+        return new CeilDecimalExpression(expressions);
+    }
+
+    public static Expression create(List<Expression> exprs) throws 
SQLException {
+        Expression expr = exprs.get(0);
        if (expr.getDataType().isCoercibleTo(PDataType.LONG)) {
-           return expr;
-       }
+            return expr;
+        }
        if (exprs.size() == 1) {
-           Expression scaleExpr = LiteralExpression.newConstant(0, 
PDataType.INTEGER, true);
-           exprs = Lists.newArrayList(expr, scaleExpr);
-       }
-       return new CeilDecimalExpression(exprs);
-   }
-   
-   /**
-    * Creates a {@link CeilDecimalExpression} with a default scale of 0 used 
for rounding. 
-    *
-    */
-   public static Expression create(Expression expr) throws SQLException {
-       return create(expr, 0);
-   }
-    
+            Expression scaleExpr = LiteralExpression.newConstant(0, 
PDataType.INTEGER, true);
+            exprs = Lists.newArrayList(expr, scaleExpr);
+        }
+        return new CeilDecimalExpression(exprs);
+    }
+
+    /**
+     * Creates a {@link CeilDecimalExpression} with a default scale of 0 used 
for rounding.
+     *
+     */
+    public static Expression create(Expression expr) throws SQLException {
+        return create(expr, 0);
+    }
+
     @Override
     protected RoundingMode getRoundingMode() {
         return RoundingMode.CEILING;
     }
-    
+
     @Override
     public String getName() {
         return CeilFunction.NAME;
     }
     
+    /**
+     * {@inheritDoc }
+     */
+    @Override
+    protected KeyRange getInputRangeProducing(BigDecimal result) {
+        if(!hasEnoughPrecisionToProduce(result)) {
+            throw new IllegalArgumentException("Cannot produce input range for 
decimal " + result 
+                + ", not enough precision with scale " + getRoundingScale());
+        }
+        byte[] lowerRange = DECIMAL.toBytes(stepPrevInScale(result));
+        byte[] upperRange = DECIMAL.toBytes(result);
+        return KeyRange.getKeyRange(lowerRange, false, upperRange, true);
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/phoenix/blob/29c610cf/phoenix-core/src/main/java/org/apache/phoenix/expression/function/FloorDecimalExpression.java
----------------------------------------------------------------------
diff --git 
a/phoenix-core/src/main/java/org/apache/phoenix/expression/function/FloorDecimalExpression.java
 
b/phoenix-core/src/main/java/org/apache/phoenix/expression/function/FloorDecimalExpression.java
index 539dbfa..b5566ca 100644
--- 
a/phoenix-core/src/main/java/org/apache/phoenix/expression/function/FloorDecimalExpression.java
+++ 
b/phoenix-core/src/main/java/org/apache/phoenix/expression/function/FloorDecimalExpression.java
@@ -26,25 +26,28 @@ import org.apache.phoenix.expression.LiteralExpression;
 import org.apache.phoenix.schema.PDataType;
 
 import com.google.common.collect.Lists;
+import java.math.BigDecimal;
+import org.apache.phoenix.query.KeyRange;
+import static org.apache.phoenix.schema.PDataType.DECIMAL;
 
 /**
- * 
+ *
  * Class encapsulating the FLOOR operation on 
  * a column/literal of type {@link 
org.apache.phoenix.schema.PDataType#DECIMAL}.
  *
- * 
+ *
  * @since 3.0.0
  */
 public class FloorDecimalExpression extends RoundDecimalExpression {
-    
+
     public FloorDecimalExpression() {}
-    
+
     private FloorDecimalExpression(List<Expression> children) {
         super(children);
     }
-    
+
     /**
-     * Creates a {@link FloorDecimalExpression} with rounding scale given by 
@param scale. 
+     * Creates a {@link FloorDecimalExpression} with rounding scale given by 
@param scale.
      *
      */
     public static Expression create(Expression expr, int scale) throws 
SQLException {
@@ -55,7 +58,7 @@ public class FloorDecimalExpression extends 
RoundDecimalExpression {
         List<Expression> expressions = Lists.newArrayList(expr, scaleExpr);
         return new FloorDecimalExpression(expressions);
     }
-    
+
     public static Expression create(List<Expression> exprs) throws 
SQLException {
         Expression expr = exprs.get(0);
         if (expr.getDataType().isCoercibleTo(PDataType.LONG)) {
@@ -67,22 +70,37 @@ public class FloorDecimalExpression extends 
RoundDecimalExpression {
         }
         return new FloorDecimalExpression(exprs);
     }
-    
+
     /**
-     * Creates a {@link FloorDecimalExpression} with a default scale of 0 used 
for rounding. 
+     * Creates a {@link FloorDecimalExpression} with a default scale of 0 used 
for rounding.
      *
      */
     public static Expression create(Expression expr) throws SQLException {
         return create(expr, 0);
     }
-    
+
     @Override
     protected RoundingMode getRoundingMode() {
         return RoundingMode.FLOOR;
     }
-    
+
     @Override
     public String getName() {
         return FloorFunction.NAME;
     }
+
+    /**
+     * {@inheritDoc }
+     */
+    @Override
+    protected KeyRange getInputRangeProducing(BigDecimal result) {
+        if(!hasEnoughPrecisionToProduce(result)) {
+            throw new IllegalArgumentException("Cannot produce input range for 
decimal " + result 
+                + ", not enough precision with scale " + getRoundingScale());
+        }
+        byte[] lowerRange = DECIMAL.toBytes(result);
+        byte[] upperRange = DECIMAL.toBytes(stepNextInScale(result));
+        return KeyRange.getKeyRange(lowerRange, upperRange);
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/phoenix/blob/29c610cf/phoenix-core/src/main/java/org/apache/phoenix/expression/function/RoundDecimalExpression.java
----------------------------------------------------------------------
diff --git 
a/phoenix-core/src/main/java/org/apache/phoenix/expression/function/RoundDecimalExpression.java
 
b/phoenix-core/src/main/java/org/apache/phoenix/expression/function/RoundDecimalExpression.java
index 64f957e..92756a5 100644
--- 
a/phoenix-core/src/main/java/org/apache/phoenix/expression/function/RoundDecimalExpression.java
+++ 
b/phoenix-core/src/main/java/org/apache/phoenix/expression/function/RoundDecimalExpression.java
@@ -34,22 +34,28 @@ import org.apache.phoenix.schema.PDataType;
 import org.apache.phoenix.schema.tuple.Tuple;
 
 import com.google.common.collect.Lists;
+import java.util.Collections;
+import org.apache.hadoop.hbase.filter.CompareFilter;
+import org.apache.phoenix.compile.KeyPart;
+import static 
org.apache.phoenix.expression.function.ScalarFunction.evaluateExpression;
+import org.apache.phoenix.query.KeyRange;
+import org.apache.phoenix.schema.PColumn;
+import static org.apache.phoenix.schema.PDataType.DECIMAL;
 
 /**
- * 
- * Class encapsulating the process for rounding off a column/literal of 
- * type {@link org.apache.phoenix.schema.PDataType#DECIMAL}
  *
- * 
+ * Class encapsulating the process for rounding off a column/literal of type
+ * {@link org.apache.phoenix.schema.PDataType#DECIMAL}
+ *
+ *
  * @since 3.0.0
  */
-
 public class RoundDecimalExpression extends ScalarFunction {
-    
+
     private int scale;
-    
+
     /**
-     * Creates a {@link RoundDecimalExpression} with rounding scale given by 
@param scale. 
+     * Creates a {@link RoundDecimalExpression} with rounding scale given by 
@param scale.
      *
      */
     public static Expression create(Expression expr, int scale) throws 
SQLException {
@@ -60,15 +66,15 @@ public class RoundDecimalExpression extends ScalarFunction {
         List<Expression> expressions = Lists.newArrayList(expr, scaleExpr);
         return new RoundDecimalExpression(expressions);
     }
-    
+
     /**
-     * Creates a {@link RoundDecimalExpression} with a default scale of 0 used 
for rounding. 
+     * Creates a {@link RoundDecimalExpression} with a default scale of 0 used 
for rounding.
      *
      */
     public static Expression create(Expression expr) throws SQLException {
         return create(expr, 0);
     }
-    
+
     public static Expression create(List<Expression> exprs) throws 
SQLException {
         Expression expr = exprs.get(0);
         if (expr.getDataType().isCoercibleTo(PDataType.LONG)) {
@@ -80,9 +86,9 @@ public class RoundDecimalExpression extends ScalarFunction {
         }
         return new RoundDecimalExpression(exprs);
     }
-    
+
     public RoundDecimalExpression() {}
-    
+
     protected RoundDecimalExpression(List<Expression> children) {
         super(children);
         LiteralExpression scaleChild = (LiteralExpression)children.get(1);
@@ -97,7 +103,7 @@ public class RoundDecimalExpression extends ScalarFunction {
                 }
             }
             throw new IllegalDataException("Invalid second argument for scale: 
" + scaleValue + ". The scale must be between 0 and " + PDataType.MAX_PRECISION 
+ " inclusive.");
-        } 
+        }
     }
 
     @Override
@@ -116,10 +122,14 @@ public class RoundDecimalExpression extends 
ScalarFunction {
     public PDataType getDataType() {
         return PDataType.DECIMAL;
     }
-    
+
     protected RoundingMode getRoundingMode() {
         return RoundingMode.HALF_UP;
     }
+
+    protected final int getRoundingScale() {
+        return scale;
+    }
     
     @Override
     public void readFields(DataInput input) throws IOException {
@@ -137,10 +147,218 @@ public class RoundDecimalExpression extends 
ScalarFunction {
     public String getName() {
         return RoundFunction.NAME;
     }
-    
+
     @Override
     public OrderPreserving preservesOrder() {
         return OrderPreserving.YES;
     }
 
+    @Override
+    public int getKeyFormationTraversalIndex() {
+        return 0;
+    }
+
+    @Override
+    public KeyPart newKeyPart(final KeyPart childPart) {
+        return new KeyPart() {
+            private final List<Expression> extractNodes = 
Collections.<Expression>singletonList(RoundDecimalExpression.this);
+
+            @Override
+            public PColumn getColumn() {
+                return childPart.getColumn();
+            }
+
+            @Override
+            public List<Expression> getExtractNodes() {
+                return extractNodes;
+            }
+
+            @Override
+            public KeyRange getKeyRange(CompareFilter.CompareOp op, Expression 
rhs) {
+                final BigDecimal rhsDecimal = (BigDecimal) 
DECIMAL.toObject(evaluateExpression(rhs));
+                
+                // equality requires an exact match. if rounding would cut off 
more precision
+                // than needed for a match, it's impossible for there to be 
any matches
+                if(op == CompareFilter.CompareOp.EQUAL && 
!hasEnoughPrecisionToProduce(rhsDecimal)) {
+                    return KeyRange.EMPTY_RANGE;
+                }
+                
+                // if the decimal needs to be rounded, round it such that the 
given 
+                // operator will still be valid
+                BigDecimal roundedDecimal = 
roundAndPreserveOperator(rhsDecimal, op);
+                
+                // the range of big decimals that could be rounded to produce 
the rounded result
+                // alternatively, the "rounding bucket" that this decimal 
falls into
+                final KeyRange equalityRange = 
getInputRangeProducing(roundedDecimal);
+                boolean lowerInclusive = equalityRange.isLowerInclusive();
+                boolean upperInclusive = equalityRange.isUpperInclusive();
+                byte[] lowerRange = KeyRange.UNBOUND;
+                byte[] upperRange = KeyRange.UNBOUND;
+                
+                switch(op) {
+                    case EQUAL:
+                        return equalityRange;
+                    case GREATER:
+                        // from the equality range and up, NOT including the 
equality range
+                        lowerRange = equalityRange.getUpperRange();
+                        lowerInclusive = !equalityRange.isUpperInclusive();
+                        break;
+                    case GREATER_OR_EQUAL:
+                        // from the equality range and up, including the 
equality range
+                        lowerRange = equalityRange.getLowerRange();
+                        break;
+                    case LESS:
+                        // from the equality range and down, NOT including the 
equality range
+                        upperRange = equalityRange.getLowerRange();
+                        upperInclusive = !equalityRange.isLowerInclusive();
+                        break;
+                    case LESS_OR_EQUAL:
+                        // from the equality range and down, including the 
equality range
+                        upperRange = equalityRange.getUpperRange();
+                        break;
+                    default:
+                        throw new AssertionError("Invalid CompareOp: " + op);
+                }
+
+                return KeyRange.getKeyRange(lowerRange, lowerInclusive, 
upperRange, upperInclusive);
+            }
+            
+            /**
+             * Produces a the given decimal rounded to this rounding 
expression's scale. If the 
+             * decimal requires more scale precision to produce than this 
expression has, as in
+             * ROUND(?, 2) &gt; 2.0098974, it ensures that the decimal is 
rounded such that the
+             * given operator will still produce correct results.
+             * @param decimal  the decimal to round with this expression's 
scale
+             * @param op  the operator to preserve comparison with in the 
event of lost precision
+             * @return  the rounded decimal
+             */
+            private BigDecimal roundAndPreserveOperator(BigDecimal decimal, 
CompareFilter.CompareOp op) {
+                final BigDecimal rounded = roundToScale(decimal);
+                
+                // if we lost information, make sure that the rounding didn't 
break the operator
+                if(!hasEnoughPrecisionToProduce(decimal)) {
+                    switch(op) {
+                        case GREATER_OR_EQUAL:
+                            // e.g. 'ROUND(dec, 2) >= 2.013' would be 
converted to 
+                            // 'ROUND(dec, 2) >= 2.01' but should be 
'ROUND(dec, 2) >= 2.02'
+                            if(decimal.compareTo(rounded) > 0) {
+                                return stepNextInScale(rounded);
+                            }
+                            break;
+                        case GREATER:
+                            // e.g. 'ROUND(dec, 2) > 2.017' would be converted 
to 
+                            // 'ROUND(dec, 2) > 2.02' but should be 
'ROUND(dec, 2) > 2.01'
+                            if(decimal.compareTo(rounded) < 0) {
+                                return stepPrevInScale(rounded);
+                            }
+                            break;
+                        case LESS_OR_EQUAL:
+                            // e.g. 'ROUND(dec, 2) < 2.017' would be converted 
to 
+                            // 'ROUND(dec, 2) < 2.02' but should be 
'ROUND(dec, 2) < 2.01'
+                            if(decimal.compareTo(rounded) < 0) {
+                                return stepPrevInScale(rounded);
+                            }
+                            break;
+                        case LESS:
+                            // e.g. 'ROUND(dec, 2) <= 2.013' would be 
converted to 
+                            // 'ROUND(dec, 2) <= 2.01' but should be 
'ROUND(dec, 2) <= 2.02'
+                            if(decimal.compareTo(rounded) > 0) {
+                                return stepNextInScale(rounded);
+                            }
+                            break;
+                    }
+                }
+                
+                // otherwise, rounding has not affected the operator, so 
return normally
+                return rounded;
+            }
+        };
+    }
+    
+    /**
+     * Finds the Decimal KeyRange that will produce the given result when fed 
into this 
+     * rounding expression. For example, a ROUND expression with scale 2 will 
produce the
+     * result "2.05" with any decimal in the range [2.045, 2.0545). 
+     * The result must be pre-rounded to within this rounding expression's 
scale. 
+     * @param result  the result to find an input range for. Must be 
producable.
+     * @return  a KeyRange of DECIMAL keys that can be rounded by this 
expression to produce result
+     * @throws IllegalArgumentException  if the result has more scale than 
this expression can produce
+     */
+    protected KeyRange getInputRangeProducing(BigDecimal result) {
+        if(!hasEnoughPrecisionToProduce(result)) {
+            throw new IllegalArgumentException("Cannot produce input range for 
decimal " + result 
+                + ", not enough precision with scale " + getRoundingScale());
+        }
+        byte[] lowerRange = DECIMAL.toBytes(halfStepPrevInScale(result));
+        byte[] upperRange = DECIMAL.toBytes(halfStepNextInScale(result));
+        // inclusiveness changes depending on sign
+        // e.g. -0.5 rounds "up" to -1 even though it is the lower boundary
+        boolean lowerInclusive = result.signum() > 0;
+        boolean upperInclusive = result.signum() < 0;
+        return KeyRange.getKeyRange(lowerRange, lowerInclusive, upperRange, 
upperInclusive);
+    }
+    
+    /**
+     * Determines whether this rounding expression's scale has enough 
precision to produce the
+     * minimum precision for the input decimal. In other words, determines 
whether the given
+     * decimal can be rounded to this scale without losing ordering 
information.
+     * For example, an expression with a scale of 2 has enough precision to 
produce "2.3", "2.71"
+     * and "2.100000", but does not have enough precision to produce "2.001"
+     * @param result  the decimal to round
+     * @return  true if the given decimal can be precisely matched by this 
rounding expression
+     */
+    protected final boolean hasEnoughPrecisionToProduce(BigDecimal result) {
+        // use compareTo so that 2.0 and 2.00 are treated as "equal"
+        return roundToScale(result).compareTo(result) == 0;
+    }
+    
+    /**
+     * Returns the given decimal rounded to this rounding expression's scale.
+     * For example, with scale 2 the decimal "2.453" would be rounded to 
either 2.45 or 
+     * 2.46 depending on the rounding mode, while "2.38" and "2.7" would be 
unchanged.  
+     * @param decimal  the decimal to round
+     * @return  the rounded result decimal
+     */
+    protected final BigDecimal roundToScale(BigDecimal decimal) {
+        return decimal.setScale(getRoundingScale(), getRoundingMode());
+    }
+    
+    /**
+     * Produces a value half of a "step" back in this expression's rounding 
scale.
+     * For example with a scale of 2, "2.5" would be stepped back to "2.495".
+     */
+    protected final BigDecimal halfStepPrevInScale(BigDecimal decimal) {
+        BigDecimal step = 
BigDecimal.ONE.scaleByPowerOfTen(-getRoundingScale());
+        BigDecimal halfStep = step.divide(BigDecimal.valueOf(2));
+        return decimal.subtract(halfStep);
+    }
+    
+    /**
+     * Produces a value half of a "step" forward in this expression's rounding 
scale.
+     * For example with a scale of 2, "2.5" would be stepped forward to 
"2.505".
+     */
+    protected final BigDecimal halfStepNextInScale(BigDecimal decimal) {
+        BigDecimal step = 
BigDecimal.ONE.scaleByPowerOfTen(-getRoundingScale());
+        BigDecimal halfStep = step.divide(BigDecimal.valueOf(2));
+        return decimal.add(halfStep);
+    }
+
+    /**
+     * Produces a value one "step" back in this expression's rounding scale.
+     * For example with a scale of 2, "2.5" would be stepped back to "2.49".
+     */
+    protected final BigDecimal stepPrevInScale(BigDecimal decimal) {
+        BigDecimal step = 
BigDecimal.ONE.scaleByPowerOfTen(-getRoundingScale());
+        return decimal.subtract(step);
+    }
+
+    /**
+     * Produces a value one "step" forward in this expression's rounding scale.
+     * For example with a scale of 2, "2.5" would be stepped forward to "2.51".
+     */
+    protected final BigDecimal stepNextInScale(BigDecimal decimal) {
+        BigDecimal step = 
BigDecimal.ONE.scaleByPowerOfTen(-getRoundingScale());
+        return decimal.add(step);
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/phoenix/blob/29c610cf/phoenix-core/src/test/java/org/apache/phoenix/expression/RoundFloorCeilExpressionsTest.java
----------------------------------------------------------------------
diff --git 
a/phoenix-core/src/test/java/org/apache/phoenix/expression/RoundFloorCeilExpressionsTest.java
 
b/phoenix-core/src/test/java/org/apache/phoenix/expression/RoundFloorCeilExpressionsTest.java
index 55bcd7f..25520c4 100644
--- 
a/phoenix-core/src/test/java/org/apache/phoenix/expression/RoundFloorCeilExpressionsTest.java
+++ 
b/phoenix-core/src/test/java/org/apache/phoenix/expression/RoundFloorCeilExpressionsTest.java
@@ -17,26 +17,36 @@
  */
 package org.apache.phoenix.expression;
 
+import java.math.BigDecimal;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
-import java.math.BigDecimal;
 import java.sql.Date;
+import java.sql.SQLException;
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
+import org.apache.hadoop.hbase.filter.CompareFilter.CompareOp;
 
 import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
+import org.apache.phoenix.compile.KeyPart;
 import org.apache.phoenix.expression.function.CeilDateExpression;
 import org.apache.phoenix.expression.function.CeilDecimalExpression;
 import org.apache.phoenix.expression.function.FloorDateExpression;
 import org.apache.phoenix.expression.function.FloorDecimalExpression;
 import org.apache.phoenix.expression.function.RoundDateExpression;
 import org.apache.phoenix.expression.function.RoundDecimalExpression;
+import org.apache.phoenix.expression.function.ScalarFunction;
 import org.apache.phoenix.expression.function.TimeUnit;
+import org.apache.phoenix.hbase.index.util.ImmutableBytesPtr;
+import org.apache.phoenix.query.KeyRange;
 import org.apache.phoenix.schema.IllegalDataException;
 import org.apache.phoenix.schema.PDataType;
+import static org.apache.phoenix.schema.PDataType.DECIMAL;
 import org.apache.phoenix.util.DateUtil;
+import static org.junit.Assert.assertFalse;
 import org.junit.Test;
 
 /**
@@ -49,148 +59,520 @@ import org.junit.Test;
  */
 public class RoundFloorCeilExpressionsTest {
 
+    // Decimal Expression Tests
+    
     @Test
     public void testRoundDecimalExpression() throws Exception {
-        LiteralExpression bd = LiteralExpression.newConstant(1.23898, 
PDataType.DECIMAL);
-        Expression rde = RoundDecimalExpression.create(bd, 3);
+        LiteralExpression decimalLiteral = 
LiteralExpression.newConstant(1.23898, PDataType.DECIMAL);
+        Expression roundDecimalExpression = 
RoundDecimalExpression.create(decimalLiteral, 3);
+        
         ImmutableBytesWritable ptr = new ImmutableBytesWritable();
-        rde.evaluate(null, ptr);
-        Object obj = rde.getDataType().toObject(ptr);
-        assertTrue(obj instanceof BigDecimal);
-        BigDecimal value = (BigDecimal)obj;
-        assertEquals(BigDecimal.valueOf(1.239), value);
+        roundDecimalExpression.evaluate(null, ptr);
+        Object result = roundDecimalExpression.getDataType().toObject(ptr);
+        
+        assertTrue(result instanceof BigDecimal);
+        BigDecimal resultDecimal = (BigDecimal)result;
+        assertEquals(BigDecimal.valueOf(1.239), resultDecimal);
     }
     
     @Test
-    public void testCeilDecimalExpression() throws Exception {
-        LiteralExpression bd = LiteralExpression.newConstant(1.23898, 
PDataType.DECIMAL);
-        Expression rde = CeilDecimalExpression.create(bd, 3);
-        ImmutableBytesWritable ptr = new ImmutableBytesWritable();
-        rde.evaluate(null, ptr);
-        Object obj = rde.getDataType().toObject(ptr);
-        assertTrue(obj instanceof BigDecimal);
-        BigDecimal value = (BigDecimal)obj;
-        assertEquals(BigDecimal.valueOf(1.239), value);
+    public void testRoundDecimalExpressionNoop() throws Exception {
+        LiteralExpression decimalLiteral = LiteralExpression.newConstant(5, 
PDataType.INTEGER);
+        Expression roundDecimalExpression = 
RoundDecimalExpression.create(decimalLiteral, 3);
+        
+        assertEquals(roundDecimalExpression, decimalLiteral);
     }
     
     @Test
-    public void testCeilDecimalExpressionNoop() throws Exception {
-        LiteralExpression bd = LiteralExpression.newConstant(5, 
PDataType.INTEGER);
-        Expression rde = CeilDecimalExpression.create(bd, 3);
-        assertEquals(rde, bd);
+    public void testFloorDecimalExpression() throws Exception {
+        LiteralExpression decimalLiteral = 
LiteralExpression.newConstant(1.23898, PDataType.DECIMAL);
+        Expression floorDecimalExpression = 
FloorDecimalExpression.create(decimalLiteral, 3);
+        
+        ImmutableBytesWritable ptr = new ImmutableBytesWritable();
+        floorDecimalExpression.evaluate(null, ptr);
+        Object result = floorDecimalExpression.getDataType().toObject(ptr);
+        
+        assertTrue(result instanceof BigDecimal);
+        BigDecimal resultDecimal = (BigDecimal)result;
+        assertEquals(BigDecimal.valueOf(1.238), resultDecimal);
     }
     
     @Test
     public void testFloorDecimalExpressionNoop() throws Exception {
-        LiteralExpression bd = LiteralExpression.newConstant(5, 
PDataType.INTEGER);
-        Expression rde = FloorDecimalExpression.create(bd, 3);
-        assertEquals(rde, bd);
+        LiteralExpression decimalLiteral = LiteralExpression.newConstant(5, 
PDataType.INTEGER);
+        Expression floorDecimalExpression = 
FloorDecimalExpression.create(decimalLiteral, 3);
+        
+        assertEquals(floorDecimalExpression, decimalLiteral);
     }
     
     @Test
-    public void testRoundDecimalExpressionNoop() throws Exception {
-        LiteralExpression bd = LiteralExpression.newConstant(5, 
PDataType.INTEGER);
-        Expression rde = RoundDecimalExpression.create(bd, 3);
-        assertEquals(rde, bd);
+    public void testCeilDecimalExpression() throws Exception {
+        LiteralExpression decimalLiteral = 
LiteralExpression.newConstant(1.23898, PDataType.DECIMAL);
+        Expression ceilDecimalExpression = 
CeilDecimalExpression.create(decimalLiteral, 3);
+        
+        ImmutableBytesWritable ptr = new ImmutableBytesWritable();
+        ceilDecimalExpression.evaluate(null, ptr);
+        Object result = ceilDecimalExpression.getDataType().toObject(ptr);
+        
+        assertTrue(result instanceof BigDecimal);
+        BigDecimal resultDecimal = (BigDecimal)result;
+        assertEquals(BigDecimal.valueOf(1.239), resultDecimal);
     }
     
     @Test
-    public void testFloorDecimalExpression() throws Exception {
-        LiteralExpression bd = LiteralExpression.newConstant(1.23898, 
PDataType.DECIMAL);
-        Expression rde = FloorDecimalExpression.create(bd, 3);
-        ImmutableBytesWritable ptr = new ImmutableBytesWritable();
-        rde.evaluate(null, ptr);
-        Object obj = rde.getDataType().toObject(ptr);
-        assertTrue(obj instanceof BigDecimal);
-        BigDecimal value = (BigDecimal)obj;
-        assertEquals(BigDecimal.valueOf(1.238), value);
+    public void testCeilDecimalExpressionNoop() throws Exception {
+        LiteralExpression decimalLiteral = LiteralExpression.newConstant(5, 
PDataType.INTEGER);
+        Expression ceilDecimalExpression = 
CeilDecimalExpression.create(decimalLiteral, 3);
+        
+        assertEquals(ceilDecimalExpression, decimalLiteral);
     }
     
     @Test
     public void testRoundDecimalExpressionScaleParamValidation() throws 
Exception {
-        LiteralExpression bd = LiteralExpression.newConstant(1.23898, 
PDataType.DECIMAL);
+        LiteralExpression decimalLiteral = 
LiteralExpression.newConstant(1.23898, PDataType.DECIMAL);
         LiteralExpression scale = LiteralExpression.newConstant("3", 
PDataType.VARCHAR);
-        List<Expression> exprs = new ArrayList<Expression>(2);
-        exprs.add(bd);
-        exprs.add(scale);
+        
+        List<Expression> childExpressions = new ArrayList<Expression>(2);
+        childExpressions.add(decimalLiteral);
+        childExpressions.add(scale);
+        
         try {
-            RoundDecimalExpression.create(exprs);
+            RoundDecimalExpression.create(childExpressions);
             fail("Evaluation should have failed because only an INTEGER is 
allowed for second param in a RoundDecimalExpression");
         } catch(IllegalDataException e) {
 
         }
     }
     
+    // KeyRange explicit simple / sanity tests
+    
+    @Test
+    public void testRoundDecimalExpressionKeyRangeSimple() throws Exception {
+        ScalarFunction roundDecimalExpression = 
(ScalarFunction)RoundDecimalExpression.create(DUMMY_DECIMAL, 3);
+        
+        byte[] upperBound = DECIMAL.toBytes(new BigDecimal("1.2385"));
+        byte[] lowerBound = DECIMAL.toBytes(new BigDecimal("1.2375"));
+        KeyRange expectedKeyRange = KeyRange.getKeyRange(lowerBound, 
upperBound);
+        
+        KeyPart keyPart = roundDecimalExpression.newKeyPart(null);
+        assertEquals(expectedKeyRange, keyPart.getKeyRange(CompareOp.EQUAL, 
LiteralExpression.newConstant(new BigDecimal("1.238"), DECIMAL)));
+    }
+    
+    @Test
+    public void testFloorDecimalExpressionKeyRangeSimple() throws Exception {
+        ScalarFunction floorDecimalExpression = 
(ScalarFunction)FloorDecimalExpression.create(DUMMY_DECIMAL, 3);
+        
+        byte[] upperBound = DECIMAL.toBytes(new BigDecimal("1.239"));
+        byte[] lowerBound = DECIMAL.toBytes(new BigDecimal("1.238"));
+        KeyRange expectedKeyRange = KeyRange.getKeyRange(lowerBound, true, 
upperBound, false);
+        
+        KeyPart keyPart = floorDecimalExpression.newKeyPart(null);
+        assertEquals(expectedKeyRange, keyPart.getKeyRange(CompareOp.EQUAL, 
LiteralExpression.newConstant(new BigDecimal("1.238"), DECIMAL)));
+    }
+    
+    @Test
+    public void testCeilDecimalExpressionKeyRangeSimple() throws Exception {
+        ScalarFunction ceilDecimalExpression = 
(ScalarFunction)CeilDecimalExpression.create(DUMMY_DECIMAL, 3);
+        
+        byte[] upperBound = DECIMAL.toBytes(new BigDecimal("1.238"));
+        byte[] lowerBound = DECIMAL.toBytes(new BigDecimal("1.237"));
+        KeyRange expectedKeyRange = KeyRange.getKeyRange(lowerBound, false, 
upperBound, true);
+        
+        KeyPart keyPart = ceilDecimalExpression.newKeyPart(null);
+        assertEquals(expectedKeyRange, keyPart.getKeyRange(CompareOp.EQUAL, 
LiteralExpression.newConstant(new BigDecimal("1.238"), DECIMAL)));
+    }
+    
+    // KeyRange complex / generated tests
+    
+    @Test
+    public void testRoundDecimalExpressionKeyRangeCoverage() throws Exception {
+        for(int scale : SCALES) {
+            ScalarFunction roundDecimalExpression = (ScalarFunction) 
RoundDecimalExpression.create(DUMMY_DECIMAL, scale);
+            KeyPart keyPart = roundDecimalExpression.newKeyPart(null);
+            verifyKeyPart(RoundingType.ROUND, scale, keyPart);
+        }
+    }
+    
+    @Test
+    public void testFloorDecimalExpressionKeyRangeCoverage() throws Exception {
+        for(int scale : SCALES) {
+            ScalarFunction floorDecimalExpression = (ScalarFunction) 
FloorDecimalExpression.create(DUMMY_DECIMAL, scale);
+            KeyPart keyPart = floorDecimalExpression.newKeyPart(null);
+            verifyKeyPart(RoundingType.FLOOR, scale, keyPart);
+        }
+    }
+    
+    @Test
+    public void testCeilDecimalExpressionKeyRangeCoverage() throws Exception {
+        for(int scale : SCALES) {
+            ScalarFunction ceilDecimalExpression = (ScalarFunction) 
CeilDecimalExpression.create(DUMMY_DECIMAL, scale);
+            KeyPart keyPart = ceilDecimalExpression.newKeyPart(null);
+            verifyKeyPart(RoundingType.CEIL, scale, keyPart);
+        }
+    }
+    
+    /**
+     * Represents the three different types of rounding expression and produces
+     * expressions of their type when given a Decimal key and scale.
+     */
+    private static enum RoundingType {
+        ROUND("ROUND"), 
+        FLOOR("FLOOR"), 
+        CEIL("CEIL");
+        
+        public final String name;
+        
+        RoundingType(String name) {
+            this.name = name;
+        }
+        
+        /**
+         * Returns a rounding expression of this type that will round the 
given decimal key at the
+         * given scale. 
+         * @param key  the byte key for the Decimal to round
+         * @param scale  the scale to round the decimal to
+         * @return  the expression containing the above parameters
+         */
+        public Expression getExpression(byte[] key, int scale) {
+            try {
+                LiteralExpression decimalLiteral = 
LiteralExpression.newConstant(DECIMAL.toObject(key), DECIMAL);
+                switch(this) {
+                    case ROUND:
+                        return RoundDecimalExpression.create(decimalLiteral, 
scale);
+                    case FLOOR:
+                        return FloorDecimalExpression.create(decimalLiteral, 
scale);
+                    case CEIL:
+                        return CeilDecimalExpression.create(decimalLiteral, 
scale);
+                    default:
+                        throw new AssertionError("Unknown RoundingType");
+                }
+            }
+            catch (SQLException ex) {
+                throw new AssertionError("Should never happen when creating 
decimal literal", ex);
+            }
+        }
+    }
+    
+    /**
+     * Represents a possible relational operator used in rounding expression 
where clauses.
+     * Includes information not kept by CompareFilter.CompareOp, including a 
string symbol
+     * representation and a method for actually comparing comparables.
+     */
+    private static enum Relation {
+        EQUAL(CompareOp.EQUAL, "="),
+        GREATER(CompareOp.GREATER, ">"),
+        GREATER_OR_EQUAL(CompareOp.GREATER_OR_EQUAL, ">="),
+        LESS(CompareOp.LESS, "<"),
+        LESS_OR_EQUAL(CompareOp.LESS_OR_EQUAL, "<=");
+        
+        public final CompareOp compareOp;
+        public final String symbol;
+        
+        Relation(CompareOp compareOp, String symbol) {
+            this.compareOp = compareOp;
+            this.symbol = symbol;
+        }
+        
+        public <E extends Comparable<? super E>> boolean compare(E lhs, E rhs) 
{
+            int comparison = lhs.compareTo(rhs);
+            switch(this) {
+                case EQUAL:
+                    return comparison == 0;
+                case GREATER_OR_EQUAL:
+                    return comparison >= 0;
+                case GREATER:
+                    return comparison > 0;
+                case LESS_OR_EQUAL:
+                    return comparison <= 0;
+                case LESS:
+                    return comparison < 0;
+                default:
+                    throw new AssertionError("Unknown RelationType");
+            }
+        }
+    }
+    
+    /**
+     * Produces a string error message containing the given information, 
formatted like a where
+     * clause. <br>
+     * Example Output: <br>
+     * 'where ROUND(?, 2) &lt;= 2.55' (produced range: [2.545, 2.555) )
+     * @param exprType
+     * @param scale
+     * @param relation
+     * @param rhs
+     * @param range
+     * @return 
+     */
+    private static String getMessage(RoundingType exprType, int scale, 
Relation relation, BigDecimal rhs, KeyRange range) {
+        String where = exprType.name + "(?, " + scale + ") " + relation.symbol 
+ " " + rhs;
+        return "'where " + where + "' (produced range: " + 
formatDecimalKeyRange(range) + " )";
+    }
+    
+    /**
+     * Interpreting the KeyRange as a range of decimal, produces a nicely 
formatted string
+     * representation. 
+     * @param range  the KeyRange to format
+     * @return  the string representation, e.g. [2.45, 2.55)
+     */
+    private static String formatDecimalKeyRange(KeyRange range) {
+        return (range.isLowerInclusive() ? "[" : "(") 
+            + (range.lowerUnbound() ? "*" : 
DECIMAL.toObject(range.getLowerRange())) 
+            + ", " 
+            + (range.upperUnbound() ? "*" : 
DECIMAL.toObject(range.getUpperRange())) 
+            + (range.isUpperInclusive() ? "]" : ")");
+    }
+    
+    // create methods need a dummy expression that is not coercible to to a 
long
+    // value doesn't matter because we only use those expressions to produce a 
keypart
+    private static final LiteralExpression DUMMY_DECIMAL = 
LiteralExpression.newConstant(new BigDecimal("2.5"));
+    
+    // this should be PDataType#MAX_PRECISION (38)
+    // but there are rounding errors in DECIMAL.toBytes() and 
DECIMAL.toObject()
+    // with precisions of 20 or greater. See 
https://issues.apache.org/jira/browse/PHOENIX-1206
+    private static final int MAX_RELIABLE_PRECISION = 18;
+    
+    // once PHOENIX-1206 is fixed, we should add more precise decimals to 
these tests
+    private static final List<BigDecimal> DECIMALS = 
Collections.unmodifiableList(
+        Arrays.asList(
+            new BigDecimal("-200300"), new BigDecimal("-8.44"), new 
BigDecimal("-2.00"), 
+            new BigDecimal("-0.6"), new BigDecimal("-0.00032"), 
+            BigDecimal.ZERO, BigDecimal.ONE, 
+            new BigDecimal("0.00000984"), new BigDecimal("0.74"), new 
BigDecimal("2.00"), 
+            new BigDecimal("7.09"), new BigDecimal("84900800")
+        ));
+    
+    private static final List<Integer> SCALES = 
Collections.unmodifiableList(Arrays.asList(0, 1, 2, 3, 8));
+    
+    /**
+     * Checks that a given KeyPart produces the right key ranges for each 
relational operator and
+     * a variety of right-hand-side decimals.
+     * @param exprType  the rounding expression type used to create this 
KeyPart
+     * @param scale  the scale used to create this KeyPart
+     * @param keyPart  the KeyPart to test
+     */
+    private void verifyKeyPart(RoundingType exprType, int scale, KeyPart 
keyPart) throws SQLException {
+        for(BigDecimal rhsDecimal : DECIMALS) {
+            LiteralExpression rhsExpression = 
LiteralExpression.newConstant(rhsDecimal, DECIMAL);
+            for(Relation relation : Relation.values()) {
+                KeyRange keyRange = keyPart.getKeyRange(relation.compareOp, 
rhsExpression);
+                verifyKeyRange(exprType, scale, relation, rhsDecimal, 
keyRange);
+            }
+        }
+    }
+    
+    /**
+     * Checks that a given KeyRange's boundaries match with the given rounding 
expression type,
+     * rounding scale, relational operator, and right hand side decimal.
+     * Does so by checking the decimal values immediately on either side of 
the KeyRange border and
+     * verifying that they either match or do not match the "where clause" 
formed by the 
+     * rounding type, scale, relation, and rhs decimal. If a relation should 
produce an unbounded
+     * upper or lower range, verifies that that end of the range is unbounded. 
Finally, if the
+     * range is empty, verifies that the rhs decimal required more precision 
than could be
+     * produced by the rounding expression.
+     * @param exprType  the rounding expression type used to create this 
KeyRange
+     * @param scale  the rounding scale used to create this KeyRange
+     * @param relation  the relational operator used to create this KeyRange
+     * @param rhs  the right hand side decimal used to create this KeyRange
+     * @param range  the KeyRange to test
+     */
+    private void verifyKeyRange(RoundingType exprType, int scale, Relation 
relation, BigDecimal rhs, KeyRange range) throws SQLException {
+        // dump of values for debugging
+        final String dump = getMessage(exprType, scale, relation, rhs, range);
+        
+        ImmutableBytesPtr rhsPtr = new ImmutableBytesPtr();
+        LiteralExpression.newConstant(rhs, DECIMAL).evaluate(null, rhsPtr);
+        
+        ImmutableBytesPtr lhsPtr = new ImmutableBytesPtr();
+        
+        // we should only get an empty range if we can verify that precision 
makes a match impossible
+        if(range == KeyRange.EMPTY_RANGE) {
+            assertTrue("should only get empty key range for unmatchable rhs 
precision (" + dump + ")", rhs.scale() > scale);
+            assertEquals("should only get empty key range for equals checks (" 
+ dump + ")", Relation.EQUAL, relation);
+            return;
+        }
+        
+        // if it should have an upper bound
+        if(relation != Relation.GREATER && relation != 
Relation.GREATER_OR_EQUAL) {
+            // figure out what the upper bound is
+            byte[] highestHighIncluded;
+            byte[] lowestHighExcluded;
+            if(range.isUpperInclusive()) {
+                highestHighIncluded = range.getUpperRange();
+                lowestHighExcluded = nextDecimalKey(range.getUpperRange());
+            }
+            else {
+                highestHighIncluded = prevDecimalKey(range.getUpperRange());
+                lowestHighExcluded = range.getUpperRange();
+            }
+            
+            // check on either side of the boundary to validate that it is in 
fact the boundary
+            exprType.getExpression(highestHighIncluded, scale).evaluate(null, 
lhsPtr);
+            assertTrue("incorrectly excluding " + 
DECIMAL.toObject(highestHighIncluded) 
+                + " in upper bound for " + dump, relation.compare(lhsPtr, 
rhsPtr));
+            exprType.getExpression(lowestHighExcluded, scale).evaluate(null, 
lhsPtr);
+            assertFalse("incorrectly including " + 
DECIMAL.toObject(lowestHighExcluded) 
+                + " in upper bound for " + dump, relation.compare(lhsPtr, 
rhsPtr));
+        }
+        else {
+            // otherwise verify that it does not have an upper bound
+            assertTrue("should not have a upper bound for " + dump, 
range.upperUnbound());
+        }
+        
+        // if it should have a lower bound
+        if(relation != Relation.LESS && relation != Relation.LESS_OR_EQUAL) {
+            // figure out what the lower bound is
+            byte[] lowestLowIncluded;
+            byte[] highestLowExcluded;
+            if(range.isLowerInclusive()) {
+                lowestLowIncluded = range.getLowerRange();
+                highestLowExcluded = prevDecimalKey(range.getLowerRange());
+            }
+            else {
+                lowestLowIncluded = nextDecimalKey(range.getLowerRange());
+                highestLowExcluded = range.getLowerRange();
+            }
+            
+            // check on either side of the boundary to validate that it is in 
fact the boundary
+            exprType.getExpression(lowestLowIncluded, scale).evaluate(null, 
lhsPtr);
+            assertTrue("incorrectly excluding " + 
DECIMAL.toObject(lowestLowIncluded) 
+                + " in lower bound for " + dump, relation.compare(lhsPtr, 
rhsPtr));
+            exprType.getExpression(highestLowExcluded, scale).evaluate(null, 
lhsPtr);
+            assertFalse("incorrectly including " + 
DECIMAL.toObject(highestLowExcluded) 
+                + " in lower bound for " + dump, relation.compare(lhsPtr, 
rhsPtr));
+        }
+        else {
+            // otherwise verify that it does not have a lower bound
+            assertTrue("should not have a lower bound for " + dump, 
range.lowerUnbound());
+        }
+    }
+    
+    /**
+     * Produces the previous Decimal key relative to the given key. The new 
key will differ from
+     * the old key in as small a unit as possible while still maintaining 
accurate serialization.
+     * @param key  bytes for the old Decimal key
+     * @return  bytes for the new Decimal key, a single unit previous to the 
old one
+     */
+    private static byte[] prevDecimalKey(byte[] key) {
+        BigDecimal decimal = (BigDecimal) DECIMAL.toObject(key);
+        BigDecimal prev = decimal.subtract(getSmallestUnit(decimal));
+        return DECIMAL.toBytes(prev);
+    }
+    
+    /**
+     * Produces the next Decimal key relative to the given key. The new key 
will differ from the
+     * old key in as small a unit as possible while still maintaining accurate 
serialization.
+     * @param key  bytes for the old Decimal key
+     * @return  bytes for the new Decimal key, a single unit next from the old 
one
+     */
+    private static byte[] nextDecimalKey(byte[] key) {
+        BigDecimal decimal = (BigDecimal) DECIMAL.toObject(key);
+        BigDecimal next = decimal.add(getSmallestUnit(decimal));
+        return DECIMAL.toBytes(next);
+    }
+    
+    /**
+     * Produces the smallest unit of difference possible for the given decimal 
that will still
+     * be serialized accurately. For example, if the 
MAXIMUM_RELIABLE_PRECISION were 4, then 
+     * getSmallestUnit(2.3) would produce 0.001, as 2.301 could be serialized 
accurately but
+     * 2.3001 could not.
+     * @param decimal  the decimal to find the smallest unit in relation to
+     * @return  the smallest BigDecimal unit possible to add to decimal while 
still maintaining
+     *          accurate serialization
+     * @throws IllegalArgumentException if decimal requires more than the 
maximum reliable precision
+     */
+    private static BigDecimal getSmallestUnit(BigDecimal decimal) {
+        if (decimal.precision() > MAX_RELIABLE_PRECISION) {
+            throw new IllegalArgumentException("rounding errors mean that we 
cannot reliably test " + decimal);
+        }
+        int minScale = decimal.scale() + (MAX_RELIABLE_PRECISION - 
decimal.precision());
+        return BigDecimal.valueOf(1, minScale);
+    }
+    
+    // Date Expression Tests
+    
     @Test
     public void testRoundDateExpression() throws Exception {
-        LiteralExpression date = 
LiteralExpression.newConstant(DateUtil.parseDate("2012-01-01 14:25:28"), 
PDataType.DATE);
-        Expression rde = RoundDateExpression.create(date, TimeUnit.DAY);
+        LiteralExpression dateLiteral = 
LiteralExpression.newConstant(DateUtil.parseDate("2012-01-01 14:25:28"), 
PDataType.DATE);
+        Expression roundDateExpression = 
RoundDateExpression.create(dateLiteral, TimeUnit.DAY);
+        
         ImmutableBytesWritable ptr = new ImmutableBytesWritable();
-        rde.evaluate(null, ptr);
-        Object obj = rde.getDataType().toObject(ptr);
-        assertTrue(obj instanceof Date);
-        Date value = (Date)obj;
-        assertEquals(DateUtil.parseDate("2012-01-02 00:00:00"), value);
+        roundDateExpression.evaluate(null, ptr);
+        Object result = roundDateExpression.getDataType().toObject(ptr);
+        
+        assertTrue(result instanceof Date);
+        Date resultDate = (Date)result;
+        assertEquals(DateUtil.parseDate("2012-01-02 00:00:00"), resultDate);
     }
     
     @Test
     public void testRoundDateExpressionWithMultiplier() throws Exception {
-        Expression date = 
LiteralExpression.newConstant(DateUtil.parseDate("2012-01-01 14:25:28"), 
PDataType.DATE);
-        Expression rde = RoundDateExpression.create(date, TimeUnit.MINUTE, 10);
+        Expression dateLiteral = 
LiteralExpression.newConstant(DateUtil.parseDate("2012-01-01 14:25:28"), 
PDataType.DATE);
+        Expression roundDateExpression = 
RoundDateExpression.create(dateLiteral, TimeUnit.MINUTE, 10);
+        
         ImmutableBytesWritable ptr = new ImmutableBytesWritable();
-        rde.evaluate(null, ptr);
-        Object obj = rde.getDataType().toObject(ptr);
-        assertTrue(obj instanceof Date);
-        Date value = (Date)obj;
-        assertEquals(DateUtil.parseDate("2012-01-01 14:30:00"), value);
+        roundDateExpression.evaluate(null, ptr);
+        Object result = roundDateExpression.getDataType().toObject(ptr);
+        
+        assertTrue(result instanceof Date);
+        Date resultDate = (Date)result;
+        assertEquals(DateUtil.parseDate("2012-01-01 14:30:00"), resultDate);
     }
     
     @Test
-    public void testCeilDateExpression() throws Exception {
-        LiteralExpression date = 
LiteralExpression.newConstant(DateUtil.parseDate("2012-01-01 14:25:28"), 
PDataType.DATE);
-        Expression rde = CeilDateExpression.create(date, TimeUnit.DAY);
+    public void testFloorDateExpression() throws Exception {
+        LiteralExpression dateLiteral = 
LiteralExpression.newConstant(DateUtil.parseDate("2012-01-01 14:25:28"), 
PDataType.DATE);
+        Expression floorDateExpression = 
FloorDateExpression.create(dateLiteral, TimeUnit.DAY);
+        
         ImmutableBytesWritable ptr = new ImmutableBytesWritable();
-        rde.evaluate(null, ptr);
-        Object obj = rde.getDataType().toObject(ptr);
-        assertTrue(obj instanceof Date);
-        Date value = (Date)obj;
-        assertEquals(DateUtil.parseDate("2012-01-02 00:00:00"), value);
+        floorDateExpression.evaluate(null, ptr);
+        Object result = floorDateExpression.getDataType().toObject(ptr);
+        
+        assertTrue(result instanceof Date);
+        Date resultDate = (Date)result;
+        assertEquals(DateUtil.parseDate("2012-01-01 00:00:00"), resultDate);
     }
     
     @Test
-    public void testCeilDateExpressionWithMultiplier() throws Exception {
-        Expression date = 
LiteralExpression.newConstant(DateUtil.parseDate("2012-01-01 14:25:28"), 
PDataType.DATE);
-        Expression rde = CeilDateExpression.create(date, TimeUnit.SECOND, 10);
+    public void testFloorDateExpressionWithMultiplier() throws Exception {
+        Expression dateLiteral = 
LiteralExpression.newConstant(DateUtil.parseDate("2012-01-01 14:25:28"), 
PDataType.DATE);
+        Expression floorDateExpression = 
FloorDateExpression.create(dateLiteral, TimeUnit.SECOND, 10);
+        
         ImmutableBytesWritable ptr = new ImmutableBytesWritable();
-        rde.evaluate(null, ptr);
-        Object obj = rde.getDataType().toObject(ptr);
-        assertTrue(obj instanceof Date);
-        Date value = (Date)obj;
-        assertEquals(DateUtil.parseDate("2012-01-01 14:25:30"), value);
+        floorDateExpression.evaluate(null, ptr);
+        Object result = floorDateExpression.getDataType().toObject(ptr);
+        
+        assertTrue(result instanceof Date);
+        Date resultDate = (Date)result;
+        assertEquals(DateUtil.parseDate("2012-01-01 14:25:20"), resultDate);
     }
     
     @Test
-    public void testFloorDateExpression() throws Exception {
-        LiteralExpression date = 
LiteralExpression.newConstant(DateUtil.parseDate("2012-01-01 14:25:28"), 
PDataType.DATE);
-        Expression rde = FloorDateExpression.create(date, TimeUnit.DAY);
+    public void testCeilDateExpression() throws Exception {
+        LiteralExpression dateLiteral = 
LiteralExpression.newConstant(DateUtil.parseDate("2012-01-01 14:25:28"), 
PDataType.DATE);
+        Expression ceilDateExpression = CeilDateExpression.create(dateLiteral, 
TimeUnit.DAY);
+        
         ImmutableBytesWritable ptr = new ImmutableBytesWritable();
-        rde.evaluate(null, ptr);
-        Object obj = rde.getDataType().toObject(ptr);
-        assertTrue(obj instanceof Date);
-        Date value = (Date)obj;
-        assertEquals(DateUtil.parseDate("2012-01-01 00:00:00"), value);
+        ceilDateExpression.evaluate(null, ptr);
+        Object result = ceilDateExpression.getDataType().toObject(ptr);
+        
+        assertTrue(result instanceof Date);
+        Date resultDate = (Date)result;
+        assertEquals(DateUtil.parseDate("2012-01-02 00:00:00"), resultDate);
     }
     
     @Test
-    public void testFloorDateExpressionWithMultiplier() throws Exception {
-        Expression date = 
LiteralExpression.newConstant(DateUtil.parseDate("2012-01-01 14:25:28"), 
PDataType.DATE);
-        Expression rde = FloorDateExpression.create(date, TimeUnit.SECOND, 10);
+    public void testCeilDateExpressionWithMultiplier() throws Exception {
+        Expression dateLiteral = 
LiteralExpression.newConstant(DateUtil.parseDate("2012-01-01 14:25:28"), 
PDataType.DATE);
+        Expression ceilDateExpression = CeilDateExpression.create(dateLiteral, 
TimeUnit.SECOND, 10);
+        
         ImmutableBytesWritable ptr = new ImmutableBytesWritable();
-        rde.evaluate(null, ptr);
-        Object obj = rde.getDataType().toObject(ptr);
-        assertTrue(obj instanceof Date);
-        Date value = (Date)obj;
-        assertEquals(DateUtil.parseDate("2012-01-01 14:25:20"), value);
+        ceilDateExpression.evaluate(null, ptr);
+        Object result = ceilDateExpression.getDataType().toObject(ptr);
+        
+        assertTrue(result instanceof Date);
+        Date resultDate = (Date)result;
+        assertEquals(DateUtil.parseDate("2012-01-01 14:25:30"), resultDate);
     }
     
     /**
@@ -198,11 +580,13 @@ public class RoundFloorCeilExpressionsTest {
      */
     @Test
     public void testRoundDateExpressionValidation_1() throws Exception {
-        LiteralExpression date = 
LiteralExpression.newConstant(DateUtil.parseDate("2012-01-01 14:25:28"), 
PDataType.DATE);
-        List<Expression> exprs = new ArrayList<Expression>(1);
-        exprs.add(date);
+        LiteralExpression dateLiteral = 
LiteralExpression.newConstant(DateUtil.parseDate("2012-01-01 14:25:28"), 
PDataType.DATE);
+        
+        List<Expression> childExpressions = new ArrayList<Expression>(1);
+        childExpressions.add(dateLiteral);
+        
         try {
-            RoundDateExpression.create(exprs);
+            RoundDateExpression.create(childExpressions);
             fail("Instantiating a RoundDateExpression with only one argument 
should have failed.");
         } catch(IllegalArgumentException e) {
 
@@ -214,13 +598,15 @@ public class RoundFloorCeilExpressionsTest {
      */
     @Test
     public void testRoundDateExpressionValidation_2() throws Exception {
-        LiteralExpression date = 
LiteralExpression.newConstant(DateUtil.parseDate("2012-01-01 14:25:28"), 
PDataType.DATE);
-        LiteralExpression timeUnit = LiteralExpression.newConstant("millis", 
PDataType.VARCHAR);
-        List<Expression> exprs = new ArrayList<Expression>(1);
-        exprs.add(date);
-        exprs.add(timeUnit);
+        LiteralExpression dateLiteral = 
LiteralExpression.newConstant(DateUtil.parseDate("2012-01-01 14:25:28"), 
PDataType.DATE);
+        LiteralExpression timeUnitLiteral = 
LiteralExpression.newConstant("millis", PDataType.VARCHAR);
+        
+        List<Expression> childExpressions = new ArrayList<Expression>(1);
+        childExpressions.add(dateLiteral);
+        childExpressions.add(timeUnitLiteral);
+        
         try {
-            RoundDateExpression.create(exprs);
+            RoundDateExpression.create(childExpressions);
             fail("Only a valid time unit represented by TimeUnit enum is 
allowed and millis is invalid.");
         } catch(IllegalArgumentException e) {
 

Reply via email to