5959import static io .trino .testing .TestingSession .testSessionBuilder ;
6060import static io .trino .type .IpAddressType .IPADDRESS ;
6161import static java .lang .String .format ;
62- import static java .time .ZoneOffset .UTC ;
6362import static org .junit .jupiter .api .TestInstance .Lifecycle .PER_CLASS ;
6463
6564@ TestInstance (PER_CLASS )
6665public abstract class BaseClickHouseTypeMapping
6766 extends AbstractTestQueryFramework
6867{
68+ private static final ZoneId UTC = ZoneId .of ("UTC" );
6969 private static final ZoneId JVM_ZONE = ZoneId .systemDefault ();
7070 // no DST in 1970, but has DST in later years (e.g. 2018)
7171 private static final ZoneId VILNIUS = ZoneId .of ("Europe/Vilnius" );
@@ -1018,6 +1018,30 @@ public void testUnsupportedTimestamp(String unsupportedTimestamp)
10181018 }
10191019 }
10201020
1021+ @ Test
1022+ void testUnsupportedDateTimeWithTimeZone ()
1023+ {
1024+ for (ZoneId zoneId : timezones ()) {
1025+ String inputType = DATETIME_TYPE_FACTORY .apply (zoneId );
1026+ testUnsupportedDateTimeWithTimeZone (inputType , "1969-12-31 23:59:59 UTC" , "1969-12-31 23:59:59" ); // min - 1 second
1027+ testUnsupportedDateTimeWithTimeZone (inputType , "2106-02-07 06:28:16 UTC" , "2106-02-07 06:28:16" ); // max + 1 second
1028+ testUnsupportedDateTimeWithTimeZone (inputType , "1970-01-01 00:00:00 Asia/Kathmandu" , "1969-12-31 18:30:00" );
1029+ testUnsupportedDateTimeWithTimeZone (inputType , "1970-01-01 00:13:42 Asia/Kathmandu" , "1969-12-31 18:43:42" );
1030+ }
1031+ }
1032+
1033+ private void testUnsupportedDateTimeWithTimeZone (String inputType , String unsupportedTimestampWithTz , String unsupportedTimestampUtc )
1034+ {
1035+ String minSupportedTimestamp = "1970-01-01 00:00:00" ;
1036+ String maxSupportedTimestamp = "2106-02-07 06:28:15" ;
1037+
1038+ try (TestTable table = new TestTable (onRemoteDatabase (), "tpch.test_unsupported_timestamp_with_tz" , "(dt %s) ENGINE=Log" .formatted (inputType ))) {
1039+ assertQueryFails (
1040+ "INSERT INTO %s VALUES (TIMESTAMP '%s')" .formatted (table .getName (), unsupportedTimestampWithTz ),
1041+ "Timestamp must be between %s and %s in ClickHouse: %s" .formatted (minSupportedTimestamp , maxSupportedTimestamp , unsupportedTimestampUtc ));
1042+ }
1043+ }
1044+
10211045 @ Test
10221046 public void testClickHouseDateTimeWithTimeZone ()
10231047 {
@@ -1039,39 +1063,38 @@ public void testClickHouseDateTimeWithTimeZone()
10391063
10401064 private SqlDataTypeTest dateTimeWithTimeZoneTest (Function <ZoneId , String > inputTypeFactory )
10411065 {
1042- ZoneId utc = ZoneId .of ("UTC" );
10431066 SqlDataTypeTest tests = SqlDataTypeTest .create ()
1044- .addRoundTrip (format ("Nullable(%s)" , inputTypeFactory .apply (utc )), "NULL" , TIMESTAMP_TZ_SECONDS , "CAST(NULL AS TIMESTAMP(0) WITH TIME ZONE)" )
1067+ .addRoundTrip (format ("Nullable(%s)" , inputTypeFactory .apply (UTC )), "NULL" , TIMESTAMP_TZ_SECONDS , "CAST(NULL AS TIMESTAMP(0) WITH TIME ZONE)" )
10451068
10461069 // Since ClickHouse datetime(timezone) does not support values before epoch, we do not test this here.
10471070
10481071 // epoch
1049- .addRoundTrip (inputTypeFactory .apply (utc ), "0" , TIMESTAMP_TZ_SECONDS , "TIMESTAMP '1970-01-01 00:00:00 Z'" )
1050- .addRoundTrip (inputTypeFactory .apply (utc ), "'1970-01-01 00:00:00'" , TIMESTAMP_TZ_SECONDS , "TIMESTAMP '1970-01-01 00:00:00 Z'" )
1072+ .addRoundTrip (inputTypeFactory .apply (UTC ), "0" , TIMESTAMP_TZ_SECONDS , "TIMESTAMP '1970-01-01 00:00:00 Z'" )
1073+ .addRoundTrip (inputTypeFactory .apply (UTC ), "'1970-01-01 00:00:00'" , TIMESTAMP_TZ_SECONDS , "TIMESTAMP '1970-01-01 00:00:00 Z'" )
1074+ // DateTime supports the range [1970-01-01 00:00:00, 2106-02-07 06:28:15]
1075+ // Values outside this range gets stored incorrectly in ClickHouse.
1076+ // For example, 1970-01-01 00:00:00 in Asia/Kathmandu could be stored as 1970-01-01 05:30:00
10511077 .addRoundTrip (inputTypeFactory .apply (KATHMANDU ), "'1970-01-01 00:00:00'" , TIMESTAMP_TZ_SECONDS , "TIMESTAMP '1970-01-01 05:30:00 +05:30'" )
10521078
10531079 // after epoch
1054- .addRoundTrip (inputTypeFactory .apply (utc ), "'2019-03-18 10:01:17'" , TIMESTAMP_TZ_SECONDS , "TIMESTAMP '2019-03-18 10:01:17 Z'" )
1080+ .addRoundTrip (inputTypeFactory .apply (UTC ), "'2019-03-18 10:01:17'" , TIMESTAMP_TZ_SECONDS , "TIMESTAMP '2019-03-18 10:01:17 Z'" )
10551081 .addRoundTrip (inputTypeFactory .apply (KATHMANDU ), "'2019-03-18 10:01:17'" , TIMESTAMP_TZ_SECONDS , "TIMESTAMP '2019-03-18 10:01:17 +05:45'" )
10561082 .addRoundTrip (inputTypeFactory .apply (ZoneId .of ("GMT" )), "'2019-03-18 10:01:17'" , TIMESTAMP_TZ_SECONDS , "TIMESTAMP '2019-03-18 10:01:17 Z'" )
10571083 .addRoundTrip (inputTypeFactory .apply (ZoneId .of ("UTC+00:00" )), "'2019-03-18 10:01:17'" , TIMESTAMP_TZ_SECONDS , "TIMESTAMP '2019-03-18 10:01:17 Z'" )
10581084
10591085 // time doubled in JVM zone
1060- .addRoundTrip (inputTypeFactory .apply (utc ), "'2018-10-28 01:33:17'" , TIMESTAMP_TZ_SECONDS , "TIMESTAMP '2018-10-28 01:33:17 Z'" )
1086+ .addRoundTrip (inputTypeFactory .apply (UTC ), "'2018-10-28 01:33:17'" , TIMESTAMP_TZ_SECONDS , "TIMESTAMP '2018-10-28 01:33:17 Z'" )
10611087 .addRoundTrip (inputTypeFactory .apply (JVM_ZONE ), "'2018-10-28 01:33:17'" , TIMESTAMP_TZ_SECONDS , "TIMESTAMP '2018-10-28 01:33:17 -05:00'" )
10621088 .addRoundTrip (inputTypeFactory .apply (KATHMANDU ), "'2018-10-28 01:33:17'" , TIMESTAMP_TZ_SECONDS , "TIMESTAMP '2018-10-28 01:33:17 +05:45'" )
10631089
10641090 // time doubled in Vilnius
1065- .addRoundTrip (inputTypeFactory .apply (utc ), "'2018-10-28 03:33:33'" , TIMESTAMP_TZ_SECONDS , "TIMESTAMP '2018-10-28 03:33:33 Z'" )
1091+ .addRoundTrip (inputTypeFactory .apply (UTC ), "'2018-10-28 03:33:33'" , TIMESTAMP_TZ_SECONDS , "TIMESTAMP '2018-10-28 03:33:33 Z'" )
10661092 .addRoundTrip (inputTypeFactory .apply (VILNIUS ), "'2018-10-28 03:33:33'" , TIMESTAMP_TZ_SECONDS , "TIMESTAMP '2018-10-28 03:33:33 +03:00'" )
10671093 .addRoundTrip (inputTypeFactory .apply (KATHMANDU ), "'2018-10-28 03:33:33'" , TIMESTAMP_TZ_SECONDS , "TIMESTAMP '2018-10-28 03:33:33 +05:45'" )
10681094
10691095 // time gap in JVM zone
1070- .addRoundTrip (inputTypeFactory .apply (utc ), "'1970-01-01 00:13:42'" , TIMESTAMP_TZ_SECONDS , "TIMESTAMP '1970-01-01 00:13:42 Z'" )
1071- // TODO: Check the range of DateTime(timezone) values written from Trino to ClickHouse to prevent ClickHouse from storing incorrect results.
1072- // e.g. 1970-01-01 00:13:42 will become 1970-01-01 05:30:00
1073- // .addRoundTrip(inputTypeFactory.apply(kathmandu), "'1970-01-01 00:13:42'", TIMESTAMP_TZ_SECONDS, "TIMESTAMP '1970-01-01 00:13:42 +05:30'")
1074- .addRoundTrip (inputTypeFactory .apply (utc ), "'2018-04-01 02:13:55'" , TIMESTAMP_TZ_SECONDS , "TIMESTAMP '2018-04-01 02:13:55 Z'" )
1096+ .addRoundTrip (inputTypeFactory .apply (UTC ), "'1970-01-01 00:13:42'" , TIMESTAMP_TZ_SECONDS , "TIMESTAMP '1970-01-01 00:13:42 Z'" )
1097+ .addRoundTrip (inputTypeFactory .apply (UTC ), "'2018-04-01 02:13:55'" , TIMESTAMP_TZ_SECONDS , "TIMESTAMP '2018-04-01 02:13:55 Z'" )
10751098 .addRoundTrip (inputTypeFactory .apply (KATHMANDU ), "'2018-04-01 02:13:55'" , TIMESTAMP_TZ_SECONDS , "TIMESTAMP '2018-04-01 02:13:55 +05:45'" )
10761099
10771100 // time gap in Vilnius
0 commit comments