You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@phoenix.apache.org by ja...@apache.org on 2016/02/17 20:56:51 UTC

[3/3] phoenix git commit: PHOENIX-2631 Exception when parsing boundary timestamp values

PHOENIX-2631 Exception when parsing boundary timestamp values


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

Branch: refs/heads/4.x-HBase-1.0
Commit: f1b79d1d18bdfdfa08ba9522a612bee3760460a2
Parents: be4915c
Author: James Taylor <jt...@salesforce.com>
Authored: Tue Feb 16 13:01:45 2016 -0800
Committer: James Taylor <jt...@salesforce.com>
Committed: Wed Feb 17 11:55:44 2016 -0800

----------------------------------------------------------------------
 .../end2end/ClientTimeArithmeticQueryIT.java    |  11 +-
 .../org/apache/phoenix/end2end/DateTimeIT.java  |   5 +-
 .../apache/phoenix/end2end/DistinctCountIT.java |   2 +-
 .../apache/phoenix/end2end/PercentileIT.java    |   2 +-
 .../phoenix/end2end/ProductMetricsIT.java       |   6 +-
 .../phoenix/end2end/RowValueConstructorIT.java  |   4 +-
 .../phoenix/end2end/VariableLengthPKIT.java     |   2 +-
 .../end2end/index/IndexExpressionIT.java        |   7 +-
 .../apache/phoenix/end2end/index/IndexIT.java   |   5 +-
 .../apache/phoenix/schema/types/PDataType.java  |   3 +
 .../apache/phoenix/schema/types/PTimestamp.java | 407 +++++++++++--------
 .../phoenix/compile/WhereOptimizerTest.java     |   5 +-
 .../java/org/apache/phoenix/query/BaseTest.java |   7 +-
 .../org/apache/phoenix/query/QueryPlanTest.java |  24 +-
 .../phoenix/schema/types/PDataTypeTest.java     |  60 +++
 .../java/org/apache/phoenix/util/TestUtil.java  |   7 +-
 16 files changed, 344 insertions(+), 213 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/phoenix/blob/f1b79d1d/phoenix-core/src/it/java/org/apache/phoenix/end2end/ClientTimeArithmeticQueryIT.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/ClientTimeArithmeticQueryIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/ClientTimeArithmeticQueryIT.java
index e617673..8347370 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/ClientTimeArithmeticQueryIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/ClientTimeArithmeticQueryIT.java
@@ -19,9 +19,9 @@
  */
 package org.apache.phoenix.end2end;
 
+import static org.apache.phoenix.query.QueryConstants.MILLIS_IN_DAY;
 import static org.apache.phoenix.util.TestUtil.B_VALUE;
 import static org.apache.phoenix.util.TestUtil.E_VALUE;
-import static org.apache.phoenix.util.TestUtil.MILLIS_IN_DAY;
 import static org.apache.phoenix.util.TestUtil.ROW1;
 import static org.apache.phoenix.util.TestUtil.ROW2;
 import static org.apache.phoenix.util.TestUtil.ROW3;
@@ -51,7 +51,6 @@ import java.util.Properties;
 
 import org.apache.phoenix.util.PhoenixRuntime;
 import org.apache.phoenix.util.PropertiesUtil;
-import org.apache.phoenix.util.TestUtil;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -614,10 +613,10 @@ public class ClientTimeArithmeticQueryIT extends BaseQueryIT {
         statement.setDate(3, date);
         statement.execute();
         statement.setString(2, ROW4);
-        statement.setDate(3, new Date(date.getTime() + TestUtil.MILLIS_IN_DAY - 1));
+        statement.setDate(3, new Date(date.getTime() + MILLIS_IN_DAY - 1));
         statement.execute();
         statement.setString(2, ROW6);
-        statement.setDate(3, new Date(date.getTime() + TestUtil.MILLIS_IN_DAY - 1));
+        statement.setDate(3, new Date(date.getTime() + MILLIS_IN_DAY - 1));
         statement.execute();
         statement.setString(2, ROW9);
         statement.setDate(3, date);
@@ -738,7 +737,7 @@ public class ClientTimeArithmeticQueryIT extends BaseQueryIT {
       conn = DriverManager.getConnection(getUrl(), props);
       rs = conn.createStatement().executeQuery("SELECT ts + 1 FROM time_table");
       assertTrue(rs.next());
-      assertEquals(time.getTime() + TestUtil.MILLIS_IN_DAY,rs.getTimestamp(1).getTime());
+      assertEquals(time.getTime() + MILLIS_IN_DAY,rs.getTimestamp(1).getTime());
     }
 
     @Test
@@ -772,7 +771,7 @@ public class ClientTimeArithmeticQueryIT extends BaseQueryIT {
       conn = DriverManager.getConnection(getUrl(), props);
       rs = conn.createStatement().executeQuery("SELECT ts - 1 FROM time_table");
       assertTrue(rs.next());
-      assertEquals(time.getTime() - TestUtil.MILLIS_IN_DAY,rs.getTimestamp(1).getTime());
+      assertEquals(time.getTime() - MILLIS_IN_DAY,rs.getTimestamp(1).getTime());
     }
  
     @Test

http://git-wip-us.apache.org/repos/asf/phoenix/blob/f1b79d1d/phoenix-core/src/it/java/org/apache/phoenix/end2end/DateTimeIT.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/DateTimeIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/DateTimeIT.java
index 6c53f1d..e87d8d4 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/DateTimeIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/DateTimeIT.java
@@ -17,12 +17,12 @@
  */
 package org.apache.phoenix.end2end;
 
+import static org.apache.phoenix.query.QueryConstants.MILLIS_IN_DAY;
 import static org.apache.phoenix.util.TestUtil.ATABLE_NAME;
 import static org.apache.phoenix.util.TestUtil.A_VALUE;
 import static org.apache.phoenix.util.TestUtil.B_VALUE;
 import static org.apache.phoenix.util.TestUtil.C_VALUE;
 import static org.apache.phoenix.util.TestUtil.E_VALUE;
-import static org.apache.phoenix.util.TestUtil.MILLIS_IN_DAY;
 import static org.apache.phoenix.util.TestUtil.ROW1;
 import static org.apache.phoenix.util.TestUtil.ROW2;
 import static org.apache.phoenix.util.TestUtil.ROW3;
@@ -52,7 +52,6 @@ import java.util.Calendar;
 import java.util.GregorianCalendar;
 
 import org.apache.phoenix.util.DateUtil;
-import org.apache.phoenix.util.TestUtil;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -672,6 +671,6 @@ public class DateTimeIT extends BaseHBaseManagedTimeIT {
         ResultSet rs = conn.createStatement().executeQuery("SELECT CURRENT_DATE()");
         assertTrue(rs.next());
         long actualTime = rs.getDate(1).getTime();
-        assertTrue(Math.abs(actualTime - expectedTime) < TestUtil.MILLIS_IN_DAY);
+        assertTrue(Math.abs(actualTime - expectedTime) < MILLIS_IN_DAY);
     }
 }

http://git-wip-us.apache.org/repos/asf/phoenix/blob/f1b79d1d/phoenix-core/src/it/java/org/apache/phoenix/end2end/DistinctCountIT.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/DistinctCountIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/DistinctCountIT.java
index b1daacc..523e83c 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/DistinctCountIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/DistinctCountIT.java
@@ -17,11 +17,11 @@
  */
 package org.apache.phoenix.end2end;
 
+import static org.apache.phoenix.query.QueryConstants.MILLIS_IN_DAY;
 import static org.apache.phoenix.util.TestUtil.ATABLE_NAME;
 import static org.apache.phoenix.util.TestUtil.A_VALUE;
 import static org.apache.phoenix.util.TestUtil.B_VALUE;
 import static org.apache.phoenix.util.TestUtil.C_VALUE;
-import static org.apache.phoenix.util.TestUtil.MILLIS_IN_DAY;
 import static org.apache.phoenix.util.TestUtil.ROW1;
 import static org.apache.phoenix.util.TestUtil.ROW2;
 import static org.apache.phoenix.util.TestUtil.ROW3;

http://git-wip-us.apache.org/repos/asf/phoenix/blob/f1b79d1d/phoenix-core/src/it/java/org/apache/phoenix/end2end/PercentileIT.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/PercentileIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/PercentileIT.java
index 8109694..fe8e5c3 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/PercentileIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/PercentileIT.java
@@ -17,13 +17,13 @@
  */
 package org.apache.phoenix.end2end;
 
+import static org.apache.phoenix.query.QueryConstants.MILLIS_IN_DAY;
 import static org.apache.phoenix.util.TestUtil.ATABLE_NAME;
 import static org.apache.phoenix.util.TestUtil.A_VALUE;
 import static org.apache.phoenix.util.TestUtil.B_VALUE;
 import static org.apache.phoenix.util.TestUtil.C_VALUE;
 import static org.apache.phoenix.util.TestUtil.INDEX_DATA_SCHEMA;
 import static org.apache.phoenix.util.TestUtil.INDEX_DATA_TABLE;
-import static org.apache.phoenix.util.TestUtil.MILLIS_IN_DAY;
 import static org.apache.phoenix.util.TestUtil.ROW1;
 import static org.apache.phoenix.util.TestUtil.ROW2;
 import static org.apache.phoenix.util.TestUtil.ROW3;

http://git-wip-us.apache.org/repos/asf/phoenix/blob/f1b79d1d/phoenix-core/src/it/java/org/apache/phoenix/end2end/ProductMetricsIT.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/ProductMetricsIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/ProductMetricsIT.java
index ddc5fab..a68ba51 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/ProductMetricsIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/ProductMetricsIT.java
@@ -17,6 +17,7 @@
  */
 package org.apache.phoenix.end2end;
 
+import static org.apache.phoenix.query.QueryConstants.MILLIS_IN_DAY;
 import static org.apache.phoenix.util.TestUtil.TEST_PROPERTIES;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -47,7 +48,6 @@ import org.apache.phoenix.util.DateUtil;
 import org.apache.phoenix.util.PhoenixRuntime;
 import org.apache.phoenix.util.PropertiesUtil;
 import org.apache.phoenix.util.SchemaUtil;
-import org.apache.phoenix.util.TestUtil;
 import org.junit.Test;
 
 import com.google.common.collect.Lists;
@@ -1983,8 +1983,8 @@ public class ProductMetricsIT extends BaseClientManagedTimeIT {
         url = getUrl() + ";" + PhoenixRuntime.CURRENT_SCN_ATTRIB + "=" + (ts); // Run query at timestamp 5
         props = PropertiesUtil.deepCopy(TEST_PROPERTIES);
         conn = DriverManager.getConnection(url, props);
-        initDateTableValues(conn, tenantId, new Date(startDate.getTime()+TestUtil.MILLIS_IN_DAY*10), 2.0);
-        initDateTableValues(conn, tenantId, new Date(startDate.getTime()+TestUtil.MILLIS_IN_DAY*20), 2.0);
+        initDateTableValues(conn, tenantId, new Date(startDate.getTime()+MILLIS_IN_DAY*10), 2.0);
+        initDateTableValues(conn, tenantId, new Date(startDate.getTime()+MILLIS_IN_DAY*20), 2.0);
         conn.commit();
         conn.close();
 

http://git-wip-us.apache.org/repos/asf/phoenix/blob/f1b79d1d/phoenix-core/src/it/java/org/apache/phoenix/end2end/RowValueConstructorIT.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/RowValueConstructorIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/RowValueConstructorIT.java
index fef105c..ea91f4f 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/RowValueConstructorIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/RowValueConstructorIT.java
@@ -17,6 +17,7 @@
  */
 package org.apache.phoenix.end2end;
 
+import static org.apache.phoenix.query.QueryConstants.MILLIS_IN_DAY;
 import static org.apache.phoenix.util.TestUtil.ENTITYHISTID1;
 import static org.apache.phoenix.util.TestUtil.ENTITYHISTID3;
 import static org.apache.phoenix.util.TestUtil.ENTITYHISTID7;
@@ -54,7 +55,6 @@ import org.apache.phoenix.util.DateUtil;
 import org.apache.phoenix.util.PhoenixRuntime;
 import org.apache.phoenix.util.PropertiesUtil;
 import org.apache.phoenix.util.QueryUtil;
-import org.apache.phoenix.util.TestUtil;
 import org.junit.Test;
 
 
@@ -847,7 +847,7 @@ public class RowValueConstructorIT extends BaseClientManagedTimeIT {
         try {
             PreparedStatement statement = conn.prepareStatement(query);
             statement.setString(1, tenantId);
-            Timestamp timestampWithNanos = DateUtil.getTimestamp(dateUpserted.getTime() + 2 * TestUtil.MILLIS_IN_DAY, 300);
+            Timestamp timestampWithNanos = DateUtil.getTimestamp(dateUpserted.getTime() + 2 * MILLIS_IN_DAY, 300);
             timestampWithNanos.setNanos(0);
             statement.setTimestamp(2, timestampWithNanos);
             statement.setTimestamp(3, timestampWithNanos);

http://git-wip-us.apache.org/repos/asf/phoenix/blob/f1b79d1d/phoenix-core/src/it/java/org/apache/phoenix/end2end/VariableLengthPKIT.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/VariableLengthPKIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/VariableLengthPKIT.java
index 1e48f8c..c0b557c 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/VariableLengthPKIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/VariableLengthPKIT.java
@@ -17,8 +17,8 @@
  */
 package org.apache.phoenix.end2end;
 
+import static org.apache.phoenix.query.QueryConstants.MILLIS_IN_DAY;
 import static org.apache.phoenix.util.TestUtil.BTABLE_NAME;
-import static org.apache.phoenix.util.TestUtil.MILLIS_IN_DAY;
 import static org.apache.phoenix.util.TestUtil.PTSDB2_NAME;
 import static org.apache.phoenix.util.TestUtil.PTSDB_NAME;
 import static org.apache.phoenix.util.TestUtil.TEST_PROPERTIES;

http://git-wip-us.apache.org/repos/asf/phoenix/blob/f1b79d1d/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/IndexExpressionIT.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/IndexExpressionIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/IndexExpressionIT.java
index 7be8d41..ed06a71 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/IndexExpressionIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/IndexExpressionIT.java
@@ -9,10 +9,10 @@
  */
 package org.apache.phoenix.end2end.index;
 
+import static org.apache.phoenix.query.QueryConstants.MILLIS_IN_DAY;
 import static org.apache.phoenix.util.TestUtil.INDEX_DATA_SCHEMA;
 import static org.apache.phoenix.util.TestUtil.INDEX_DATA_TABLE;
 import static org.apache.phoenix.util.TestUtil.MUTABLE_INDEX_DATA_TABLE;
-import static org.apache.phoenix.util.TestUtil.NUM_MILLIS_IN_DAY;
 import static org.apache.phoenix.util.TestUtil.TEST_PROPERTIES;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -38,7 +38,6 @@ import org.apache.phoenix.util.DateUtil;
 import org.apache.phoenix.util.IndexUtil;
 import org.apache.phoenix.util.PropertiesUtil;
 import org.apache.phoenix.util.QueryUtil;
-import org.apache.phoenix.util.TestUtil;
 import org.junit.Test;
 
 public class IndexExpressionIT extends BaseHBaseManagedTimeIT {
@@ -76,7 +75,7 @@ public class IndexExpressionIT extends BaseHBaseManagedTimeIT {
         stmt.setInt(3, i);
         stmt.setLong(4, i);
         stmt.setBigDecimal(5, new BigDecimal(i*0.5d));
-        Date date = new Date(DateUtil.parseDate("2015-01-01 00:00:00").getTime() + (i - 1) * TestUtil.NUM_MILLIS_IN_DAY);
+        Date date = new Date(DateUtil.parseDate("2015-01-01 00:00:00").getTime() + (i - 1) * MILLIS_IN_DAY);
         stmt.setDate(6, date);
         stmt.setString(7, "a.varchar" + String.valueOf(i));
         stmt.setString(8, "a.char" + String.valueOf(i));
@@ -99,7 +98,7 @@ public class IndexExpressionIT extends BaseHBaseManagedTimeIT {
                 + "_A.VARCHAR" + String.valueOf(i) + "_" + StringUtils.rightPad("B.CHAR" + String.valueOf(i), 10, ' '),
                 rs.getString(1));
         assertEquals(i * 3, rs.getInt(2));
-        Date date = new Date(DateUtil.parseDate("2015-01-01 00:00:00").getTime() + (i) * TestUtil.NUM_MILLIS_IN_DAY);
+        Date date = new Date(DateUtil.parseDate("2015-01-01 00:00:00").getTime() + (i) * MILLIS_IN_DAY);
         assertEquals(date, rs.getDate(3));
         assertEquals(date, rs.getDate(4));
         assertEquals(date, rs.getDate(5));

http://git-wip-us.apache.org/repos/asf/phoenix/blob/f1b79d1d/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/IndexIT.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/IndexIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/IndexIT.java
index 6d54076..4a7e053 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/IndexIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/IndexIT.java
@@ -18,6 +18,7 @@
 
 package org.apache.phoenix.end2end.index;
 
+import static org.apache.phoenix.query.QueryConstants.MILLIS_IN_DAY;
 import static org.apache.phoenix.util.TestUtil.TEST_PROPERTIES;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -519,9 +520,9 @@ public class IndexIT extends BaseHBaseManagedTimeIT {
             assertTrue(rs.next());
             assertEquals(date, rs.getDate(1));
             assertTrue(rs.next());
-            assertEquals(new Date(date.getTime() + TestUtil.MILLIS_IN_DAY), rs.getDate(1));
+            assertEquals(new Date(date.getTime() + MILLIS_IN_DAY), rs.getDate(1));
             assertTrue(rs.next());
-            assertEquals(new Date(date.getTime() + 2 * TestUtil.MILLIS_IN_DAY), rs.getDate(1));
+            assertEquals(new Date(date.getTime() + 2 * MILLIS_IN_DAY), rs.getDate(1));
             assertFalse(rs.next());
         } 
     }

http://git-wip-us.apache.org/repos/asf/phoenix/blob/f1b79d1d/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PDataType.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PDataType.java b/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PDataType.java
index 7bf54ce..1b5b695 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PDataType.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PDataType.java
@@ -1116,6 +1116,9 @@ public abstract class PDataType<T> implements DataType<T>, Comparable<PDataType<
          */
         if (lowerRange != KeyRange.UNBOUND && !lowerInclusive && isFixedWidth()) {
             lowerRange = ByteUtil.nextKey(lowerRange);
+            if (lowerRange == null) { // overflow
+                lowerRange = KeyRange.UNBOUND;
+            }
             lowerInclusive = true;
         }
         return KeyRange.getKeyRange(lowerRange, lowerInclusive, upperRange, upperInclusive);

http://git-wip-us.apache.org/repos/asf/phoenix/blob/f1b79d1d/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PTimestamp.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PTimestamp.java b/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PTimestamp.java
index 16b110e..1f654fe 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PTimestamp.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PTimestamp.java
@@ -24,190 +24,243 @@ import java.text.Format;
 
 import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
 import org.apache.hadoop.hbase.util.Bytes;
+import org.apache.phoenix.query.KeyRange;
 import org.apache.phoenix.query.QueryConstants;
+import org.apache.phoenix.schema.IllegalDataException;
 import org.apache.phoenix.schema.SortOrder;
 import org.apache.phoenix.util.ByteUtil;
 import org.apache.phoenix.util.DateUtil;
 
 public class PTimestamp extends PDataType<Timestamp> {
+    public static final int MAX_NANOS_VALUE_EXCLUSIVE = 1000000;
+    public static final PTimestamp INSTANCE = new PTimestamp();
 
-  public static final PTimestamp INSTANCE = new PTimestamp();
-
-  private PTimestamp() {
-    super("TIMESTAMP", Types.TIMESTAMP, java.sql.Timestamp.class,
-        new PDate.DateCodec(), 9);
-  }
-
-  @Override
-  public byte[] toBytes(Object object) {
-    byte[] bytes = new byte[getByteSize()];
-    toBytes(object, bytes, 0);
-    return bytes;
-  }
-
-  @Override
-  public int toBytes(Object object, byte[] bytes, int offset) {
-    if (object == null) {
-      // Create the byte[] of size MAX_TIMESTAMP_BYTES
-      if(bytes.length != getByteSize()) {
-          bytes = Bytes.padTail(bytes, (getByteSize() - bytes.length));
-      }
-      PDate.INSTANCE.getCodec().encodeLong(0l, bytes, offset);
-      Bytes.putInt(bytes, offset + Bytes.SIZEOF_LONG, 0);
-      return getByteSize();
-    }
-    java.sql.Timestamp value = (java.sql.Timestamp) object;
-    PDate.INSTANCE.getCodec().encodeLong(value.getTime(), bytes, offset);
-
-    /*
-     * By not getting the stuff that got spilled over from the millis part,
-     * it leaves the timestamp's byte representation saner - 8 bytes of millis | 4 bytes of nanos.
-     * Also, it enables timestamp bytes to be directly compared with date/time bytes.
-     */
-    Bytes.putInt(bytes, offset + Bytes.SIZEOF_LONG, value.getNanos() % 1000000);
-    return getByteSize();
-  }
-
-  @Override
-  public Object toObject(Object object, PDataType actualType) {
-    if (object == null) {
-      return null;
-    }
-    if (equalsAny(actualType, PDate.INSTANCE, PUnsignedDate.INSTANCE, PTime.INSTANCE,
-        PUnsignedTime.INSTANCE)) {
-      return new java.sql.Timestamp(((java.util.Date) object).getTime());
-    } else if (equalsAny(actualType, PTimestamp.INSTANCE, PUnsignedTimestamp.INSTANCE)) {
-      return object;
-    } else if (equalsAny(actualType, PLong.INSTANCE, PUnsignedLong.INSTANCE)) {
-      return new java.sql.Timestamp((Long) object);
-    } else if (actualType == PDecimal.INSTANCE) {
-      BigDecimal bd = (BigDecimal) object;
-      long ms = bd.longValue();
-      int nanos =
-          (bd.remainder(BigDecimal.ONE).multiply(QueryConstants.BD_MILLIS_NANOS_CONVERSION))
-              .intValue();
-      return DateUtil.getTimestamp(ms, nanos);
-    } else if (actualType == PVarchar.INSTANCE) {
-      return DateUtil.parseTimestamp((String) object);
-    }
-    return throwConstraintViolationException(actualType, this);
-  }
-
-  @Override
-  public java.sql.Timestamp toObject(byte[] b, int o, int l, PDataType actualType,
-      SortOrder sortOrder, Integer maxLength, Integer scale) {
-    if (actualType == null || l == 0) {
-      return null;
-    }
-    java.sql.Timestamp v;
-    if (equalsAny(actualType, PTimestamp.INSTANCE, PUnsignedTimestamp.INSTANCE)) {
-      long millisDeserialized =
-          (actualType == PTimestamp.INSTANCE ? PDate.INSTANCE : PUnsignedDate.INSTANCE).getCodec()
-              .decodeLong(b, o, sortOrder);
-      v = new java.sql.Timestamp(millisDeserialized);
-      int nanosDeserialized =
-          PUnsignedInt.INSTANCE.getCodec().decodeInt(b, o + Bytes.SIZEOF_LONG, sortOrder);
-                /*
-                 * There was a bug in serialization of timestamps which was causing the sub-second millis part
-                 * of time stamp to be present both in the LONG and INT bytes. Having the <100000 check
-                 * makes this serialization fix backward compatible.
-                 */
-      v.setNanos(
-          nanosDeserialized < 1000000 ? v.getNanos() + nanosDeserialized : nanosDeserialized);
-      return v;
-    } else if (equalsAny(actualType, PDate.INSTANCE, PUnsignedDate.INSTANCE, PTime.INSTANCE,
-        PUnsignedTime.INSTANCE, PLong.INSTANCE, PUnsignedLong.INSTANCE)) {
-      return new java.sql.Timestamp(actualType.getCodec().decodeLong(b, o, sortOrder));
-    } else if (actualType == PDecimal.INSTANCE) {
-      BigDecimal bd = (BigDecimal) actualType.toObject(b, o, l, actualType, sortOrder);
-      long ms = bd.longValue();
-      int nanos = (bd.remainder(BigDecimal.ONE).multiply(QueryConstants.BD_MILLIS_NANOS_CONVERSION))
-          .intValue();
-      v = DateUtil.getTimestamp(ms, nanos);
-      return v;
-    }
-    throwConstraintViolationException(actualType, this);
-    return null;
-  }
-
-  @Override
-  public boolean isCastableTo(PDataType targetType) {
-    return PDate.INSTANCE.isCastableTo(targetType);
-  }
-
-  @Override
-  public boolean isCoercibleTo(PDataType targetType) {
-    return equalsAny(targetType, this, PVarbinary.INSTANCE, PBinary.INSTANCE);
-  }
-
-  @Override
-  public boolean isCoercibleTo(PDataType targetType, Object value) {
-    if (value != null) {
-      if (targetType.equals(PUnsignedTimestamp.INSTANCE)) {
-        return ((java.util.Date) value).getTime() >= 0;
-      } else if (equalsAny(targetType, PUnsignedDate.INSTANCE, PUnsignedTime.INSTANCE)) {
-        return ((java.util.Date) value).getTime() >= 0
-            && ((java.sql.Timestamp) value).getNanos() == 0;
-      } else if (equalsAny(targetType, PDate.INSTANCE, PTime.INSTANCE)) {
-        return ((java.sql.Timestamp) value).getNanos() == 0;
-      }
-    }
-    return super.isCoercibleTo(targetType, value);
-  }
-
-  @Override
-  public boolean isFixedWidth() {
-    return true;
-  }
-
-  @Override
-  public Integer getByteSize() {
-    return MAX_TIMESTAMP_BYTES;
-  }
-
-  @Override
-  public int compareTo(Object lhs, Object rhs, PDataType rhsType) {
-    if (equalsAny(rhsType, PTimestamp.INSTANCE, PUnsignedTimestamp.INSTANCE)) {
-      return ((java.sql.Timestamp) lhs).compareTo((java.sql.Timestamp) rhs);
-    }
-    int c = ((java.util.Date) rhs).compareTo((java.util.Date) lhs);
-    if (c != 0) return c;
-    return ((java.sql.Timestamp) lhs).getNanos();
-  }
-
-  @Override
-  public Object toObject(String value) {
-    if (value == null || value.length() == 0) {
-      return null;
-    }
-    return DateUtil.parseTimestamp(value);
-  }
-
-  @Override
-  public String toStringLiteral(Object o, Format formatter) {
-      if (formatter == null) {
-          formatter = DateUtil.DEFAULT_TIMESTAMP_FORMATTER;
+    private PTimestamp() {
+        super("TIMESTAMP", Types.TIMESTAMP, java.sql.Timestamp.class,
+                new PDate.DateCodec(), 9);
+    }
+
+    @Override
+    public byte[] toBytes(Object object) {
+        byte[] bytes = new byte[getByteSize()];
+        toBytes(object, bytes, 0);
+        return bytes;
+    }
+
+    @Override
+    public int toBytes(Object object, byte[] bytes, int offset) {
+        if (object == null) {
+            // Create the byte[] of size MAX_TIMESTAMP_BYTES
+            if(bytes.length != getByteSize()) {
+                bytes = Bytes.padTail(bytes, (getByteSize() - bytes.length));
+            }
+            PDate.INSTANCE.getCodec().encodeLong(0l, bytes, offset);
+            Bytes.putInt(bytes, offset + Bytes.SIZEOF_LONG, 0);
+            return getByteSize();
+        }
+        java.sql.Timestamp value = (java.sql.Timestamp) object;
+        // For Timestamp, the getTime() method includes milliseconds that may
+        // be stored in the nanos part as well.
+        PDate.INSTANCE.getCodec().encodeLong(value.getTime(), bytes, offset);
+
+        /*
+         * By not getting the stuff that got spilled over from the millis part,
+         * it leaves the timestamp's byte representation saner - 8 bytes of millis | 4 bytes of nanos.
+         * Also, it enables timestamp bytes to be directly compared with date/time bytes.
+         */
+        Bytes.putInt(bytes, offset + Bytes.SIZEOF_LONG, value.getNanos() % MAX_NANOS_VALUE_EXCLUSIVE);
+        return getByteSize();
+    }
+
+    @Override
+    public Object toObject(Object object, PDataType actualType) {
+        if (object == null) {
+            return null;
+        }
+        if (equalsAny(actualType, PDate.INSTANCE, PUnsignedDate.INSTANCE, PTime.INSTANCE,
+                PUnsignedTime.INSTANCE)) {
+            return new java.sql.Timestamp(((java.util.Date) object).getTime());
+        } else if (equalsAny(actualType, PTimestamp.INSTANCE, PUnsignedTimestamp.INSTANCE)) {
+            return object;
+        } else if (equalsAny(actualType, PLong.INSTANCE, PUnsignedLong.INSTANCE)) {
+            return new java.sql.Timestamp((Long) object);
+        } else if (actualType == PDecimal.INSTANCE) {
+            BigDecimal bd = (BigDecimal) object;
+            long ms = bd.longValue();
+            int nanos =
+                    (bd.remainder(BigDecimal.ONE).multiply(QueryConstants.BD_MILLIS_NANOS_CONVERSION))
+                    .intValue();
+            return DateUtil.getTimestamp(ms, nanos);
+        } else if (actualType == PVarchar.INSTANCE) {
+            return DateUtil.parseTimestamp((String) object);
+        }
+        return throwConstraintViolationException(actualType, this);
+    }
+
+    @Override
+    public java.sql.Timestamp toObject(byte[] b, int o, int l, PDataType actualType,
+            SortOrder sortOrder, Integer maxLength, Integer scale) {
+        if (actualType == null || l == 0) {
+            return null;
+        }
+        java.sql.Timestamp v;
+        if (equalsAny(actualType, PTimestamp.INSTANCE, PUnsignedTimestamp.INSTANCE)) {
+            long millisDeserialized =
+                    (actualType == PTimestamp.INSTANCE ? PDate.INSTANCE : PUnsignedDate.INSTANCE).getCodec()
+                    .decodeLong(b, o, sortOrder);
+            v = new java.sql.Timestamp(millisDeserialized);
+            int nanosDeserialized =
+                    PUnsignedInt.INSTANCE.getCodec().decodeInt(b, o + Bytes.SIZEOF_LONG, sortOrder);
+            /*
+             * There was a bug in serialization of timestamps which was causing the sub-second millis part
+             * of time stamp to be present both in the LONG and INT bytes. Having the <100000 check
+             * makes this serialization fix backward compatible.
+             */
+            v.setNanos(
+                    nanosDeserialized < MAX_NANOS_VALUE_EXCLUSIVE ? v.getNanos() + nanosDeserialized : nanosDeserialized);
+            return v;
+        } else if (equalsAny(actualType, PDate.INSTANCE, PUnsignedDate.INSTANCE, PTime.INSTANCE,
+                PUnsignedTime.INSTANCE, PLong.INSTANCE, PUnsignedLong.INSTANCE)) {
+            return new java.sql.Timestamp(actualType.getCodec().decodeLong(b, o, sortOrder));
+        } else if (actualType == PDecimal.INSTANCE) {
+            BigDecimal bd = (BigDecimal) actualType.toObject(b, o, l, actualType, sortOrder);
+            long ms = bd.longValue();
+            int nanos = (bd.remainder(BigDecimal.ONE).multiply(QueryConstants.BD_MILLIS_NANOS_CONVERSION))
+                    .intValue();
+            v = DateUtil.getTimestamp(ms, nanos);
+            return v;
+        }
+        throwConstraintViolationException(actualType, this);
+        return null;
+    }
+
+    @Override
+    public boolean isCastableTo(PDataType targetType) {
+        return PDate.INSTANCE.isCastableTo(targetType);
+    }
+
+    @Override
+    public boolean isCoercibleTo(PDataType targetType) {
+        return equalsAny(targetType, this, PVarbinary.INSTANCE, PBinary.INSTANCE);
+    }
+
+    @Override
+    public boolean isCoercibleTo(PDataType targetType, Object value) {
+        if (value != null) {
+            if (targetType.equals(PUnsignedTimestamp.INSTANCE)) {
+                return ((java.util.Date) value).getTime() >= 0;
+            } else if (equalsAny(targetType, PUnsignedDate.INSTANCE, PUnsignedTime.INSTANCE)) {
+                return ((java.util.Date) value).getTime() >= 0
+                        && ((java.sql.Timestamp) value).getNanos() == 0;
+            } else if (equalsAny(targetType, PDate.INSTANCE, PTime.INSTANCE)) {
+                return ((java.sql.Timestamp) value).getNanos() == 0;
+            }
+        }
+        return super.isCoercibleTo(targetType, value);
+    }
+
+    @Override
+    public boolean isFixedWidth() {
+        return true;
+    }
+
+    @Override
+    public Integer getByteSize() {
+        return MAX_TIMESTAMP_BYTES;
+    }
+
+    @Override
+    public int compareTo(Object lhs, Object rhs, PDataType rhsType) {
+        if (equalsAny(rhsType, PTimestamp.INSTANCE, PUnsignedTimestamp.INSTANCE)) {
+            return ((java.sql.Timestamp) lhs).compareTo((java.sql.Timestamp) rhs);
+        }
+        int c = ((java.util.Date) rhs).compareTo((java.util.Date) lhs);
+        if (c != 0) return c;
+        return ((java.sql.Timestamp) lhs).getNanos();
+    }
+
+    @Override
+    public Object toObject(String value) {
+        if (value == null || value.length() == 0) {
+            return null;
+        }
+        return DateUtil.parseTimestamp(value);
+    }
+
+    @Override
+    public String toStringLiteral(Object o, Format formatter) {
+        if (formatter == null) {
+            formatter = DateUtil.DEFAULT_TIMESTAMP_FORMATTER;
         }
         return "'" + super.toStringLiteral(o, formatter) + "'";
-  }
-
-
-  @Override
-  public int getNanos(ImmutableBytesWritable ptr, SortOrder sortOrder) {
-    int nanos = PUnsignedInt.INSTANCE.getCodec()
-        .decodeInt(ptr.get(), ptr.getOffset() + PLong.INSTANCE.getByteSize(), sortOrder);
-    return nanos;
-  }
-
-  @Override
-  public long getMillis(ImmutableBytesWritable ptr, SortOrder sortOrder) {
-    long millis = PLong.INSTANCE.getCodec().decodeLong(ptr.get(), ptr.getOffset(), sortOrder);
-    return millis;
-  }
-
-  @Override
-  public Object getSampleValue(Integer maxLength, Integer arrayLength) {
-    return new java.sql.Timestamp(
-        (Long) PLong.INSTANCE.getSampleValue(maxLength, arrayLength));
-  }
+    }
+
+
+    @Override
+    public int getNanos(ImmutableBytesWritable ptr, SortOrder sortOrder) {
+        int nanos = PUnsignedInt.INSTANCE.getCodec()
+                .decodeInt(ptr.get(), ptr.getOffset() + PLong.INSTANCE.getByteSize(), sortOrder);
+        return nanos;
+    }
+
+    @Override
+    public long getMillis(ImmutableBytesWritable ptr, SortOrder sortOrder) {
+        long millis = PLong.INSTANCE.getCodec().decodeLong(ptr.get(), ptr.getOffset(), sortOrder);
+        return millis;
+    }
+
+    @Override
+    public Object getSampleValue(Integer maxLength, Integer arrayLength) {
+        return new java.sql.Timestamp(
+                (Long) PLong.INSTANCE.getSampleValue(maxLength, arrayLength));
+    }
+
+    /**
+     * With timestamp, because our last 4 bytes store a value from [0 - 1000000), we need
+     * to detect when the boundary is crossed if we increment to the nextKey.
+     */
+    @Override
+    public KeyRange getKeyRange(byte[] lowerRange, boolean lowerInclusive, byte[] upperRange, boolean upperInclusive) {
+        /*
+         * Force lower bound to be inclusive for fixed width keys because it makes comparisons less expensive when you
+         * can count on one bound or the other being inclusive. Comparing two fixed width exclusive bounds against each
+         * other is inherently more expensive, because you need to take into account if the bigger key is equal to the
+         * next key after the smaller key. For example: (A-B] compared against [A-B) An exclusive lower bound A is
+         * bigger than an exclusive upper bound B. Forcing a fixed width exclusive lower bound key to be inclusive
+         * prevents us from having to do this extra logic in the compare function.
+         * 
+         */
+        if (lowerRange != KeyRange.UNBOUND && !lowerInclusive && isFixedWidth()) {
+            if (lowerRange.length != MAX_TIMESTAMP_BYTES) {
+                throw new IllegalDataException("Unexpected size of " + lowerRange.length + " for " + this);
+            }
+            // Infer sortOrder based on most significant byte
+            SortOrder sortOrder = lowerRange[Bytes.SIZEOF_LONG] < 0 ? SortOrder.DESC : SortOrder.ASC;
+            int nanos = PUnsignedInt.INSTANCE.getCodec().decodeInt(lowerRange, Bytes.SIZEOF_LONG, sortOrder);
+            if ((sortOrder == SortOrder.DESC && nanos == 0) || (sortOrder == SortOrder.ASC && nanos == MAX_NANOS_VALUE_EXCLUSIVE-1)) {
+                // With timestamp, because our last 4 bytes store a value from [0 - 1000000), we need
+                // to detect when the boundary is crossed with our nextKey
+                byte[] newLowerRange = new byte[MAX_TIMESTAMP_BYTES];
+                if (sortOrder == SortOrder.DESC) {
+                    // Set nanos part as inverted 999999 as it needs to be the max nano value
+                    // The millisecond part is moving to the previous value below
+                    System.arraycopy(lowerRange, 0, newLowerRange, 0, Bytes.SIZEOF_LONG);
+                    PUnsignedInt.INSTANCE.getCodec().encodeInt(MAX_NANOS_VALUE_EXCLUSIVE-1, newLowerRange, Bytes.SIZEOF_LONG);
+                    SortOrder.invert(newLowerRange, Bytes.SIZEOF_LONG, newLowerRange, Bytes.SIZEOF_LONG, Bytes.SIZEOF_INT);
+                } else {
+                    // Leave nanos part as zero as the millisecond part is rolling over to the next value
+                    System.arraycopy(lowerRange, 0, newLowerRange, 0, Bytes.SIZEOF_LONG);
+                }
+                // Increment millisecond part, but leave nanos alone
+                if (ByteUtil.nextKey(newLowerRange, Bytes.SIZEOF_LONG)) {
+                    lowerRange = newLowerRange;
+                } else {
+                    lowerRange = KeyRange.UNBOUND;
+                }
+                return KeyRange.getKeyRange(lowerRange, true, upperRange, upperInclusive);
+            }
+        }
+        return super.getKeyRange(lowerRange, lowerInclusive, upperRange, upperInclusive);
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/phoenix/blob/f1b79d1d/phoenix-core/src/test/java/org/apache/phoenix/compile/WhereOptimizerTest.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/test/java/org/apache/phoenix/compile/WhereOptimizerTest.java b/phoenix-core/src/test/java/org/apache/phoenix/compile/WhereOptimizerTest.java
index 1b45dc4..9076ea5 100644
--- a/phoenix-core/src/test/java/org/apache/phoenix/compile/WhereOptimizerTest.java
+++ b/phoenix-core/src/test/java/org/apache/phoenix/compile/WhereOptimizerTest.java
@@ -17,6 +17,7 @@
  */
 package org.apache.phoenix.compile;
 
+import static org.apache.phoenix.query.QueryConstants.MILLIS_IN_DAY;
 import static org.apache.phoenix.util.TestUtil.BINARY_NAME;
 import static org.apache.phoenix.util.TestUtil.TEST_PROPERTIES;
 import static org.apache.phoenix.util.TestUtil.assertDegenerate;
@@ -36,8 +37,6 @@ import java.math.BigDecimal;
 import java.sql.Connection;
 import java.sql.Date;
 import java.sql.DriverManager;
-import java.sql.PreparedStatement;
-import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.util.Arrays;
 import java.util.Collections;
@@ -1263,7 +1262,7 @@ public class WhereOptimizerTest extends BaseConnectionlessQueryTest {
         String query = "select /*+ RANGE_SCAN */ ORGANIZATION_ID, PARENT_ID, CREATED_DATE, ENTITY_HISTORY_ID from " + TestUtil.ENTITY_HISTORY_TABLE_NAME + 
                 " where ORGANIZATION_ID=? and SUBSTR(PARENT_ID, 1, 3) = ? and  CREATED_DATE >= ? and CREATED_DATE < ? order by ORGANIZATION_ID, PARENT_ID, CREATED_DATE, ENTITY_HISTORY_ID limit 6";
         Date startTime = new Date(System.currentTimeMillis());
-        Date stopTime = new Date(startTime.getTime() + TestUtil.MILLIS_IN_DAY);
+        Date stopTime = new Date(startTime.getTime() + MILLIS_IN_DAY);
         List<Object> binds = Arrays.<Object>asList(tenantId, keyPrefix, startTime, stopTime);
         StatementContext context = compileStatement(query, binds, 6);
         Scan scan = context.getScan();

http://git-wip-us.apache.org/repos/asf/phoenix/blob/f1b79d1d/phoenix-core/src/test/java/org/apache/phoenix/query/BaseTest.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/test/java/org/apache/phoenix/query/BaseTest.java b/phoenix-core/src/test/java/org/apache/phoenix/query/BaseTest.java
index 9539e69..1192059 100644
--- a/phoenix-core/src/test/java/org/apache/phoenix/query/BaseTest.java
+++ b/phoenix-core/src/test/java/org/apache/phoenix/query/BaseTest.java
@@ -17,6 +17,7 @@
  */
 package org.apache.phoenix.query;
 
+import static org.apache.phoenix.query.QueryConstants.MILLIS_IN_DAY;
 import static org.apache.phoenix.util.PhoenixRuntime.CURRENT_SCN_ATTRIB;
 import static org.apache.phoenix.util.PhoenixRuntime.JDBC_PROTOCOL;
 import static org.apache.phoenix.util.PhoenixRuntime.JDBC_PROTOCOL_TERMINATOR;
@@ -53,7 +54,6 @@ import static org.apache.phoenix.util.TestUtil.JOIN_ORDER_TABLE_FULL_NAME;
 import static org.apache.phoenix.util.TestUtil.JOIN_SUPPLIER_TABLE_FULL_NAME;
 import static org.apache.phoenix.util.TestUtil.KEYONLY_NAME;
 import static org.apache.phoenix.util.TestUtil.MDTEST_NAME;
-import static org.apache.phoenix.util.TestUtil.MILLIS_IN_DAY;
 import static org.apache.phoenix.util.TestUtil.MULTI_CF_NAME;
 import static org.apache.phoenix.util.TestUtil.MUTABLE_INDEX_DATA_TABLE;
 import static org.apache.phoenix.util.TestUtil.PARENTID1;
@@ -159,7 +159,6 @@ import org.apache.phoenix.util.PropertiesUtil;
 import org.apache.phoenix.util.QueryUtil;
 import org.apache.phoenix.util.ReadOnlyProps;
 import org.apache.phoenix.util.SchemaUtil;
-import org.apache.phoenix.util.TestUtil;
 import org.apache.twill.discovery.DiscoveryService;
 import org.apache.twill.discovery.ZKDiscoveryService;
 import org.apache.twill.internal.utils.Networks;
@@ -1933,7 +1932,7 @@ public abstract class BaseTest {
             stmt.setInt(13, 3);
             stmt.setLong(14, 3L);
             stmt.setBigDecimal(15, new BigDecimal("3.1"));
-            stmt.setDate(16, date == null ? null : new Date(date.getTime() + TestUtil.MILLIS_IN_DAY));
+            stmt.setDate(16, date == null ? null : new Date(date.getTime() + MILLIS_IN_DAY));
             stmt.executeUpdate();
             
             stmt.setString(1, "varchar2");
@@ -1969,7 +1968,7 @@ public abstract class BaseTest {
             stmt.setInt(13, 5);
             stmt.setLong(14, 5L);
             stmt.setBigDecimal(15, new BigDecimal("5.3"));
-            stmt.setDate(16, date == null ? null : new Date(date.getTime() + 2 * TestUtil.MILLIS_IN_DAY));
+            stmt.setDate(16, date == null ? null : new Date(date.getTime() + 2 * MILLIS_IN_DAY));
             stmt.executeUpdate();
             
             conn.commit();

http://git-wip-us.apache.org/repos/asf/phoenix/blob/f1b79d1d/phoenix-core/src/test/java/org/apache/phoenix/query/QueryPlanTest.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/test/java/org/apache/phoenix/query/QueryPlanTest.java b/phoenix-core/src/test/java/org/apache/phoenix/query/QueryPlanTest.java
index fdae749..4ba4d16 100644
--- a/phoenix-core/src/test/java/org/apache/phoenix/query/QueryPlanTest.java
+++ b/phoenix-core/src/test/java/org/apache/phoenix/query/QueryPlanTest.java
@@ -26,6 +26,7 @@ import java.sql.Statement;
 import java.util.Properties;
 
 import org.apache.phoenix.util.PhoenixRuntime;
+import org.apache.phoenix.util.PropertiesUtil;
 import org.apache.phoenix.util.QueryUtil;
 import org.junit.Test;
 
@@ -229,8 +230,27 @@ public class QueryPlanTest extends BaseConnectionlessQueryTest {
     }
     
     @Test
-    public void testLimitOnTenantSpecific() throws Exception {
-        
+    public void testDescTimestampAtBoundary() throws Exception {
+        Properties props = PropertiesUtil.deepCopy(new Properties());
+        Connection conn = DriverManager.getConnection(getUrl(), props);
+        try {
+            conn.createStatement().execute("CREATE TABLE FOO(\n" + 
+                    "                a VARCHAR NOT NULL,\n" + 
+                    "                b TIMESTAMP NOT NULL,\n" + 
+                    "                c VARCHAR,\n" + 
+                    "                CONSTRAINT pk PRIMARY KEY (a, b DESC, c)\n" + 
+                    "              ) IMMUTABLE_ROWS=true\n" + 
+                    "                ,SALT_BUCKETS=20");
+            String query = "select * from foo where a = 'a' and b >= timestamp '2016-01-28 00:00:00' and b < timestamp '2016-01-29 00:00:00'";
+            ResultSet rs = conn.createStatement().executeQuery("EXPLAIN " + query);
+            String queryPlan = QueryUtil.getExplainPlan(rs);
+            assertEquals(
+                    "CLIENT PARALLEL 20-WAY RANGE SCAN OVER FOO [0,'a',~'2016-01-28 23:59:59.999'] - [0,'a',~'2016-01-28 00:00:00.000']\n" + 
+                    "    SERVER FILTER BY FIRST KEY ONLY\n" + 
+                    "CLIENT MERGE SORT", queryPlan);
+        } finally {
+            conn.close();
+        }
     }
 
 }

http://git-wip-us.apache.org/repos/asf/phoenix/blob/f1b79d1d/phoenix-core/src/test/java/org/apache/phoenix/schema/types/PDataTypeTest.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/test/java/org/apache/phoenix/schema/types/PDataTypeTest.java b/phoenix-core/src/test/java/org/apache/phoenix/schema/types/PDataTypeTest.java
index 7a04aeb..085af44 100644
--- a/phoenix-core/src/test/java/org/apache/phoenix/schema/types/PDataTypeTest.java
+++ b/phoenix-core/src/test/java/org/apache/phoenix/schema/types/PDataTypeTest.java
@@ -17,6 +17,7 @@
  */
 package org.apache.phoenix.schema.types;
 
+import static org.apache.phoenix.query.QueryConstants.MILLIS_IN_DAY;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
@@ -38,9 +39,11 @@ import java.util.List;
 import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
 import org.apache.hadoop.hbase.util.Bytes;
 import org.apache.phoenix.exception.SQLExceptionCode;
+import org.apache.phoenix.query.KeyRange;
 import org.apache.phoenix.query.QueryConstants;
 import org.apache.phoenix.schema.ConstraintViolationException;
 import org.apache.phoenix.schema.SortOrder;
+import org.apache.phoenix.util.DateUtil;
 import org.apache.phoenix.util.ScanUtil;
 import org.apache.phoenix.util.TestUtil;
 import org.junit.Test;
@@ -1832,4 +1835,61 @@ public class PDataTypeTest {
         b = PBoolean.INSTANCE.toObject(bytes, 0, bytes.length, PDecimal.INSTANCE, SortOrder.DESC);
         assertEquals(false, b);
     }
+    
+    @Test
+    public void testTimestamp() {
+        long now = System.currentTimeMillis();
+        Timestamp ts1 = DateUtil.getTimestamp(now,  1111);
+        final byte[] bytes1 = PTimestamp.INSTANCE.toBytes(ts1);
+        Timestamp ts2 = DateUtil.getTimestamp(now,  1112);
+        final byte[] bytes2 = PTimestamp.INSTANCE.toBytes(ts2);
+        assertTrue(Bytes.compareTo(bytes1, bytes2) < 0);
+        
+        final byte[] ibytes1 = SortOrder.invert(bytes1, 0, bytes1.length);
+        final byte[] ibytes2 = SortOrder.invert(bytes2, 0, bytes2.length);
+        assertTrue(Bytes.compareTo(ibytes1, ibytes2) > 0);
+
+        Timestamp ts3 = new Timestamp(now+1);
+        final byte[] bytes3 = PTimestamp.INSTANCE.toBytes(ts3);
+        assertTrue(Bytes.compareTo(bytes3, bytes2) > 0);
+        final byte[] ibytes3 = SortOrder.invert(bytes3, 0, bytes3.length);
+        assertTrue(Bytes.compareTo(ibytes3, ibytes2) < 0);
+        
+        Timestamp ts4 = new Timestamp(now-1);
+        byte[] bytes4 = PTimestamp.INSTANCE.toBytes(ts4);
+        assertTrue(Bytes.compareTo(bytes4, bytes1) < 0);
+        byte[] ibytes4 = SortOrder.invert(bytes4, 0, bytes4.length);
+        assertTrue(Bytes.compareTo(ibytes4, ibytes1) > 0);
+    }
+    
+    @Test
+    public void testAscExclusiveTimestampRange() {
+        long now = System.currentTimeMillis();
+        Timestamp ts1 = DateUtil.getTimestamp(now,  999999);
+        final byte[] lowerRange = PTimestamp.INSTANCE.toBytes(ts1);
+        Timestamp ts2 = new Timestamp(now + MILLIS_IN_DAY);
+        final byte[] upperRange = PTimestamp.INSTANCE.toBytes(ts2);
+        KeyRange range = PTimestamp.INSTANCE.getKeyRange(lowerRange, false, upperRange, false);
+        Timestamp ts3 = new Timestamp(now + 1);
+        // Rolled up to next millis
+        final byte[] expectedLowerRange = PTimestamp.INSTANCE.toBytes(ts3);
+        assertTrue(Bytes.compareTo(expectedLowerRange, range.getLowerRange()) == 0);
+        assertTrue(Bytes.compareTo(upperRange, range.getUpperRange()) == 0);
+    }
+
+    
+    @Test
+    public void testDescExclusiveTimestampRange() {
+        long now = System.currentTimeMillis();
+        Timestamp ts1 = new Timestamp(now + MILLIS_IN_DAY);
+        final byte[] lowerRange = PTimestamp.INSTANCE.toBytes(ts1, SortOrder.DESC);
+        Timestamp ts2 = new Timestamp(now);
+        final byte[] upperRange = PTimestamp.INSTANCE.toBytes(ts2, SortOrder.DESC);
+        KeyRange range = PTimestamp.INSTANCE.getKeyRange(lowerRange, false, upperRange, false);
+        Timestamp ts3 = DateUtil.getTimestamp(now + MILLIS_IN_DAY - 1,  999999);
+        // Rolled up to next millis
+        final byte[] expectedLowerRange = PTimestamp.INSTANCE.toBytes(ts3, SortOrder.DESC);
+        assertTrue(Bytes.compareTo(expectedLowerRange, range.getLowerRange()) == 0);
+        assertTrue(Bytes.compareTo(upperRange, range.getUpperRange()) == 0);
+    }
 }

http://git-wip-us.apache.org/repos/asf/phoenix/blob/f1b79d1d/phoenix-core/src/test/java/org/apache/phoenix/util/TestUtil.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/test/java/org/apache/phoenix/util/TestUtil.java b/phoenix-core/src/test/java/org/apache/phoenix/util/TestUtil.java
index 66a3c65..c73c160 100644
--- a/phoenix-core/src/test/java/org/apache/phoenix/util/TestUtil.java
+++ b/phoenix-core/src/test/java/org/apache/phoenix/util/TestUtil.java
@@ -17,6 +17,7 @@
  */
 package org.apache.phoenix.util;
 
+import static org.apache.phoenix.query.QueryConstants.MILLIS_IN_DAY;
 import static org.apache.phoenix.util.PhoenixRuntime.CONNECTIONLESS;
 import static org.apache.phoenix.util.PhoenixRuntime.JDBC_PROTOCOL;
 import static org.apache.phoenix.util.PhoenixRuntime.JDBC_PROTOCOL_SEPARATOR;
@@ -155,7 +156,6 @@ public class TestUtil {
 
     public final static List<String> ENTITYHISTIDS = Lists.newArrayList(ENTITYHISTID1, ENTITYHISTID2, ENTITYHISTID3, ENTITYHISTID4, ENTITYHISTID5, ENTITYHISTID6, ENTITYHISTID7, ENTITYHISTID8, ENTITYHISTID9);
     
-    public static final long MILLIS_IN_DAY = 1000 * 60 * 60 * 24;
     public static final String LOCALHOST = "localhost";
     public static final String PHOENIX_JDBC_URL = JDBC_PROTOCOL + JDBC_PROTOCOL_SEPARATOR + LOCALHOST + JDBC_PROTOCOL_TERMINATOR + PHOENIX_TEST_DRIVER_URL_PARAM;
     public static final String PHOENIX_CONNECTIONLESS_JDBC_URL = JDBC_PROTOCOL + JDBC_PROTOCOL_SEPARATOR + CONNECTIONLESS  + JDBC_PROTOCOL_TERMINATOR  + PHOENIX_TEST_DRIVER_URL_PARAM;
@@ -212,7 +212,6 @@ public class TestUtil {
     public static final String JOIN_SUPPLIER_TABLE_DISPLAY_NAME = JOIN_SCHEMA + "." + JOIN_SUPPLIER_TABLE;
     public static final String JOIN_COITEM_TABLE_DISPLAY_NAME = JOIN_SCHEMA + "." + JOIN_COITEM_TABLE;
     public static final String BINARY_NAME = "BinaryTable";
-    public static final int NUM_MILLIS_IN_DAY = 86400000;
 
     /**
      * Read-only properties used by all tests
@@ -600,7 +599,7 @@ public class TestUtil {
         stmt.setInt(3, i);
         stmt.setLong(4, i);
         stmt.setBigDecimal(5, new BigDecimal(i*0.5d));
-        Date date = new Date(DateUtil.parseDate("2015-01-01 00:00:00").getTime() + (i - 1) * TestUtil.NUM_MILLIS_IN_DAY);
+        Date date = new Date(DateUtil.parseDate("2015-01-01 00:00:00").getTime() + (i - 1) * MILLIS_IN_DAY);
         stmt.setDate(6, date);
     }
 	
@@ -611,7 +610,7 @@ public class TestUtil {
 		assertEquals(rs.getInt(3), i);
 		assertEquals(rs.getInt(4), i);
 		assertEquals(rs.getBigDecimal(5), new BigDecimal(i*0.5d));
-		Date date = new Date(DateUtil.parseDate("2015-01-01 00:00:00").getTime() + (i - 1) * TestUtil.NUM_MILLIS_IN_DAY);
+		Date date = new Date(DateUtil.parseDate("2015-01-01 00:00:00").getTime() + (i - 1) * MILLIS_IN_DAY);
 		assertEquals(rs.getDate(6), date);
 	}