From bd41fe1823cc787b37aaeafe45196df23b211dba Mon Sep 17 00:00:00 2001 From: Piotr Findeisen Date: Sat, 23 Jun 2018 23:07:53 +0200 Subject: [PATCH] Accept values with zone in varchar to timestamp cast --- .../presto/testing/DateTimeTestingUtils.java | 13 ++++++ .../facebook/presto/util/DateTimeUtils.java | 33 ++++++++++++-- .../facebook/presto/type/TestTimestamp.java | 45 +++++++++++++++++++ 3 files changed, 87 insertions(+), 4 deletions(-) diff --git a/presto-main/src/main/java/com/facebook/presto/testing/DateTimeTestingUtils.java b/presto-main/src/main/java/com/facebook/presto/testing/DateTimeTestingUtils.java index 7af29df1c2c0..f5fe2da27332 100644 --- a/presto-main/src/main/java/com/facebook/presto/testing/DateTimeTestingUtils.java +++ b/presto-main/src/main/java/com/facebook/presto/testing/DateTimeTestingUtils.java @@ -21,6 +21,11 @@ import org.joda.time.DateTime; import org.joda.time.DateTimeZone; +import java.time.LocalDateTime; + +import static java.util.concurrent.TimeUnit.DAYS; +import static java.util.concurrent.TimeUnit.NANOSECONDS; + public final class DateTimeTestingUtils { private DateTimeTestingUtils() {} @@ -60,6 +65,14 @@ public static SqlTimestamp sqlTimestampOf( } } + /** + * Constructs standard (non-legacy) TIMESTAMP value corresponding to argument + */ + public static SqlTimestamp sqlTimestampOf(LocalDateTime dateTime) + { + return new SqlTimestamp(DAYS.toMillis(dateTime.toLocalDate().toEpochDay()) + NANOSECONDS.toMillis(dateTime.toLocalTime().toNanoOfDay())); + } + public static SqlTimestamp sqlTimestampOf(DateTime dateTime, Session session) { return sqlTimestampOf(dateTime, session.toConnectorSession()); diff --git a/presto-main/src/main/java/com/facebook/presto/util/DateTimeUtils.java b/presto-main/src/main/java/com/facebook/presto/util/DateTimeUtils.java index ff705f67fac5..43c9289231dd 100644 --- a/presto-main/src/main/java/com/facebook/presto/util/DateTimeUtils.java +++ b/presto-main/src/main/java/com/facebook/presto/util/DateTimeUtils.java @@ -21,6 +21,7 @@ import org.joda.time.DateTime; import org.joda.time.DateTimeZone; import org.joda.time.DurationFieldType; +import org.joda.time.LocalDateTime; import org.joda.time.MutablePeriod; import org.joda.time.Period; import org.joda.time.ReadWritablePeriod; @@ -35,6 +36,9 @@ import org.joda.time.format.PeriodFormatterBuilder; import org.joda.time.format.PeriodParser; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; import java.util.Locale; @@ -123,6 +127,20 @@ public static String printDate(int days) .withOffsetParsed(); } + /** {@link LocalDateTime#getLocalMillis()} */ + private static final MethodHandle getLocalMillis; + + static { + try { + Method getLocalMillisMethod = LocalDateTime.class.getDeclaredMethod("getLocalMillis"); + getLocalMillisMethod.setAccessible(true); + getLocalMillis = MethodHandles.lookup().unreflect(getLocalMillisMethod); + } + catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + } + /** * Parse a string (optionally containing a zone) as a value of either TIMESTAMP or TIMESTAMP WITH TIME ZONE type. *

@@ -139,7 +157,7 @@ public static long parseTimestampLiteral(String value) return packDateTimeWithZone(dateTime); } catch (Exception e) { - return parseTimestampWithoutTimeZone(value); + return TIMESTAMP_WITHOUT_TIME_ZONE_FORMATTER.parseMillis(value); } } @@ -178,16 +196,23 @@ public static long parseTimestampWithTimeZone(TimeZoneKey timeZoneKey, String ti } /** - * Parse a string (without a zone) as a value of TIMESTAMP type. + * Parse a string (optionally containing a zone) as a value of TIMESTAMP type. + * If the string specifies a zone, the zone is discarded. *

* For example: {@code "2000-01-01 01:23:00"} is parsed to TIMESTAMP {@code 2000-01-01T01:23:00} - * and {@code "2000-01-01 01:23:00 +01:23"} is rejected. + * and {@code "2000-01-01 01:23:00 +01:23"} is also parsed to TIMESTAMP {@code 2000-01-01T01:23:00.000}. * * @return stack representation of TIMESTAMP type */ public static long parseTimestampWithoutTimeZone(String value) { - return TIMESTAMP_WITHOUT_TIME_ZONE_FORMATTER.parseMillis(value); + LocalDateTime localDateTime = TIMESTAMP_WITH_OR_WITHOUT_TIME_ZONE_FORMATTER.parseLocalDateTime(value); + try { + return (long) getLocalMillis.invokeExact(localDateTime); + } + catch (Throwable e) { + throw new RuntimeException(e); + } } /** diff --git a/presto-main/src/test/java/com/facebook/presto/type/TestTimestamp.java b/presto-main/src/test/java/com/facebook/presto/type/TestTimestamp.java index 4d184081f1d9..8121944f7800 100644 --- a/presto-main/src/test/java/com/facebook/presto/type/TestTimestamp.java +++ b/presto-main/src/test/java/com/facebook/presto/type/TestTimestamp.java @@ -13,6 +13,13 @@ */ package com.facebook.presto.type; +import org.testng.annotations.Test; + +import java.time.LocalDateTime; + +import static com.facebook.presto.spi.type.TimestampType.TIMESTAMP; +import static com.facebook.presto.testing.DateTimeTestingUtils.sqlTimestampOf; + public class TestTimestamp extends TestTimestampBase { @@ -20,4 +27,42 @@ public TestTimestamp() { super(false); } + + @Test + public void testCastFromVarcharContainingTimeZone() + { + assertFunction( + "cast('2001-1-22 03:04:05.321 +07:09' as timestamp)", + TIMESTAMP, + sqlTimestampOf(LocalDateTime.of(2001, 1, 22, 3, 4, 5, 321_000_000))); + assertFunction( + "cast('2001-1-22 03:04:05 +07:09' as timestamp)", + TIMESTAMP, + sqlTimestampOf(LocalDateTime.of(2001, 1, 22, 3, 4, 5))); + assertFunction( + "cast('2001-1-22 03:04 +07:09' as timestamp)", + TIMESTAMP, + sqlTimestampOf(LocalDateTime.of(2001, 1, 22, 3, 4, 0))); + assertFunction( + "cast('2001-1-22 +07:09' as timestamp)", + TIMESTAMP, + sqlTimestampOf(LocalDateTime.of(2001, 1, 22, 0, 0, 0))); + + assertFunction( + "cast('2001-1-22 03:04:05.321 Asia/Oral' as timestamp)", + TIMESTAMP, + sqlTimestampOf(LocalDateTime.of(2001, 1, 22, 3, 4, 5, 321_000_000))); + assertFunction( + "cast('2001-1-22 03:04:05 Asia/Oral' as timestamp)", + TIMESTAMP, + sqlTimestampOf(LocalDateTime.of(2001, 1, 22, 3, 4, 5))); + assertFunction( + "cast('2001-1-22 03:04 Asia/Oral' as timestamp)", + TIMESTAMP, + sqlTimestampOf(LocalDateTime.of(2001, 1, 22, 3, 4, 0))); + assertFunction( + "cast('2001-1-22 Asia/Oral' as timestamp)", + TIMESTAMP, + sqlTimestampOf(LocalDateTime.of(2001, 1, 22, 0, 0, 0))); + } }