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