Repository: calcite
Updated Branches:
  refs/heads/master bb6ae0acf -> 49888a6c5


[CALCITE-1664] CAST('<string>' as TIMESTAMP) wrongly adds part of sub-second 
fraction to the value

Fix is in Avatica. In Calcite, add a test case (currently with the wrong 
result, until we upgrade to the Avatica version with the fix).


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

Branch: refs/heads/master
Commit: 4ba9d1b2284b93bee8f94d4fcff62b8ff2235d84
Parents: bb6ae0a
Author: Julian Hyde <[email protected]>
Authored: Mon Feb 27 12:55:26 2017 -0800
Committer: Julian Hyde <[email protected]>
Committed: Mon Feb 27 12:55:26 2017 -0800

----------------------------------------------------------------------
 .../calcite/avatica/util/DateTimeUtils.java     | 50 ++++++++++++++++++--
 .../calcite/avatica/util/DateTimeUtilsTest.java | 31 ++++++++----
 core/src/test/resources/sql/misc.iq             | 18 +++++++
 3 files changed, 87 insertions(+), 12 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/calcite/blob/4ba9d1b2/avatica/core/src/main/java/org/apache/calcite/avatica/util/DateTimeUtils.java
----------------------------------------------------------------------
diff --git 
a/avatica/core/src/main/java/org/apache/calcite/avatica/util/DateTimeUtils.java 
b/avatica/core/src/main/java/org/apache/calcite/avatica/util/DateTimeUtils.java
index c4625d9..85b6d6c 100644
--- 
a/avatica/core/src/main/java/org/apache/calcite/avatica/util/DateTimeUtils.java
+++ 
b/avatica/core/src/main/java/org/apache/calcite/avatica/util/DateTimeUtils.java
@@ -265,6 +265,10 @@ public class DateTimeUtils {
 
   /** Helper for CAST({timestamp} AS VARCHAR(n)). */
   public static String unixTimestampToString(long timestamp) {
+    return unixTimestampToString(timestamp, 0);
+  }
+
+  public static String unixTimestampToString(long timestamp, int precision) {
     final StringBuilder buf = new StringBuilder(17);
     int date = (int) (timestamp / MILLIS_PER_DAY);
     int time = (int) (timestamp % MILLIS_PER_DAY);
@@ -274,18 +278,23 @@ public class DateTimeUtils {
     }
     unixDateToString(buf, date);
     buf.append(' ');
-    unixTimeToString(buf, time);
+    unixTimeToString(buf, time, precision);
     return buf.toString();
   }
 
   /** Helper for CAST({timestamp} AS VARCHAR(n)). */
   public static String unixTimeToString(int time) {
+    return unixTimeToString(time, 0);
+  }
+
+  public static String unixTimeToString(int time, int precision) {
     final StringBuilder buf = new StringBuilder(8);
-    unixTimeToString(buf, time);
+    unixTimeToString(buf, time, precision);
     return buf.toString();
   }
 
-  private static void unixTimeToString(StringBuilder buf, int time) {
+  private static void unixTimeToString(StringBuilder buf, int time,
+      int precision) {
     int h = time / 3600000;
     int time2 = time % 3600000;
     int m = time2 / 60000;
@@ -297,6 +306,15 @@ public class DateTimeUtils {
     int2(buf, m);
     buf.append(':');
     int2(buf, s);
+    if (precision > 0) {
+      buf.append('.');
+      while (precision > 0) {
+        buf.append((char) ('0' + (ms / 100)));
+        ms = ms % 100;
+        ms = ms * 10;
+        --precision;
+      }
+    }
   }
 
   private static void int2(StringBuilder buf, int i) {
@@ -635,7 +653,7 @@ public class DateTimeUtils {
           milli = 0;
         } else {
           second = Integer.parseInt(v.substring(colon2 + 1, dot).trim());
-          milli = Integer.parseInt(v.substring(dot + 1).trim());
+          milli = parseFraction(v.substring(dot + 1).trim(), 100);
         }
       }
     }
@@ -645,6 +663,30 @@ public class DateTimeUtils {
         + milli;
   }
 
+  /** Parses a fraction, multiplying the first character by {@code multiplier},
+   * the second character by {@code multiplier / 10},
+   * the third character by {@code multiplier / 100}, and so forth.
+   *
+   * <p>For example, {@code parseFraction("1234", 100)} yields {@code 123}. */
+  private static int parseFraction(String v, int multiplier) {
+    int r = 0;
+    for (int i = 0; i < v.length(); i++) {
+      char c = v.charAt(i);
+      int x = c < '0' || c > '9' ? 0 : (c - '0');
+      r += multiplier * x;
+      if (multiplier < 10) {
+        // We're at the last digit. Check for rounding.
+        if (i + 1 < v.length()
+            && v.charAt(i + 1) >= '5') {
+          ++r;
+        }
+        break;
+      }
+      multiplier /= 10;
+    }
+    return r;
+  }
+
   public static long timestampStringToUnixDate(String s) {
     final long d;
     final long t;

http://git-wip-us.apache.org/repos/asf/calcite/blob/4ba9d1b2/avatica/core/src/test/java/org/apache/calcite/avatica/util/DateTimeUtilsTest.java
----------------------------------------------------------------------
diff --git 
a/avatica/core/src/test/java/org/apache/calcite/avatica/util/DateTimeUtilsTest.java
 
b/avatica/core/src/test/java/org/apache/calcite/avatica/util/DateTimeUtilsTest.java
index efc4df3..1ac1a90 100644
--- 
a/avatica/core/src/test/java/org/apache/calcite/avatica/util/DateTimeUtilsTest.java
+++ 
b/avatica/core/src/test/java/org/apache/calcite/avatica/util/DateTimeUtilsTest.java
@@ -145,8 +145,14 @@ public class DateTimeUtilsTest {
   }
 
   @Test public void testTimeToString() {
-    checkTimeString("00:00:00", 0);
-    checkTimeString("23:59:59", 86400000 - 1000);
+    checkTimeString("00:00:00", 0, 0);
+    checkTimeString("23:59:59", 0, 86400000 - 1000);
+    checkTimeString("23:59:59.1", 1, 86400000 - 1000 + 100);
+    checkTimeString("23:59:59.01", 2, 86400000 - 1000 + 10);
+    checkTimeString("23:59:59.1234", 3, 86400000 - 1000 + 123);
+    checkTimeString("23:59:59.1236", 3, 86400000 - 1000 + 124);
+    checkTimeString("23:59:59.123456789012345678901234567890", 3,
+        86400000 - 1000 + 123);
   }
 
   @Test public void testTimestampExtract() {
@@ -183,19 +189,28 @@ public class DateTimeUtilsTest {
     assertThat(unixTimeExtract(TimeUnitRange.SECOND, 86399999), is(59));
   }
 
-  private void checkTimeString(String s, int d) {
-    assertThat(unixTimeToString(d), is(s));
+  private void checkTimeString(String s, int p, int d) {
+    int digitsAfterPoint = s.indexOf('.') >= 0
+        ? s.length() - s.indexOf('.') - 1
+        : 0;
+    if (digitsAfterPoint == p) {
+      assertThat(unixTimeToString(d, p), is(s));
+    }
     assertThat(timeStringToUnixDate(s), is(d));
   }
 
   @Test public void testTimestampToString() {
     // ISO format would be "1970-01-01T00:00:00" but SQL format is different
-    checkTimestampString("1970-01-01 00:00:00", 0L);
-    checkTimestampString("1970-02-01 23:59:59", 86400000L * 32L - 1000L);
+    checkTimestampString("1970-01-01 00:00:00", 0, 0L);
+    checkTimestampString("1970-02-01 23:59:59", 0, 86400000L * 32L - 1000L);
+    checkTimestampString("1970-02-01 23:59:59.123", 3,
+        86400000L * 32L - 1000L + 123);
+    checkTimestampString("1970-02-01 23:59:59.04", 2,
+        86400000L * 32L - 1000L + 40);
   }
 
-  private void checkTimestampString(String s, long d) {
-    assertThat(unixTimestampToString(d), is(s));
+  private void checkTimestampString(String s, int p, long d) {
+    assertThat(unixTimestampToString(d, p), is(s));
     assertThat(timestampStringToUnixDate(s), is(d));
   }
 

http://git-wip-us.apache.org/repos/asf/calcite/blob/4ba9d1b2/core/src/test/resources/sql/misc.iq
----------------------------------------------------------------------
diff --git a/core/src/test/resources/sql/misc.iq 
b/core/src/test/resources/sql/misc.iq
index 93da331..2f1f959 100644
--- a/core/src/test/resources/sql/misc.iq
+++ b/core/src/test/resources/sql/misc.iq
@@ -1884,4 +1884,22 @@ EnumerableCalc(expr#0=[{inputs}], expr#1=[2016-02-26 
19:06:00.123], expr#2=[2016
   EnumerableValues(tuples=[[{ 0 }]])
 !plan
 
+# [CALCITE-1664] CAST('<string>' as TIMESTAMP) adds part of sub-second 
fraction to the value
+# The following output is incorrect (i.e. demonstrates the bug), but will 
become
+# correct when we upgrade to Avatica-1.10 (where the bug is fixed).
+select
+  TIMESTAMP '2016-02-26 19:06:00.12345678',
+  CAST('2016-02-26 19:06:00.12345678' as TIMESTAMP),
+  TIMESTAMPDIFF(SECOND,
+    TIMESTAMP '2016-02-26 19:06:00.123456789',
+    CAST('2016-02-26 19:06:00.123456789' as TIMESTAMP));
++---------------------+---------------------+--------+
+| EXPR$0              | EXPR$1              | EXPR$2 |
++---------------------+---------------------+--------+
+| 2016-02-26 19:06:00 | 2016-02-26 22:31:46 | 123456 |
++---------------------+---------------------+--------+
+(1 row)
+
+!ok
+
 # End misc.iq

Reply via email to