diff --git a/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/StandardColumnMappings.java b/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/StandardColumnMappings.java index 576d7088514f..875fb8da6259 100644 --- a/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/StandardColumnMappings.java +++ b/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/StandardColumnMappings.java @@ -434,27 +434,30 @@ public static LongWriteFunction timeWriteFunction(int precision) /** * @deprecated This method uses {@link java.sql.Timestamp} and the class cannot represent date-time value when JVM zone had * forward offset change (a 'gap'). This includes regular DST changes (e.g. Europe/Warsaw) and one-time policy changes - * (Asia/Kathmandu's shift by 15 minutes on January 1, 1986, 00:00:00). If driver only supports {@link LocalDateTime}, use - * {@link #timestampColumnMapping} instead. + * (Asia/Kathmandu's shift by 15 minutes on January 1, 1986, 00:00:00). This mapping also disables pushdown by default + * to ensure correctness because rounding happens within Trino and won't apply to remote system. + * If driver supports {@link LocalDateTime}, use {@link #timestampColumnMapping} instead. */ @Deprecated - public static ColumnMapping timestampColumnMappingUsingSqlTimestamp(TimestampType timestampType) + public static ColumnMapping timestampColumnMappingUsingSqlTimestampWithRounding(TimestampType timestampType) { // TODO support higher precision checkArgument(timestampType.getPrecision() <= TimestampType.MAX_SHORT_PRECISION, "Precision is out of range: %s", timestampType.getPrecision()); return ColumnMapping.longMapping( timestampType, (resultSet, columnIndex) -> { - Timestamp timestamp = resultSet.getTimestamp(columnIndex); - return toPrestoTimestamp(timestampType, timestamp.toLocalDateTime()); + LocalDateTime localDateTime = resultSet.getTimestamp(columnIndex).toLocalDateTime(); + int roundedNanos = toIntExact(round(localDateTime.getNano(), 9 - timestampType.getPrecision())); + LocalDateTime rounded = localDateTime + .withNano(0) + .plusNanos(roundedNanos); + return toPrestoTimestamp(timestampType, rounded); }, - timestampWriteFunctionUsingSqlTimestamp(timestampType)); - } - - @Deprecated - public static ColumnMapping timestampColumnMapping() - { - return timestampColumnMapping(TIMESTAMP_MILLIS); + timestampWriteFunctionUsingSqlTimestamp(timestampType), + // NOTE: pushdown is disabled because the values stored in remote system might not match the values as + // read by Trino due to rounding. This can lead to incorrect results if operations are pushed down to + // the remote system. + DISABLE_PUSHDOWN); } public static ColumnMapping timestampColumnMapping(TimestampType timestampType) @@ -631,7 +634,7 @@ public static Optional jdbcTypeToPrestoType(JdbcTypeHandle typeHa case Types.TIMESTAMP: // TODO default to `timestampColumnMapping` - return Optional.of(timestampColumnMappingUsingSqlTimestamp(TIMESTAMP_MILLIS)); + return Optional.of(timestampColumnMappingUsingSqlTimestampWithRounding(TIMESTAMP_MILLIS)); } return Optional.empty(); } diff --git a/plugin/trino-mysql/src/main/java/io/trino/plugin/mysql/MySqlClient.java b/plugin/trino-mysql/src/main/java/io/trino/plugin/mysql/MySqlClient.java index bd9345a1694e..5a44d9955ac4 100644 --- a/plugin/trino-mysql/src/main/java/io/trino/plugin/mysql/MySqlClient.java +++ b/plugin/trino-mysql/src/main/java/io/trino/plugin/mysql/MySqlClient.java @@ -310,6 +310,10 @@ public Optional toColumnMapping(ConnectorSession session, Connect case Types.DATE: return Optional.of(dateColumnMapping()); + + case Types.TIMESTAMP: + // TODO support higher precisions (https://github.com/trinodb/trino/issues/6910) + break; // currently handled by the default mappings } // TODO add explicit mappings @@ -355,7 +359,7 @@ public WriteMapping toWriteMapping(ConnectorSession session, Type type) throw new TrinoException(NOT_SUPPORTED, "Unsupported column type: " + type.getDisplayName()); } if (TIMESTAMP_MILLIS.equals(type)) { - // TODO use `timestampWriteFunction` + // TODO use `timestampWriteFunction` (https://github.com/trinodb/trino/issues/6910) return WriteMapping.longMapping("datetime", timestampWriteFunctionUsingSqlTimestamp(TIMESTAMP_MILLIS)); } if (VARBINARY.equals(type)) { diff --git a/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/TestMySqlTypeMapping.java b/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/TestMySqlTypeMapping.java index a10d1dfdd213..2f7344388218 100644 --- a/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/TestMySqlTypeMapping.java +++ b/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/TestMySqlTypeMapping.java @@ -26,6 +26,7 @@ import io.trino.spi.type.VarcharType; import io.trino.testing.AbstractTestQueryFramework; import io.trino.testing.QueryRunner; +import io.trino.testing.TestingSession; import io.trino.testing.datatype.CreateAndInsertDataSetup; import io.trino.testing.datatype.CreateAsSelectDataSetup; import io.trino.testing.datatype.DataSetup; @@ -58,6 +59,7 @@ import static io.trino.plugin.mysql.MySqlQueryRunner.createMySqlQueryRunner; import static io.trino.spi.type.DecimalType.createDecimalType; import static io.trino.spi.type.TimeZoneKey.UTC_KEY; +import static io.trino.spi.type.TimestampType.TIMESTAMP_MILLIS; import static io.trino.spi.type.VarbinaryType.VARBINARY; import static io.trino.spi.type.VarcharType.createUnboundedVarcharType; import static io.trino.spi.type.VarcharType.createVarcharType; @@ -78,6 +80,7 @@ import static java.lang.String.format; import static java.math.RoundingMode.HALF_UP; import static java.math.RoundingMode.UNNECESSARY; +import static java.time.ZoneOffset.UTC; import static java.util.Arrays.asList; import static java.util.function.Function.identity; @@ -478,18 +481,256 @@ public void testDate() } } - @Test - public void testDatetime() + @Test(dataProvider = "sessionZonesDataProvider") + public void testDatetime(ZoneId sessionZone) { - // TODO (https://github.com/trinodb/trino/issues/5449) MySQL datetime is not correctly read (see comment in StandardColumnMappings.timestampColumnMappingUsingSqlTimestamp) - // testing this is hard because of https://github.com/trinodb/trino/issues/37 + Session session = Session.builder(getSession()) + .setTimeZoneKey(TimeZoneKey.getTimeZoneKey(sessionZone.getId())) + .build(); + + SqlDataTypeTest.create() + // before epoch + .addRoundTrip("datetime(3)", "TIMESTAMP '1958-01-01 13:18:03.123'", TIMESTAMP_MILLIS, "TIMESTAMP '1958-01-01 13:18:03.123'") + // after epoch + .addRoundTrip("datetime(3)", "TIMESTAMP '2019-03-18 10:01:17.987'", TIMESTAMP_MILLIS, "TIMESTAMP '2019-03-18 10:01:17.987'") + // time doubled in JVM zone + .addRoundTrip("datetime(3)", "TIMESTAMP '2018-10-28 01:33:17.456'", TIMESTAMP_MILLIS, "TIMESTAMP '2018-10-28 01:33:17.456'") + // time double in Vilnius + .addRoundTrip("datetime(3)", "TIMESTAMP '2018-10-28 03:33:33.333'", TIMESTAMP_MILLIS, "TIMESTAMP '2018-10-28 03:33:33.333'") + // epoch + .addRoundTrip("datetime(3)", "TIMESTAMP '1970-01-01 00:00:00.000'", TIMESTAMP_MILLIS, "TIMESTAMP '1970-01-01 01:00:00.000'") + // TODO time gaps do not round-trip (https://github.com/trinodb/trino/issues/6910) + .addRoundTrip("datetime(3)", "TIMESTAMP '1970-01-01 00:13:42.000'", TIMESTAMP_MILLIS, "TIMESTAMP '1970-01-01 01:13:42.000'") + .addRoundTrip("datetime(3)", "TIMESTAMP '2018-04-01 02:13:55.123'", TIMESTAMP_MILLIS, "TIMESTAMP '2018-04-01 03:13:55.123'") + // time gap in Vilnius + .addRoundTrip("datetime(3)", "TIMESTAMP '2018-03-25 03:17:17.000'", TIMESTAMP_MILLIS, "TIMESTAMP '2018-03-25 03:17:17.000'") + // time gap in Kathmandu + .addRoundTrip("datetime(3)", "TIMESTAMP '1986-01-01 00:13:07.000'", TIMESTAMP_MILLIS, "TIMESTAMP '1986-01-01 00:13:07.000'") + + // same as above but with higher precision + .addRoundTrip("datetime(6)", "TIMESTAMP '1958-01-01 13:18:03.123000'", TIMESTAMP_MILLIS, "TIMESTAMP '1958-01-01 13:18:03.123'") + .addRoundTrip("datetime(6)", "TIMESTAMP '2019-03-18 10:01:17.987000'", TIMESTAMP_MILLIS, "TIMESTAMP '2019-03-18 10:01:17.987'") + .addRoundTrip("datetime(6)", "TIMESTAMP '2018-10-28 01:33:17.456000'", TIMESTAMP_MILLIS, "TIMESTAMP '2018-10-28 01:33:17.456'") + .addRoundTrip("datetime(6)", "TIMESTAMP '2018-10-28 03:33:33.333000'", TIMESTAMP_MILLIS, "TIMESTAMP '2018-10-28 03:33:33.333'") + .addRoundTrip("datetime(6)", "TIMESTAMP '1970-01-01 00:00:00.000000'", TIMESTAMP_MILLIS, "TIMESTAMP '1970-01-01 01:00:00.000'") + // TODO time gaps do not round-trip (https://github.com/trinodb/trino/issues/6910) + .addRoundTrip("datetime(6)", "TIMESTAMP '1970-01-01 00:13:42.000000'", TIMESTAMP_MILLIS, "TIMESTAMP '1970-01-01 01:13:42.000'") + .addRoundTrip("datetime(6)", "TIMESTAMP '2018-04-01 02:13:55.123000'", TIMESTAMP_MILLIS, "TIMESTAMP '2018-04-01 03:13:55.123'") + .addRoundTrip("datetime(6)", "TIMESTAMP '2018-03-25 03:17:17.000000'", TIMESTAMP_MILLIS, "TIMESTAMP '2018-03-25 03:17:17.000'") + .addRoundTrip("datetime(6)", "TIMESTAMP '1986-01-01 00:13:07.000000'", TIMESTAMP_MILLIS, "TIMESTAMP '1986-01-01 00:13:07.000'") + + // test arbitrary time for all supported precisions + .addRoundTrip("datetime(0)", "TIMESTAMP '1970-01-01 00:00:01'", TIMESTAMP_MILLIS, "TIMESTAMP '1970-01-01 01:00:01.000'") + .addRoundTrip("datetime(1)", "TIMESTAMP '1970-01-01 00:00:01.1'", TIMESTAMP_MILLIS, "TIMESTAMP '1970-01-01 01:00:01.100'") + .addRoundTrip("datetime(2)", "TIMESTAMP '1970-01-01 00:00:01.12'", TIMESTAMP_MILLIS, "TIMESTAMP '1970-01-01 01:00:01.120'") + .addRoundTrip("datetime(3)", "TIMESTAMP '1970-01-01 00:00:01.123'", TIMESTAMP_MILLIS, "TIMESTAMP '1970-01-01 01:00:01.123'") + .addRoundTrip("datetime(4)", "TIMESTAMP '1970-01-01 00:00:01.1234'", TIMESTAMP_MILLIS, "TIMESTAMP '1970-01-01 01:00:01.123'") + .addRoundTrip("datetime(5)", "TIMESTAMP '1970-01-01 00:00:01.12345'", TIMESTAMP_MILLIS, "TIMESTAMP '1970-01-01 01:00:01.123'") + .addRoundTrip("datetime(6)", "TIMESTAMP '1970-01-01 00:00:01.123456'", TIMESTAMP_MILLIS, "TIMESTAMP '1970-01-01 01:00:01.123'") + + // test rounding for precisions too high for MySQL + .addRoundTrip("datetime(6)", "TIMESTAMP '1970-01-01 00:00:01.1234560'", TIMESTAMP_MILLIS, "TIMESTAMP '1970-01-01 01:00:01.123'") + .addRoundTrip("datetime(6)", "TIMESTAMP '1970-01-01 00:00:01.12345649999'", TIMESTAMP_MILLIS, "TIMESTAMP '1970-01-01 01:00:01.123'") + .addRoundTrip("datetime(6)", "TIMESTAMP '1970-01-01 00:00:01.1234565'", TIMESTAMP_MILLIS, "TIMESTAMP '1970-01-01 01:00:01.123'") + .addRoundTrip("datetime(6)", "TIMESTAMP '1970-01-01 00:00:01.1234569'", TIMESTAMP_MILLIS, "TIMESTAMP '1970-01-01 01:00:01.123'") + + .execute(getQueryRunner(), session, mysqlCreateAndInsert("tpch.test_datetime")); + + // Note that MySQL treats TIMESTAMP literals as datetime type + SqlDataTypeTest.create() + // TODO support higher precision timestamps (https://github.com/trinodb/trino/issues/6910) + // before epoch with second fraction + //.addRoundTrip("timestamp(7)", "TIMESTAMP '1969-12-31 23:59:59.1230000'", createTimestampType(7), "TIMESTAMP '1969-12-31 23:59:59.1230000'") + //.addRoundTrip("timestamp(7)", "TIMESTAMP '1969-12-31 23:59:59.1234567'", createTimestampType(7), "TIMESTAMP '1969-12-31 23:59:59.1234567'") + + // precision 0 ends up as precision 0 + .addRoundTrip("TIMESTAMP '1970-01-01 00:00:01'", "TIMESTAMP '1970-01-01 01:00:01.000'") + + .addRoundTrip("TIMESTAMP '1970-01-01 00:00:00.1'", "TIMESTAMP '1970-01-01 01:00:00.100'") + .addRoundTrip("TIMESTAMP '1970-01-01 00:00:00.9'", "TIMESTAMP '1970-01-01 01:00:00.900'") + .addRoundTrip("TIMESTAMP '1970-01-01 00:00:00.123'", "TIMESTAMP '1970-01-01 01:00:00.123'") + .addRoundTrip("TIMESTAMP '1970-01-01 00:00:00.123000'", "TIMESTAMP '1970-01-01 01:00:00.123'") + .addRoundTrip("TIMESTAMP '1970-01-01 00:00:00.999'", "TIMESTAMP '1970-01-01 01:00:00.999'") + // max supported precision + .addRoundTrip("TIMESTAMP '1970-01-01 00:00:00.123456'", "TIMESTAMP '1970-01-01 01:00:00.123'") + + .addRoundTrip("TIMESTAMP '2020-09-27 12:34:56.1'", "TIMESTAMP '2020-09-27 12:34:56.100'") + .addRoundTrip("TIMESTAMP '2020-09-27 12:34:56.9'", "TIMESTAMP '2020-09-27 12:34:56.900'") + .addRoundTrip("TIMESTAMP '2020-09-27 12:34:56.123'", "TIMESTAMP '2020-09-27 12:34:56.123'") + .addRoundTrip("TIMESTAMP '2020-09-27 12:34:56.123000'", "TIMESTAMP '2020-09-27 12:34:56.123'") + .addRoundTrip("TIMESTAMP '2020-09-27 12:34:56.999'", "TIMESTAMP '2020-09-27 12:34:56.999'") + // max supported precision + .addRoundTrip("TIMESTAMP '2020-09-27 12:34:56.123456'", "TIMESTAMP '2020-09-27 12:34:56.123'") + + // round down + .addRoundTrip("TIMESTAMP '1970-01-01 00:00:00.123451'", "TIMESTAMP '1970-01-01 01:00:00.123'") + + // nanos round up, end result rounds down + .addRoundTrip("TIMESTAMP '1970-01-01 00:00:00.123499'", "TIMESTAMP '1970-01-01 01:00:00.123'") + .addRoundTrip("TIMESTAMP '1970-01-01 00:00:00.123399'", "TIMESTAMP '1970-01-01 01:00:00.123'") + + // round up + .addRoundTrip("TIMESTAMP '1970-01-01 00:00:00.123999'", "TIMESTAMP '1970-01-01 01:00:00.124'") + + // max precision + .addRoundTrip("TIMESTAMP '1970-01-01 00:00:00.123456'", "TIMESTAMP '1970-01-01 01:00:00.123'") + + // round up to next second + .addRoundTrip("TIMESTAMP '1970-01-01 00:00:00.999999'", "TIMESTAMP '1970-01-01 01:00:01.000'") + + // round up to next day + .addRoundTrip("TIMESTAMP '1970-01-01 23:59:59.999999'", "TIMESTAMP '1970-01-02 00:00:00.000'") + + // negative epoch - round up + .addRoundTrip("TIMESTAMP '1969-12-31 23:59:59.999995'", "TIMESTAMP '1970-01-01 00:00:00.000'") + .addRoundTrip("TIMESTAMP '1969-12-31 23:59:59.999949'", "TIMESTAMP '1970-01-01 00:00:00.000'") + .addRoundTrip("TIMESTAMP '1969-12-31 23:59:59.999994'", "TIMESTAMP '1970-01-01 00:00:00.000'") + + .execute(getQueryRunner(), session, mysqlCreateAndInsert("tpch.test_timestamp")); } - @Test - public void testTimestamp() + @Test(dataProvider = "sessionZonesDataProvider") + public void testTimestamp(ZoneId sessionZone) { - // TODO MySQL timestamp is not correctly read (see comment in StandardColumnMappings.timestampColumnMappingUsingSqlTimestamp) - // testing this is hard because of https://github.com/trinodb/trino/issues/37 + Session session = Session.builder(getSession()) + .setTimeZoneKey(TimeZoneKey.getTimeZoneKey(sessionZone.getId())) + .build(); + + // TODO we are dropping precision during writes because everything is mapped to datetime(0) (https://github.com/trinodb/trino/issues/6909) + SqlDataTypeTest.create() + // before epoch + .addRoundTrip("timestamp(3)", "TIMESTAMP '1958-01-01 13:18:03.123'", TIMESTAMP_MILLIS, "TIMESTAMP '1958-01-01 13:18:03.000'") + // after epoch + .addRoundTrip("timestamp(3)", "TIMESTAMP '2019-03-18 10:01:17.987'", TIMESTAMP_MILLIS, "TIMESTAMP '2019-03-18 10:01:18.000'") + // time doubled in JVM zone + .addRoundTrip("timestamp(3)", "TIMESTAMP '2018-10-28 01:33:17.456'", TIMESTAMP_MILLIS, "TIMESTAMP '2018-10-28 01:33:17.000'") + // time double in Vilnius + .addRoundTrip("timestamp(3)", "TIMESTAMP '2018-10-28 03:33:33.333'", TIMESTAMP_MILLIS, "TIMESTAMP '2018-10-28 03:33:33.000'") + // epoch + .addRoundTrip("timestamp(3)", "TIMESTAMP '1970-01-01 00:00:00.000'", TIMESTAMP_MILLIS, "TIMESTAMP '1970-01-01 01:00:00.000'") + // TODO time gaps do not round-trip (https://github.com/trinodb/trino/issues/6910) + .addRoundTrip("timestamp(3)", "TIMESTAMP '1970-01-01 00:13:42.000'", TIMESTAMP_MILLIS, "TIMESTAMP '1970-01-01 01:13:42.000'") + .addRoundTrip("timestamp(3)", "TIMESTAMP '2018-04-01 02:13:55.123'", TIMESTAMP_MILLIS, "TIMESTAMP '2018-04-01 03:13:55.000'") + // time gap in Vilnius + .addRoundTrip("timestamp(3)", "TIMESTAMP '2018-03-25 03:17:17.000'", TIMESTAMP_MILLIS, "TIMESTAMP '2018-03-25 03:17:17.000'") + // time gap in Kathmandu + .addRoundTrip("timestamp(3)", "TIMESTAMP '1986-01-01 00:13:07.000'", TIMESTAMP_MILLIS, "TIMESTAMP '1986-01-01 00:13:07.000'") + + .execute(getQueryRunner(), session, trinoCreateAsSelect(session, "test_timestamp")) + .execute(getQueryRunner(), session, trinoCreateAsSelect(getSession(), "test_timestamp")) + .execute(getQueryRunner(), session, trinoCreateAndInsert(session, "test_timestamp")); + + // TODO merge this into the above SqlDataTypeTest once we support writing and reading timestamps with precisions other than 3 (TODO https://github.com/trinodb/trino/issues/6910) + // Same as above but with inserts from MySQL - i.e. read path + SqlDataTypeTest.create() + // before epoch (MySQL's timestamp type doesn't support values <= epoch) + //.addRoundTrip("timestamp(3)", "TIMESTAMP '1958-01-01 13:18:03.123'", createTimestampType(3), "TIMESTAMP '1958-01-01 13:18:03.000'") + // after epoch + .addRoundTrip("timestamp(3)", "TIMESTAMP '2019-03-18 10:01:17.987'", TIMESTAMP_MILLIS, "TIMESTAMP '2019-03-18 10:01:17.987'") + // time doubled in JVM zone + .addRoundTrip("timestamp(3)", "TIMESTAMP '2018-10-28 01:33:17.456'", TIMESTAMP_MILLIS, "TIMESTAMP '2018-10-28 01:33:17.456'") + // time double in Vilnius + .addRoundTrip("timestamp(3)", "TIMESTAMP '2018-10-28 03:33:33.333'", TIMESTAMP_MILLIS, "TIMESTAMP '2018-10-28 03:33:33.333'") + // epoch (MySQL's timestamp type doesn't support values <= epoch) + //.addRoundTrip("timestamp(3)", "TIMESTAMP '1970-01-01 00:00:00.000'", createTimestampType(3), "TIMESTAMP '1970-01-01 01:00:00.000'") + // TODO time gaps do not round-trip (https://github.com/trinodb/trino/issues/6910) + .addRoundTrip("timestamp(3)", "TIMESTAMP '1970-01-01 00:13:42.000'", TIMESTAMP_MILLIS, "TIMESTAMP '1970-01-01 01:13:42.000'") + .addRoundTrip("timestamp(3)", "TIMESTAMP '2018-04-01 02:13:55.123'", TIMESTAMP_MILLIS, "TIMESTAMP '2018-04-01 03:13:55.123'") + // time gap in Vilnius + .addRoundTrip("timestamp(3)", "TIMESTAMP '2018-03-25 03:17:17.000'", TIMESTAMP_MILLIS, "TIMESTAMP '2018-03-25 03:17:17.000'") + // time gap in Kathmandu + .addRoundTrip("timestamp(3)", "TIMESTAMP '1986-01-01 00:13:07.000'", TIMESTAMP_MILLIS, "TIMESTAMP '1986-01-01 00:13:07.000'") + + // same as above but with higher precision - note that currently anything other than timestamp(3) can only be inserted directly via MySQL + // MySQL's timestamp type doesn't support values <= epoch + //.addRoundTrip("timestamp(6)", "TIMESTAMP '1958-01-01 13:18:03.123000'", createTimestampType(3), "TIMESTAMP '1958-01-01 13:18:03.123'") + .addRoundTrip("timestamp(6)", "TIMESTAMP '2019-03-18 10:01:17.987000'", TIMESTAMP_MILLIS, "TIMESTAMP '2019-03-18 10:01:17.987'") + .addRoundTrip("timestamp(6)", "TIMESTAMP '2018-10-28 01:33:17.456000'", TIMESTAMP_MILLIS, "TIMESTAMP '2018-10-28 01:33:17.456'") + .addRoundTrip("timestamp(6)", "TIMESTAMP '2018-10-28 03:33:33.333000'", TIMESTAMP_MILLIS, "TIMESTAMP '2018-10-28 03:33:33.333'") + // MySQL's timestamp type doesn't support values <= epoch + //.addRoundTrip("timestamp(6)", "TIMESTAMP '1970-01-01 00:00:00.000000'", createTimestampType(3), "TIMESTAMP '1970-01-01 00:00:00.000'") + // TODO time gaps do not round-trip (https://github.com/trinodb/trino/issues/6910) + .addRoundTrip("timestamp(6)", "TIMESTAMP '1970-01-01 00:13:42.000000'", TIMESTAMP_MILLIS, "TIMESTAMP '1970-01-01 01:13:42.000'") + .addRoundTrip("timestamp(6)", "TIMESTAMP '2018-04-01 02:13:55.123000'", TIMESTAMP_MILLIS, "TIMESTAMP '2018-04-01 03:13:55.123'") + .addRoundTrip("timestamp(6)", "TIMESTAMP '2018-03-25 03:17:17.000000'", TIMESTAMP_MILLIS, "TIMESTAMP '2018-03-25 03:17:17.000'") + .addRoundTrip("timestamp(6)", "TIMESTAMP '1986-01-01 00:13:07.000000'", TIMESTAMP_MILLIS, "TIMESTAMP '1986-01-01 00:13:07.000'") + + // test arbitrary time for all supported precisions + .addRoundTrip("timestamp(0)", "TIMESTAMP '1970-01-01 00:00:01'", TIMESTAMP_MILLIS, "TIMESTAMP '1970-01-01 01:00:01.000'") + .addRoundTrip("timestamp(1)", "TIMESTAMP '1970-01-01 00:00:01.1'", TIMESTAMP_MILLIS, "TIMESTAMP '1970-01-01 01:00:01.100'") + .addRoundTrip("timestamp(2)", "TIMESTAMP '1970-01-01 00:00:01.12'", TIMESTAMP_MILLIS, "TIMESTAMP '1970-01-01 01:00:01.120'") + .addRoundTrip("timestamp(3)", "TIMESTAMP '1970-01-01 00:00:01.123'", TIMESTAMP_MILLIS, "TIMESTAMP '1970-01-01 01:00:01.123'") + .addRoundTrip("timestamp(4)", "TIMESTAMP '1970-01-01 00:00:01.1234'", TIMESTAMP_MILLIS, "TIMESTAMP '1970-01-01 01:00:01.123'") + .addRoundTrip("timestamp(5)", "TIMESTAMP '1970-01-01 00:00:01.12345'", TIMESTAMP_MILLIS, "TIMESTAMP '1970-01-01 01:00:01.123'") + .addRoundTrip("timestamp(6)", "TIMESTAMP '1970-01-01 00:00:01.123456'", TIMESTAMP_MILLIS, "TIMESTAMP '1970-01-01 01:00:01.123'") + .addRoundTrip("timestamp(6)", "TIMESTAMP '1970-01-01 00:00:01.1234560'", TIMESTAMP_MILLIS, "TIMESTAMP '1970-01-01 01:00:01.123'") + .addRoundTrip("timestamp(6)", "TIMESTAMP '1970-01-01 00:00:01.12345649999'", TIMESTAMP_MILLIS, "TIMESTAMP '1970-01-01 01:00:01.123'") + .addRoundTrip("timestamp(6)", "TIMESTAMP '1970-01-01 00:00:01.1234565'", TIMESTAMP_MILLIS, "TIMESTAMP '1970-01-01 01:00:01.123'") + .addRoundTrip("timestamp(6)", "TIMESTAMP '1970-01-01 00:00:01.1234569'", TIMESTAMP_MILLIS, "TIMESTAMP '1970-01-01 01:00:01.123'") + + // TODO support higher precision timestamps (https://github.com/trinodb/trino/issues/6910) + // before epoch with second fraction + //.addRoundTrip("timestamp(7)", "TIMESTAMP '1969-12-31 23:59:59.1230000'", createTimestampType(7), "TIMESTAMP '1969-12-31 23:59:59.1230000'") + //.addRoundTrip("timestamp(7)", "TIMESTAMP '1969-12-31 23:59:59.1234567'", createTimestampType(7), "TIMESTAMP '1969-12-31 23:59:59.1234567'") + + // precision 0 ends up as precision 0 + .addRoundTrip("timestamp(0)", "TIMESTAMP '1970-01-01 00:00:01'", TIMESTAMP_MILLIS, "TIMESTAMP '1970-01-01 01:00:01.000'") + + .addRoundTrip("timestamp(1)", "TIMESTAMP '1970-01-01 00:00:01.1'", TIMESTAMP_MILLIS, "TIMESTAMP '1970-01-01 01:00:01.100'") + .addRoundTrip("timestamp(1)", "TIMESTAMP '1970-01-01 00:00:01.9'", TIMESTAMP_MILLIS, "TIMESTAMP '1970-01-01 01:00:01.900'") + .addRoundTrip("timestamp(3)", "TIMESTAMP '1970-01-01 00:00:01.123'", TIMESTAMP_MILLIS, "TIMESTAMP '1970-01-01 01:00:01.123'") + .addRoundTrip("timestamp(6)", "TIMESTAMP '1970-01-01 00:00:01.123000'", TIMESTAMP_MILLIS, "TIMESTAMP '1970-01-01 01:00:01.123'") + .addRoundTrip("timestamp(3)", "TIMESTAMP '1970-01-01 00:00:01.999'", TIMESTAMP_MILLIS, "TIMESTAMP '1970-01-01 01:00:01.999'") + // max supported precision + .addRoundTrip("timestamp(6)", "TIMESTAMP '1970-01-01 00:00:01.123456'", TIMESTAMP_MILLIS, "TIMESTAMP '1970-01-01 01:00:01.123'") + + .addRoundTrip("timestamp(1)", "TIMESTAMP '2020-09-27 12:34:56.1'", TIMESTAMP_MILLIS, "TIMESTAMP '2020-09-27 12:34:56.100'") + .addRoundTrip("timestamp(1)", "TIMESTAMP '2020-09-27 12:34:56.9'", TIMESTAMP_MILLIS, "TIMESTAMP '2020-09-27 12:34:56.900'") + .addRoundTrip("timestamp(3)", "TIMESTAMP '2020-09-27 12:34:56.123'", TIMESTAMP_MILLIS, "TIMESTAMP '2020-09-27 12:34:56.123'") + .addRoundTrip("timestamp(6)", "TIMESTAMP '2020-09-27 12:34:56.123000'", TIMESTAMP_MILLIS, "TIMESTAMP '2020-09-27 12:34:56.123'") + .addRoundTrip("timestamp(3)", "TIMESTAMP '2020-09-27 12:34:56.999'", TIMESTAMP_MILLIS, "TIMESTAMP '2020-09-27 12:34:56.999'") + // max supported precision + .addRoundTrip("timestamp(6)", "TIMESTAMP '2020-09-27 12:34:56.123456'", TIMESTAMP_MILLIS, "TIMESTAMP '2020-09-27 12:34:56.123'") + + // round down + .addRoundTrip("timestamp(6)", "TIMESTAMP '1970-01-01 00:00:01.12345671'", TIMESTAMP_MILLIS, "TIMESTAMP '1970-01-01 01:00:01.123'") + + // nanos round up, end result rounds down + .addRoundTrip("timestamp(6)", "TIMESTAMP '1970-01-01 00:00:01.1234567499'", TIMESTAMP_MILLIS, "TIMESTAMP '1970-01-01 01:00:01.123'") + .addRoundTrip("timestamp(6)", "TIMESTAMP '1970-01-01 00:00:01.123456749999'", TIMESTAMP_MILLIS, "TIMESTAMP '1970-01-01 01:00:01.123'") + + // round up + .addRoundTrip("timestamp(6)", "TIMESTAMP '1970-01-01 00:00:01.12345675'", TIMESTAMP_MILLIS, "TIMESTAMP '1970-01-01 01:00:01.123'") + + // max precision + .addRoundTrip("timestamp(6)", "TIMESTAMP '1970-01-01 00:00:01.111222333444'", TIMESTAMP_MILLIS, "TIMESTAMP '1970-01-01 01:00:01.111'") + + // round up to next second + .addRoundTrip("timestamp(6)", "TIMESTAMP '1970-01-01 00:00:01.99999995'", TIMESTAMP_MILLIS, "TIMESTAMP '1970-01-01 01:00:02.000'") + + // round up to next day + .addRoundTrip("timestamp(6)", "TIMESTAMP '1970-01-01 23:59:59.99999995'", TIMESTAMP_MILLIS, "TIMESTAMP '1970-01-02 00:00:00.000'") + + // negative epoch (MySQL's timestamp type doesn't support values <= epoch) + //.addRoundTrip("TIMESTAMP '1969-12-31 23:59:59.99999995'", "TIMESTAMP '1970-01-01 00:00:00.000'") + //.addRoundTrip("TIMESTAMP '1969-12-31 23:59:59.999999949999'", "TIMESTAMP '1969-12-31 23:59:59.999'") + //.addRoundTrip("TIMESTAMP '1969-12-31 23:59:59.99999994'", "TIMESTAMP '1969-12-31 23:59:59.999'") + + .execute(getQueryRunner(), session, mysqlCreateAndInsert("tpch.test_timestamp")); + } + + @DataProvider + public Object[][] sessionZonesDataProvider() + { + return new Object[][] { + {UTC}, + {ZoneId.systemDefault()}, + // no DST in 1970, but has DST in later years (e.g. 2018) + {ZoneId.of("Europe/Vilnius")}, + // minutes offset change since 1970-01-01, no DST + {ZoneId.of("Asia/Kathmandu")}, + {ZoneId.of(TestingSession.DEFAULT_TIME_ZONE_KEY.getId())}, + }; } @Test