diff --git a/presto-main/src/main/java/io/prestosql/operator/scalar/timestamp/VarcharToTimestampCast.java b/presto-main/src/main/java/io/prestosql/operator/scalar/timestamp/VarcharToTimestampCast.java index d39af8fc218f..c4a3b118b37e 100644 --- a/presto-main/src/main/java/io/prestosql/operator/scalar/timestamp/VarcharToTimestampCast.java +++ b/presto-main/src/main/java/io/prestosql/operator/scalar/timestamp/VarcharToTimestampCast.java @@ -23,6 +23,7 @@ import io.prestosql.spi.type.LongTimestamp; import io.prestosql.type.DateTimes; +import java.time.DateTimeException; import java.time.ZonedDateTime; import java.util.regex.Matcher; @@ -74,7 +75,7 @@ public static long castToShortTimestamp(int precision, String value) Matcher matcher = DateTimes.DATETIME_PATTERN.matcher(value); if (!matcher.matches()) { - throw new IllegalArgumentException("Invalid timestamp: " + value); + throw new PrestoException(INVALID_CAST_ARGUMENT, "Value cannot be cast to timestamp: " + value); } String year = matcher.group("year"); @@ -85,16 +86,22 @@ public static long castToShortTimestamp(int precision, String value) String second = matcher.group("second"); String fraction = matcher.group("fraction"); - long epochSecond = ZonedDateTime.of( - Integer.parseInt(year), - Integer.parseInt(month), - Integer.parseInt(day), - hour == null ? 0 : Integer.parseInt(hour), - minute == null ? 0 : Integer.parseInt(minute), - second == null ? 0 : Integer.parseInt(second), - 0, - UTC) - .toEpochSecond(); + long epochSecond; + try { + epochSecond = ZonedDateTime.of( + Integer.parseInt(year), + Integer.parseInt(month), + Integer.parseInt(day), + hour == null ? 0 : Integer.parseInt(hour), + minute == null ? 0 : Integer.parseInt(minute), + second == null ? 0 : Integer.parseInt(second), + 0, + UTC) + .toEpochSecond(); + } + catch (DateTimeException e) { + throw new PrestoException(INVALID_CAST_ARGUMENT, "Value cannot be cast to timestamp: " + value, e); + } int actualPrecision = 0; long fractionValue = 0; @@ -118,7 +125,7 @@ public static LongTimestamp castToLongTimestamp(int precision, String value) Matcher matcher = DateTimes.DATETIME_PATTERN.matcher(value); if (!matcher.matches()) { - throw new IllegalArgumentException("Invalid timestamp: " + value); + throw new PrestoException(INVALID_CAST_ARGUMENT, "Value cannot be cast to timestamp: " + value); } String year = matcher.group("year"); @@ -129,16 +136,22 @@ public static LongTimestamp castToLongTimestamp(int precision, String value) String second = matcher.group("second"); String fraction = matcher.group("fraction"); - long epochSecond = ZonedDateTime.of( - Integer.parseInt(year), - Integer.parseInt(month), - Integer.parseInt(day), - hour == null ? 0 : Integer.parseInt(hour), - minute == null ? 0 : Integer.parseInt(minute), - second == null ? 0 : Integer.parseInt(second), - 0, - UTC) - .toEpochSecond(); + long epochSecond; + try { + epochSecond = ZonedDateTime.of( + Integer.parseInt(year), + Integer.parseInt(month), + Integer.parseInt(day), + hour == null ? 0 : Integer.parseInt(hour), + minute == null ? 0 : Integer.parseInt(minute), + second == null ? 0 : Integer.parseInt(second), + 0, + UTC) + .toEpochSecond(); + } + catch (DateTimeException e) { + throw new PrestoException(INVALID_CAST_ARGUMENT, "Value cannot be cast to timestamp: " + value, e); + } int actualPrecision = 0; long fractionValue = 0; diff --git a/presto-main/src/main/java/io/prestosql/operator/scalar/timestamptz/VarcharToTimestampWithTimeZoneCast.java b/presto-main/src/main/java/io/prestosql/operator/scalar/timestamptz/VarcharToTimestampWithTimeZoneCast.java index d98294d7a846..3a3988798bcc 100644 --- a/presto-main/src/main/java/io/prestosql/operator/scalar/timestamptz/VarcharToTimestampWithTimeZoneCast.java +++ b/presto-main/src/main/java/io/prestosql/operator/scalar/timestamptz/VarcharToTimestampWithTimeZoneCast.java @@ -23,6 +23,7 @@ import io.prestosql.spi.type.LongTimestampWithTimeZone; import io.prestosql.type.DateTimes; +import java.time.DateTimeException; import java.time.ZoneId; import java.time.ZonedDateTime; import java.util.function.Function; @@ -86,7 +87,7 @@ private static long toShort(int precision, String value, Function assertions.expression("CAST('ABC' AS TIMESTAMP)")) + .hasMessage("Value cannot be cast to timestamp: ABC"); + assertThatThrownBy(() -> assertions.expression("CAST('2022-01-00 00:00:00' AS TIMESTAMP)")) + .hasMessage("Value cannot be cast to timestamp: 2022-01-00 00:00:00"); + assertThatThrownBy(() -> assertions.expression("CAST('2022-00-01 00:00:00' AS TIMESTAMP)")) + .hasMessage("Value cannot be cast to timestamp: 2022-00-01 00:00:00"); + assertThatThrownBy(() -> assertions.expression("CAST('2022-01-01 25:00:00' AS TIMESTAMP)")) + .hasMessage("Value cannot be cast to timestamp: 2022-01-01 25:00:00"); + assertThatThrownBy(() -> assertions.expression("CAST('2022-01-01 00:61:00' AS TIMESTAMP)")) + .hasMessage("Value cannot be cast to timestamp: 2022-01-01 00:61:00"); + assertThatThrownBy(() -> assertions.expression("CAST('2022-01-01 00:00:61' AS TIMESTAMP)")) + .hasMessage("Value cannot be cast to timestamp: 2022-01-01 00:00:61"); + + assertThatThrownBy(() -> assertions.expression("CAST('ABC' AS TIMESTAMP(12))")) + .hasMessage("Value cannot be cast to timestamp: ABC"); + assertThatThrownBy(() -> assertions.expression("CAST('2022-01-00 00:00:00' AS TIMESTAMP(12))")) + .hasMessage("Value cannot be cast to timestamp: 2022-01-00 00:00:00"); + assertThatThrownBy(() -> assertions.expression("CAST('2022-00-01 00:00:00' AS TIMESTAMP(12))")) + .hasMessage("Value cannot be cast to timestamp: 2022-00-01 00:00:00"); + assertThatThrownBy(() -> assertions.expression("CAST('2022-01-01 25:00:00' AS TIMESTAMP(12))")) + .hasMessage("Value cannot be cast to timestamp: 2022-01-01 25:00:00"); + assertThatThrownBy(() -> assertions.expression("CAST('2022-01-01 00:61:00' AS TIMESTAMP(12))")) + .hasMessage("Value cannot be cast to timestamp: 2022-01-01 00:61:00"); + assertThatThrownBy(() -> assertions.expression("CAST('2022-01-01 00:00:61' AS TIMESTAMP(12))")) + .hasMessage("Value cannot be cast to timestamp: 2022-01-01 00:00:61"); + } + private static BiFunction timestamp(int precision, int year, int month, int day, int hour, int minute, int second, long picoOfSecond) { return (session, queryRunner) -> { diff --git a/presto-main/src/test/java/io/prestosql/operator/scalar/timestamptz/TestTimestampWithTimeZone.java b/presto-main/src/test/java/io/prestosql/operator/scalar/timestamptz/TestTimestampWithTimeZone.java index 277da08a92e5..90ad6c3b95e7 100644 --- a/presto-main/src/test/java/io/prestosql/operator/scalar/timestamptz/TestTimestampWithTimeZone.java +++ b/presto-main/src/test/java/io/prestosql/operator/scalar/timestamptz/TestTimestampWithTimeZone.java @@ -2424,6 +2424,40 @@ public void testJoin() .matches("VALUES BIGINT '1'"); } + @Test + public void testCastInvalidTimestamp() + { + assertThatThrownBy(() -> assertions.expression("CAST('ABC' AS TIMESTAMP WITH TIME ZONE)")) + .hasMessage("Value cannot be cast to timestamp: ABC"); + assertThatThrownBy(() -> assertions.expression("CAST('2022-01-00 00:00:00 UTC' AS TIMESTAMP WITH TIME ZONE)")) + .hasMessage("Value cannot be cast to timestamp: 2022-01-00 00:00:00 UTC"); + assertThatThrownBy(() -> assertions.expression("CAST('2022-00-01 00:00:00 UTC' AS TIMESTAMP WITH TIME ZONE)")) + .hasMessage("Value cannot be cast to timestamp: 2022-00-01 00:00:00 UTC"); + assertThatThrownBy(() -> assertions.expression("CAST('2022-01-01 25:00:00 UTC' AS TIMESTAMP WITH TIME ZONE)")) + .hasMessage("Value cannot be cast to timestamp: 2022-01-01 25:00:00 UTC"); + assertThatThrownBy(() -> assertions.expression("CAST('2022-01-01 00:61:00 UTC' AS TIMESTAMP WITH TIME ZONE)")) + .hasMessage("Value cannot be cast to timestamp: 2022-01-01 00:61:00 UTC"); + assertThatThrownBy(() -> assertions.expression("CAST('2022-01-01 00:00:61 UTC' AS TIMESTAMP WITH TIME ZONE)")) + .hasMessage("Value cannot be cast to timestamp: 2022-01-01 00:00:61 UTC"); + assertThatThrownBy(() -> assertions.expression("CAST('2022-01-01 00:00:00 ABC' AS TIMESTAMP WITH TIME ZONE)")) + .hasMessage("Value cannot be cast to timestamp: 2022-01-01 00:00:00 ABC"); + + assertThatThrownBy(() -> assertions.expression("CAST('ABC' AS TIMESTAMP(12))")) + .hasMessage("Value cannot be cast to timestamp: ABC"); + assertThatThrownBy(() -> assertions.expression("CAST('2022-01-00 00:00:00 UTC' AS TIMESTAMP(12) WITH TIME ZONE)")) + .hasMessage("Value cannot be cast to timestamp: 2022-01-00 00:00:00 UTC"); + assertThatThrownBy(() -> assertions.expression("CAST('2022-00-01 00:00:00 UTC' AS TIMESTAMP(12) WITH TIME ZONE)")) + .hasMessage("Value cannot be cast to timestamp: 2022-00-01 00:00:00 UTC"); + assertThatThrownBy(() -> assertions.expression("CAST('2022-01-01 25:00:00 UTC' AS TIMESTAMP(12) WITH TIME ZONE)")) + .hasMessage("Value cannot be cast to timestamp: 2022-01-01 25:00:00 UTC"); + assertThatThrownBy(() -> assertions.expression("CAST('2022-01-01 00:61:00 UTC' AS TIMESTAMP(12) WITH TIME ZONE)")) + .hasMessage("Value cannot be cast to timestamp: 2022-01-01 00:61:00 UTC"); + assertThatThrownBy(() -> assertions.expression("CAST('2022-01-01 00:00:61 UTC' AS TIMESTAMP(12) WITH TIME ZONE)")) + .hasMessage("Value cannot be cast to timestamp: 2022-01-01 00:00:61 UTC"); + assertThatThrownBy(() -> assertions.expression("CAST('2022-01-01 00:00:00 ABC' AS TIMESTAMP(12) WITH TIME ZONE)")) + .hasMessage("Value cannot be cast to timestamp: 2022-01-01 00:00:00 ABC"); + } + private BiFunction timestampWithTimeZone(int precision, int year, int month, int day, int hour, int minute, int second, long picoOfSecond, TimeZoneKey timeZoneKey) { return (session, queryRunner) -> {