diff --git a/plugin/trino-mongodb/src/main/java/io/trino/plugin/mongodb/MongoPageSink.java b/plugin/trino-mongodb/src/main/java/io/trino/plugin/mongodb/MongoPageSink.java index afbb7bc84f41..ffd9cd8e324a 100644 --- a/plugin/trino-mongodb/src/main/java/io/trino/plugin/mongodb/MongoPageSink.java +++ b/plugin/trino-mongodb/src/main/java/io/trino/plugin/mongodb/MongoPageSink.java @@ -47,15 +47,17 @@ import org.bson.types.Binary; import org.bson.types.ObjectId; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; import java.util.ArrayList; import java.util.Collection; -import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.TimeUnit; import static io.trino.plugin.mongodb.ObjectIdType.OBJECT_ID; import static io.trino.plugin.mongodb.TypeUtils.isArrayType; @@ -68,11 +70,12 @@ import static io.trino.spi.type.Decimals.readBigDecimal; import static io.trino.spi.type.TimestampType.TIMESTAMP_MILLIS; import static io.trino.spi.type.Timestamps.MICROSECONDS_PER_MILLISECOND; -import static io.trino.spi.type.Timestamps.PICOSECONDS_PER_MILLISECOND; +import static io.trino.spi.type.Timestamps.PICOSECONDS_PER_NANOSECOND; import static io.trino.spi.type.Timestamps.roundDiv; import static java.lang.Float.intBitsToFloat; import static java.lang.Math.floorDiv; import static java.lang.Math.toIntExact; +import static java.time.ZoneOffset.UTC; import static java.util.Collections.unmodifiableList; import static java.util.Collections.unmodifiableMap; import static java.util.Objects.requireNonNull; @@ -162,19 +165,21 @@ private Object getObjectValue(Type type, Block block, int position) } if (type.equals(DateType.DATE)) { long days = type.getLong(block, position); - return new Date(TimeUnit.DAYS.toMillis(days)); + return LocalDate.ofEpochDay(days); } if (type.equals(TimeType.TIME)) { long picos = type.getLong(block, position); - return new Date(roundDiv(picos, PICOSECONDS_PER_MILLISECOND)); + return LocalTime.ofNanoOfDay(roundDiv(picos, PICOSECONDS_PER_NANOSECOND)); } if (type.equals(TIMESTAMP_MILLIS)) { long millisUtc = floorDiv(type.getLong(block, position), MICROSECONDS_PER_MILLISECOND); - return new Date(millisUtc); + Instant instant = Instant.ofEpochMilli(millisUtc); + return LocalDateTime.ofInstant(instant, UTC); } if (type.equals(TimestampWithTimeZoneType.TIMESTAMP_TZ_MILLIS)) { long millisUtc = unpackMillisUtc(type.getLong(block, position)); - return new Date(millisUtc); + Instant instant = Instant.ofEpochMilli(millisUtc); + return LocalDateTime.ofInstant(instant, UTC); } if (type instanceof DecimalType) { return readBigDecimal((DecimalType) type, block, position); diff --git a/plugin/trino-mongodb/src/main/java/io/trino/plugin/mongodb/MongoSession.java b/plugin/trino-mongodb/src/main/java/io/trino/plugin/mongodb/MongoSession.java index f43530a1a14c..553d7592d697 100644 --- a/plugin/trino-mongodb/src/main/java/io/trino/plugin/mongodb/MongoSession.java +++ b/plugin/trino-mongodb/src/main/java/io/trino/plugin/mongodb/MongoSession.java @@ -57,6 +57,10 @@ import org.bson.types.Binary; import org.bson.types.ObjectId; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; @@ -78,15 +82,21 @@ import static io.trino.spi.HostAddress.fromParts; import static io.trino.spi.type.BigintType.BIGINT; import static io.trino.spi.type.BooleanType.BOOLEAN; +import static io.trino.spi.type.DateType.DATE; import static io.trino.spi.type.DoubleType.DOUBLE; import static io.trino.spi.type.SmallintType.SMALLINT; +import static io.trino.spi.type.TimeType.TIME_MILLIS; import static io.trino.spi.type.TimestampType.TIMESTAMP_MILLIS; +import static io.trino.spi.type.TimestampWithTimeZoneType.TIMESTAMP_TZ_MILLIS; +import static io.trino.spi.type.Timestamps.PICOSECONDS_PER_NANOSECOND; +import static io.trino.spi.type.Timestamps.roundDiv; import static io.trino.spi.type.TinyintType.TINYINT; import static io.trino.spi.type.VarbinaryType.VARBINARY; import static io.trino.spi.type.VarcharType.VARCHAR; import static io.trino.spi.type.VarcharType.createUnboundedVarcharType; import static java.lang.Math.toIntExact; import static java.lang.String.format; +import static java.time.ZoneOffset.UTC; import static java.util.Locale.ENGLISH; import static java.util.Objects.requireNonNull; import static java.util.concurrent.TimeUnit.MINUTES; @@ -516,6 +526,28 @@ private static Optional translateValue(Object trinoNativeValue, Type typ return Optional.of(((Slice) trinoNativeValue).toStringUtf8()); } + if (type == DATE) { + long days = (long) trinoNativeValue; + return Optional.of(LocalDate.ofEpochDay(days)); + } + + if (type == TIME_MILLIS) { + long picos = (long) trinoNativeValue; + return Optional.of(LocalTime.ofNanoOfDay(roundDiv(picos, PICOSECONDS_PER_NANOSECOND))); + } + + if (type == TIMESTAMP_MILLIS) { + long millisUtc = (long) trinoNativeValue; + Instant instant = Instant.ofEpochMilli(millisUtc); + return Optional.of(LocalDateTime.ofInstant(instant, UTC)); + } + + if (type == TIMESTAMP_TZ_MILLIS) { + long millisUtc = (long) trinoNativeValue; + Instant instant = Instant.ofEpochMilli(millisUtc); + return Optional.of(LocalDateTime.ofInstant(instant, UTC)); + } + return Optional.empty(); } diff --git a/plugin/trino-mongodb/src/test/java/io/trino/plugin/mongodb/TestMongoTypeMapping.java b/plugin/trino-mongodb/src/test/java/io/trino/plugin/mongodb/TestMongoTypeMapping.java index bf255ce86fc7..6ebaec807490 100644 --- a/plugin/trino-mongodb/src/test/java/io/trino/plugin/mongodb/TestMongoTypeMapping.java +++ b/plugin/trino-mongodb/src/test/java/io/trino/plugin/mongodb/TestMongoTypeMapping.java @@ -287,6 +287,58 @@ public void testDate(ZoneId sessionZone) .execute(getQueryRunner(), session, trinoCreateAndInsert("test_date")); } + @Test(dataProvider = "sessionZonesDataProvider") + public void testTimestamp(ZoneId sessionZone) + { + Session session = Session.builder(getSession()) + .setTimeZoneKey(TimeZoneKey.getTimeZoneKey(sessionZone.getId())) + .build(); + + SqlDataTypeTest.create() + .addRoundTrip("timestamp '-290307-12-31 23:59:59.999'", "timestamp '-290307-12-31 23:59:59.999'") // min value + .addRoundTrip("timestamp '1582-10-04 23:59:59.999'", "timestamp '1582-10-04 23:59:59.999'") // before julian->gregorian switch + .addRoundTrip("timestamp '1582-10-05 00:00:00.000'", "timestamp '1582-10-05 00:00:00.000'") // begin julian->gregorian switch + .addRoundTrip("timestamp '1582-10-14 23:59:59.999'", "timestamp '1582-10-14 23:59:59.999'") // end julian->gregorian switch + .addRoundTrip("timestamp '1970-01-01 00:00:00.000'", "timestamp '1970-01-01 00:00:00.000'") // epoch + .addRoundTrip("timestamp '1986-01-01 00:13:07.123'", "timestamp '1986-01-01 00:13:07.123'") // time gap in Kathmandu + .addRoundTrip("timestamp '2018-03-25 03:17:17.123'", "timestamp '2018-03-25 03:17:17.123'") // time gap in Vilnius + .addRoundTrip("timestamp '2018-10-28 01:33:17.456'", "timestamp '2018-10-28 01:33:17.456'") // time doubled in JVM zone + .addRoundTrip("timestamp '2018-10-28 03:33:33.333'", "timestamp '2018-10-28 03:33:33.333'") // time double in Vilnius + .addRoundTrip("timestamp '294247-01-10 04:00:54.775'", "timestamp '294247-01-10 04:00:54.775'") // max value + + .execute(getQueryRunner(), session, trinoCreateAsSelect(session, "test_timestamp")) + .execute(getQueryRunner(), session, trinoCreateAsSelect("test_timestamp")) + .execute(getQueryRunner(), session, trinoCreateAndInsert(session, "test_timestamp")) + .execute(getQueryRunner(), session, trinoCreateAndInsert("test_timestamp")); + } + + @Test(dataProvider = "sessionZonesDataProvider") + public void testTimestampWithTimeZoneMapping(ZoneId sessionZone) + { + Session session = Session.builder(getSession()) + .setTimeZoneKey(TimeZoneKey.getTimeZoneKey(sessionZone.getId())) + .build(); + + SqlDataTypeTest.create() + .addRoundTrip("timestamp '-69387-04-22 03:45:14.752 UTC'", "timestamp '-69387-04-22 03:45:14.752 UTC'") // min value + .addRoundTrip("timestamp '1582-10-04 23:59:59.999 UTC'", "timestamp '1582-10-04 23:59:59.999 UTC'") // before julian->gregorian switch + .addRoundTrip("timestamp '1582-10-05 00:00:00.000 UTC'", "timestamp '1582-10-05 00:00:00.000 UTC'") // begin julian->gregorian switch + .addRoundTrip("timestamp '1582-10-14 23:59:59.999 UTC'", "timestamp '1582-10-14 23:59:59.999 UTC'") // end julian->gregorian switch + .addRoundTrip("timestamp '1970-01-01 00:00:00.000 UTC'", "timestamp '1970-01-01 00:00:00.000 UTC'") // epoch + .addRoundTrip("timestamp '1986-01-01 00:13:07.123 UTC'", "timestamp '1986-01-01 00:13:07.123 UTC'") // time gap in Kathmandu + .addRoundTrip("timestamp '2018-03-25 03:17:17.123 UTC'", "timestamp '2018-03-25 03:17:17.123 UTC'") // time gap in Vilnius + .addRoundTrip("timestamp '2018-10-28 01:33:17.456 UTC'", "timestamp '2018-10-28 01:33:17.456 UTC'") // time doubled in JVM zone + .addRoundTrip("timestamp '2018-10-28 03:33:33.333 UTC'", "timestamp '2018-10-28 03:33:33.333 UTC'") // time double in Vilnius + .addRoundTrip("timestamp '2022-10-01 22:30:00.000 -01:30'", "timestamp '2022-10-02 00:00:00.000 UTC'") // not UTC (-01:30) + .addRoundTrip("timestamp '2022-10-02 01:30:00.000 +01:30'", "timestamp '2022-10-02 00:00:00.000 UTC'") // not UTC (+01:30) + .addRoundTrip("timestamp '73326-09-11 20:14:45.247 UTC'", "timestamp '73326-09-11 20:14:45.247 UTC'") // max value + + .execute(getQueryRunner(), session, trinoCreateAsSelect(session, "test_timestamp_with_time_zone")) + .execute(getQueryRunner(), session, trinoCreateAsSelect("test_timestamp_with_time_zone")) + .execute(getQueryRunner(), session, trinoCreateAndInsert(session, "test_timestamp_with_time_zone")) + .execute(getQueryRunner(), session, trinoCreateAndInsert("test_timestamp_with_time_zone")); + } + @Test public void testArray() {