diff --git a/presto-base-jdbc/src/main/java/io/prestosql/plugin/jdbc/StandardColumnMappings.java b/presto-base-jdbc/src/main/java/io/prestosql/plugin/jdbc/StandardColumnMappings.java index bb1b44813e9f..f006fd4aebff 100644 --- a/presto-base-jdbc/src/main/java/io/prestosql/plugin/jdbc/StandardColumnMappings.java +++ b/presto-base-jdbc/src/main/java/io/prestosql/plugin/jdbc/StandardColumnMappings.java @@ -19,6 +19,7 @@ import io.prestosql.spi.type.CharType; import io.prestosql.spi.type.DecimalType; import io.prestosql.spi.type.Decimals; +import io.prestosql.spi.type.TimestampType; import io.prestosql.spi.type.VarcharType; import org.joda.time.DateTimeZone; import org.joda.time.chrono.ISOChronology; @@ -55,9 +56,11 @@ import static io.prestosql.spi.type.RealType.REAL; import static io.prestosql.spi.type.SmallintType.SMALLINT; import static io.prestosql.spi.type.TimeType.TIME; +import static io.prestosql.spi.type.TimestampType.MAX_SHORT_PRECISION; import static io.prestosql.spi.type.TimestampType.TIMESTAMP_MILLIS; -import static io.prestosql.spi.type.Timestamps.MICROSECONDS_PER_MILLISECOND; +import static io.prestosql.spi.type.Timestamps.MICROSECONDS_PER_SECOND; import static io.prestosql.spi.type.Timestamps.NANOSECONDS_PER_DAY; +import static io.prestosql.spi.type.Timestamps.NANOSECONDS_PER_MICROSECOND; import static io.prestosql.spi.type.Timestamps.NANOSECONDS_PER_MILLISECOND; import static io.prestosql.spi.type.Timestamps.PICOSECONDS_PER_DAY; import static io.prestosql.spi.type.Timestamps.PICOSECONDS_PER_MILLISECOND; @@ -70,6 +73,7 @@ import static java.lang.Float.floatToRawIntBits; import static java.lang.Float.intBitsToFloat; import static java.lang.Math.floorDiv; +import static java.lang.Math.floorMod; import static java.lang.Math.max; import static java.lang.Math.min; import static java.lang.Math.toIntExact; @@ -351,28 +355,38 @@ public static LongWriteFunction timeWriteFunction() * {@link #timestampColumnMapping} instead. */ @Deprecated - public static ColumnMapping timestampColumnMappingUsingSqlTimestamp() + public static ColumnMapping timestampColumnMappingUsingSqlTimestamp(TimestampType timestampType) { + // TODO support higher precision + checkArgument(timestampType.getPrecision() <= MAX_SHORT_PRECISION, "Precision is out of range: %s", timestampType.getPrecision()); return ColumnMapping.longMapping( TIMESTAMP_MILLIS, (resultSet, columnIndex) -> { Timestamp timestamp = resultSet.getTimestamp(columnIndex); - return toPrestoTimestamp(timestamp.toLocalDateTime()); + return toPrestoTimestamp(timestampType, timestamp.toLocalDateTime()); }, - timestampWriteFunctionUsingSqlTimestamp()); + timestampWriteFunctionUsingSqlTimestamp(timestampType)); } + @Deprecated public static ColumnMapping timestampColumnMapping() { + return timestampColumnMapping(TIMESTAMP_MILLIS); + } + + public static ColumnMapping timestampColumnMapping(TimestampType timestampType) + { + checkArgument(timestampType.getPrecision() <= MAX_SHORT_PRECISION, "Precision is out of range: %s", timestampType.getPrecision()); return ColumnMapping.longMapping( - TIMESTAMP_MILLIS, - timestampReadFunction(), - timestampWriteFunction()); + timestampType, + timestampReadFunction(timestampType), + timestampWriteFunction(timestampType)); } - public static LongReadFunction timestampReadFunction() + public static LongReadFunction timestampReadFunction(TimestampType timestampType) { - return (resultSet, columnIndex) -> toPrestoTimestamp(resultSet.getObject(columnIndex, LocalDateTime.class)); + checkArgument(timestampType.getPrecision() <= MAX_SHORT_PRECISION, "Precision is out of range: %s", timestampType.getPrecision()); + return (resultSet, columnIndex) -> toPrestoTimestamp(timestampType, resultSet.getObject(columnIndex, LocalDateTime.class)); } /** @@ -382,24 +396,32 @@ public static LongReadFunction timestampReadFunction() * {@link #timestampWriteFunction} instead. */ @Deprecated - public static LongWriteFunction timestampWriteFunctionUsingSqlTimestamp() + public static LongWriteFunction timestampWriteFunctionUsingSqlTimestamp(TimestampType timestampType) { + checkArgument(timestampType.getPrecision() <= MAX_SHORT_PRECISION, "Precision is out of range: %s", timestampType.getPrecision()); return (statement, index, value) -> statement.setTimestamp(index, Timestamp.valueOf(fromPrestoTimestamp(value))); } - public static LongWriteFunction timestampWriteFunction() + public static LongWriteFunction timestampWriteFunction(TimestampType timestampType) { + checkArgument(timestampType.getPrecision() <= MAX_SHORT_PRECISION, "Precision is out of range: %s", timestampType.getPrecision()); return (statement, index, value) -> statement.setObject(index, fromPrestoTimestamp(value)); } - public static long toPrestoTimestamp(LocalDateTime localDateTime) + public static long toPrestoTimestamp(TimestampType timestampType, LocalDateTime localDateTime) { - return localDateTime.atZone(UTC).toInstant().toEpochMilli() * MICROSECONDS_PER_MILLISECOND; + long precision = timestampType.getPrecision(); + checkArgument(precision <= MAX_SHORT_PRECISION, "Precision is out of range: %s", precision); + Instant instant = localDateTime.atZone(UTC).toInstant(); + return instant.getEpochSecond() * MICROSECONDS_PER_SECOND + roundDiv(instant.getNano(), NANOSECONDS_PER_MICROSECOND); } - public static LocalDateTime fromPrestoTimestamp(long value) + public static LocalDateTime fromPrestoTimestamp(long epochMicros) { - return Instant.ofEpochMilli(floorDiv(value, MICROSECONDS_PER_MILLISECOND)).atZone(UTC).toLocalDateTime(); + long epochSecond = floorDiv(epochMicros, MICROSECONDS_PER_SECOND); + int nanoFraction = floorMod(epochMicros, MICROSECONDS_PER_SECOND) * NANOSECONDS_PER_MICROSECOND; + Instant instant = Instant.ofEpochSecond(epochSecond, nanoFraction); + return LocalDateTime.ofInstant(instant, UTC); } public static LocalTime fromPrestoTime(long value) @@ -473,7 +495,7 @@ public static Optional jdbcTypeToPrestoType(JdbcTypeHandle type) case Types.TIMESTAMP: // TODO default to `timestampColumnMapping` - return Optional.of(timestampColumnMappingUsingSqlTimestamp()); + return Optional.of(timestampColumnMappingUsingSqlTimestamp(TIMESTAMP_MILLIS)); } return Optional.empty(); } diff --git a/presto-mysql/src/main/java/io/prestosql/plugin/mysql/MySqlClient.java b/presto-mysql/src/main/java/io/prestosql/plugin/mysql/MySqlClient.java index a203deab83d8..1909bc3f6bae 100644 --- a/presto-mysql/src/main/java/io/prestosql/plugin/mysql/MySqlClient.java +++ b/presto-mysql/src/main/java/io/prestosql/plugin/mysql/MySqlClient.java @@ -262,7 +262,7 @@ public WriteMapping toWriteMapping(ConnectorSession session, Type type) } if (TIMESTAMP_MILLIS.equals(type)) { // TODO use `timestampWriteFunction` - return WriteMapping.longMapping("datetime", timestampWriteFunctionUsingSqlTimestamp()); + return WriteMapping.longMapping("datetime", timestampWriteFunctionUsingSqlTimestamp(TIMESTAMP_MILLIS)); } if (VARBINARY.equals(type)) { return WriteMapping.sliceMapping("mediumblob", varbinaryWriteFunction()); diff --git a/presto-postgresql/src/main/java/io/prestosql/plugin/postgresql/PostgreSqlClient.java b/presto-postgresql/src/main/java/io/prestosql/plugin/postgresql/PostgreSqlClient.java index 19789cd147f6..39c600a1f2a3 100644 --- a/presto-postgresql/src/main/java/io/prestosql/plugin/postgresql/PostgreSqlClient.java +++ b/presto-postgresql/src/main/java/io/prestosql/plugin/postgresql/PostgreSqlClient.java @@ -63,6 +63,7 @@ import io.prestosql.spi.type.LongTimestampWithTimeZone; import io.prestosql.spi.type.MapType; import io.prestosql.spi.type.StandardTypes; +import io.prestosql.spi.type.TimestampType; import io.prestosql.spi.type.TimestampWithTimeZoneType; import io.prestosql.spi.type.TinyintType; import io.prestosql.spi.type.Type; @@ -134,7 +135,7 @@ import static io.prestosql.spi.type.StandardTypes.JSON; import static io.prestosql.spi.type.TimeType.TIME; import static io.prestosql.spi.type.TimeZoneKey.UTC_KEY; -import static io.prestosql.spi.type.TimestampType.TIMESTAMP_MILLIS; +import static io.prestosql.spi.type.TimestampType.createTimestampType; import static io.prestosql.spi.type.TimestampWithTimeZoneType.createTimestampWithTimeZoneType; import static io.prestosql.spi.type.Timestamps.MILLISECONDS_PER_SECOND; import static io.prestosql.spi.type.Timestamps.NANOSECONDS_PER_MILLISECOND; @@ -360,10 +361,11 @@ public Optional toPrestoType(ConnectorSession session, Connection return Optional.of(timeColumnMappingWithTruncation()); } if (typeHandle.getJdbcType() == Types.TIMESTAMP) { + TimestampType timestampType = createTimestampType(typeHandle.getDecimalDigits()); return Optional.of(ColumnMapping.longMapping( - TIMESTAMP_MILLIS, - timestampReadFunction(), - timestampWriteFunction())); + timestampType, + timestampReadFunction(timestampType), + timestampWriteFunction(timestampType))); } if (typeHandle.getJdbcType() == Types.NUMERIC && getDecimalRounding(session) == ALLOW_OVERFLOW) { if (typeHandle.getColumnSize() == 131089) { @@ -439,8 +441,9 @@ public WriteMapping toWriteMapping(ConnectorSession session, Type type) if (TIME.equals(type)) { return WriteMapping.longMapping("time", timeWriteFunction()); } - if (TIMESTAMP_MILLIS.equals(type)) { - return WriteMapping.longMapping("timestamp", timestampWriteFunction()); + if (type instanceof TimestampType && ((TimestampType) type).getPrecision() <= MAX_SUPPORTED_TIMESTAMP_PRECISION) { + TimestampType timestampType = (TimestampType) type; + return WriteMapping.longMapping(format("timestamp(%s)", timestampType.getPrecision()), timestampWriteFunction(timestampType)); } if (type instanceof TimestampWithTimeZoneType && ((TimestampWithTimeZoneType) type).getPrecision() <= MAX_SUPPORTED_TIMESTAMP_PRECISION) { int precision = ((TimestampWithTimeZoneType) type).getPrecision(); @@ -496,7 +499,7 @@ public boolean isLimitGuaranteed(ConnectorSession session) // When writing with setObject() using LocalDateTime, driver converts the value to string representing date-time in JVM zone, // therefore cannot represent local date-time which is a "gap" in this zone. // TODO replace this method with StandardColumnMappings#timestampWriteFunction when https://github.com/pgjdbc/pgjdbc/issues/1390 is done - private static LongWriteFunction timestampWriteFunction() + private static LongWriteFunction timestampWriteFunction(TimestampType timestampType) { return (statement, index, value) -> { LocalDateTime localDateTime = fromPrestoTimestamp(value); diff --git a/presto-postgresql/src/main/java/io/prestosql/plugin/postgresql/TypeUtils.java b/presto-postgresql/src/main/java/io/prestosql/plugin/postgresql/TypeUtils.java index d82f13b83ddf..47d9f013ec2f 100644 --- a/presto-postgresql/src/main/java/io/prestosql/plugin/postgresql/TypeUtils.java +++ b/presto-postgresql/src/main/java/io/prestosql/plugin/postgresql/TypeUtils.java @@ -23,6 +23,7 @@ import io.prestosql.spi.type.CharType; import io.prestosql.spi.type.DecimalType; import io.prestosql.spi.type.LongTimestampWithTimeZone; +import io.prestosql.spi.type.TimestampType; import io.prestosql.spi.type.TimestampWithTimeZoneType; import io.prestosql.spi.type.Type; import io.prestosql.spi.type.VarcharType; @@ -53,7 +54,6 @@ import static io.prestosql.spi.type.RealType.REAL; import static io.prestosql.spi.type.SmallintType.SMALLINT; import static io.prestosql.spi.type.TimeZoneKey.UTC_KEY; -import static io.prestosql.spi.type.TimestampType.TIMESTAMP_MILLIS; import static io.prestosql.spi.type.Timestamps.MILLISECONDS_PER_SECOND; import static io.prestosql.spi.type.Timestamps.NANOSECONDS_PER_MILLISECOND; import static io.prestosql.spi.type.Timestamps.PICOSECONDS_PER_NANOSECOND; @@ -92,6 +92,12 @@ static String getArrayElementPgTypeName(ConnectorSession session, PostgreSqlClie return "timestamptz"; } + // TypeInfoCache#getPGArrayType doesn't allow for specifying variable-length limits. + // Map all timestamp(x) types to unparametrized one which defaults to highest precision + if (elementType instanceof TimestampType) { + return "timestamp"; + } + if (elementType instanceof DecimalType) { return "decimal"; } @@ -189,7 +195,8 @@ private static Object prestoNativeToJdbcObject(ConnectorSession session, Type pr return new Date(UTC.getMillisKeepLocal(DateTimeZone.getDefault(), millis)); } - if (TIMESTAMP_MILLIS.equals(prestoType)) { + if (prestoType instanceof TimestampType && ((TimestampType) prestoType).isShort()) { + TimestampType timestampType = (TimestampType) prestoType; return toPgTimestamp(fromPrestoTimestamp((long) prestoNative)); } diff --git a/presto-postgresql/src/test/java/io/prestosql/plugin/postgresql/TestPostgreSqlTypeMapping.java b/presto-postgresql/src/test/java/io/prestosql/plugin/postgresql/TestPostgreSqlTypeMapping.java index a5b1743aa2d0..34ecb9baf4dd 100644 --- a/presto-postgresql/src/test/java/io/prestosql/plugin/postgresql/TestPostgreSqlTypeMapping.java +++ b/presto-postgresql/src/test/java/io/prestosql/plugin/postgresql/TestPostgreSqlTypeMapping.java @@ -685,8 +685,8 @@ public void testArrayEmptyOrNulls() .addRoundTrip(arrayDataType(booleanDataType()), null) .addRoundTrip(arrayDataType(realDataType()), singletonList(null)) .addRoundTrip(arrayDataType(integerDataType()), asList(1, null, 3, null)) - .addRoundTrip(arrayDataType(timestampDataType()), asList()) - .addRoundTrip(arrayDataType(timestampDataType()), singletonList(null)) + .addRoundTrip(arrayDataType(timestampDataType(3)), asList()) + .addRoundTrip(arrayDataType(timestampDataType(3)), singletonList(null)) .addRoundTrip(arrayDataType(prestoTimestampWithTimeZoneDataType(3)), asList()) .addRoundTrip(arrayDataType(prestoTimestampWithTimeZoneDataType(3)), singletonList(null)) .execute(getQueryRunner(), sessionWithArrayAsArray(), prestoCreateAsSelect(sessionWithArrayAsArray(), "test_array_empty_or_nulls")); @@ -854,7 +854,7 @@ public void testArrayAsJson() DataTypeTest.create() .addRoundTrip(arrayAsJsonDataType("timestamp[]"), null) - .addRoundTrip(arrayAsJsonDataType("timestamp[]"), "[\"2019-01-02 03:04:05.789\"]") + .addRoundTrip(arrayAsJsonDataType("timestamp[]"), "[\"2019-01-02 03:04:05.789000\"]") .addRoundTrip(arrayAsJsonDataType("timestamp[]"), "[null,null]") .execute(getQueryRunner(), session, postgresCreateAndInsert("tpch.test_timestamp_array_as_json")); @@ -1004,24 +1004,36 @@ public void testTime(boolean insertWithPresto, ZoneId sessionZone) @Test(dataProvider = "testTimestampDataProvider") public void testTimestamp(boolean insertWithPresto, ZoneId sessionZone) { - // using two non-JVM zones so that we don't need to worry what Postgres system zone is - DataTypeTest tests = DataTypeTest.create(true) - .addRoundTrip(timestampDataType(), beforeEpoch) - .addRoundTrip(timestampDataType(), afterEpoch) - .addRoundTrip(timestampDataType(), timeDoubledInJvmZone) - .addRoundTrip(timestampDataType(), timeDoubledInVilnius) - .addRoundTrip(timestampDataType(), epoch) // epoch also is a gap in JVM zone - .addRoundTrip(timestampDataType(), timeGapInJvmZone1) - .addRoundTrip(timestampDataType(), timeGapInJvmZone2) - .addRoundTrip(timestampDataType(), timeGapInVilnius) - .addRoundTrip(timestampDataType(), timeGapInKathmandu); + DataTypeTest tests = DataTypeTest.create(true); + + // no need to test gap for multiple precisions as both Presto and PostgreSql JDBC + // uses same representation for all precisions 1-6 + DataType timestampDataType = timestampDataType(3); + tests.addRoundTrip(timestampDataType, beforeEpoch); + tests.addRoundTrip(timestampDataType, afterEpoch); + tests.addRoundTrip(timestampDataType, timeDoubledInJvmZone); + tests.addRoundTrip(timestampDataType, timeDoubledInVilnius); + tests.addRoundTrip(timestampDataType, epoch); // epoch also is a gap in JVM zone + tests.addRoundTrip(timestampDataType, timeGapInJvmZone1); + tests.addRoundTrip(timestampDataType, timeGapInJvmZone2); + tests.addRoundTrip(timestampDataType, timeGapInVilnius); + tests.addRoundTrip(timestampDataType, timeGapInKathmandu); + + // test arbitrary time for all supported precisions + tests.addRoundTrip(timestampDataType(0), LocalDateTime.of(1970, 1, 1, 0, 0, 0)); + tests.addRoundTrip(timestampDataType(1), LocalDateTime.of(1970, 1, 1, 0, 0, 0, 100_000_000)); + tests.addRoundTrip(timestampDataType(2), LocalDateTime.of(1970, 1, 1, 0, 0, 0, 120_000_000)); + tests.addRoundTrip(timestampDataType(3), LocalDateTime.of(1970, 1, 1, 0, 0, 0, 123_000_000)); + tests.addRoundTrip(timestampDataType(4), LocalDateTime.of(1970, 1, 1, 0, 0, 0, 123_400_000)); + tests.addRoundTrip(timestampDataType(5), LocalDateTime.of(1970, 1, 1, 0, 0, 0, 123_450_000)); + tests.addRoundTrip(timestampDataType(6), LocalDateTime.of(1970, 1, 1, 0, 0, 0, 123_456_000)); Session session = Session.builder(getQueryRunner().getDefaultSession()) .setTimeZoneKey(TimeZoneKey.getTimeZoneKey(sessionZone.getId())) .build(); if (insertWithPresto) { - tests.execute(getQueryRunner(), session, prestoCreateAsSelect(session, "test_timestamp")); + tests.execute(getQueryRunner(), session, prestoCreateAsSelect("test_timestamp")); } else { tests.execute(getQueryRunner(), session, postgresCreateAndInsert("tpch.test_timestamp")); @@ -1031,32 +1043,48 @@ public void testTimestamp(boolean insertWithPresto, ZoneId sessionZone) @Test(dataProvider = "testTimestampDataProvider") public void testArrayTimestamp(boolean insertWithPresto, ZoneId sessionZone) { - DataType> dataType; - DataSetup dataSetup; - if (insertWithPresto) { - dataType = arrayDataType(timestampDataType()); - dataSetup = prestoCreateAsSelect(sessionWithArrayAsArray(), "test_array_timestamp"); - } - else { - dataType = arrayDataType(timestampDataType(), "timestamp[]"); - dataSetup = postgresCreateAndInsert("tpch.test_array_timestamp"); - } - DataTypeTest tests = DataTypeTest.create(true) - .addRoundTrip(dataType, asList(beforeEpoch)) - .addRoundTrip(dataType, asList(afterEpoch)) - .addRoundTrip(dataType, asList(timeDoubledInJvmZone)) - .addRoundTrip(dataType, asList(timeDoubledInVilnius)) - .addRoundTrip(dataType, asList(epoch)) - .addRoundTrip(dataType, asList(timeGapInJvmZone1)) - .addRoundTrip(dataType, asList(timeGapInJvmZone2)) - .addRoundTrip(dataType, asList(timeGapInVilnius)) - .addRoundTrip(dataType, asList(timeGapInKathmandu)); + DataTypeTest tests = DataTypeTest.create(true); + // no need to test gap for multiple precisions as both Presto and PostgreSql JDBC + // uses same representation for all precisions 1-6 + DataType> dataType = arrayOfTimestampDataType(3, insertWithPresto); + tests.addRoundTrip(dataType, asList(beforeEpoch)); + tests.addRoundTrip(dataType, asList(afterEpoch)); + tests.addRoundTrip(dataType, asList(timeDoubledInJvmZone)); + tests.addRoundTrip(dataType, asList(timeDoubledInVilnius)); + tests.addRoundTrip(dataType, asList(epoch)); + tests.addRoundTrip(dataType, asList(timeGapInJvmZone1)); + tests.addRoundTrip(dataType, asList(timeGapInJvmZone2)); + tests.addRoundTrip(dataType, asList(timeGapInVilnius)); + tests.addRoundTrip(dataType, asList(timeGapInKathmandu)); + + // test arbitrary time for all supported precisions + tests.addRoundTrip(arrayOfTimestampDataType(1, insertWithPresto), asList(LocalDateTime.of(1970, 1, 1, 1, 1, 1, 100_000_000))); + tests.addRoundTrip(arrayOfTimestampDataType(2, insertWithPresto), asList(LocalDateTime.of(1970, 1, 1, 1, 1, 1, 120_000_000))); + tests.addRoundTrip(arrayOfTimestampDataType(3, insertWithPresto), asList(LocalDateTime.of(1970, 1, 1, 1, 1, 1, 123_000_000))); + tests.addRoundTrip(arrayOfTimestampDataType(4, insertWithPresto), asList(LocalDateTime.of(1970, 1, 1, 1, 1, 1, 123_400_000))); + tests.addRoundTrip(arrayOfTimestampDataType(5, insertWithPresto), asList(LocalDateTime.of(1970, 1, 1, 1, 1, 1, 123_450_000))); + tests.addRoundTrip(arrayOfTimestampDataType(6, insertWithPresto), asList(LocalDateTime.of(1970, 1, 1, 1, 1, 1, 123_456_000))); Session session = Session.builder(sessionWithArrayAsArray()) .setTimeZoneKey(TimeZoneKey.getTimeZoneKey(sessionZone.getId())) .build(); - tests.execute(getQueryRunner(), session, dataSetup); + if (insertWithPresto) { + tests.execute(getQueryRunner(), session, prestoCreateAsSelect(sessionWithArrayAsArray(), "test_array_timestamp")); + } + else { + tests.execute(getQueryRunner(), session, postgresCreateAndInsert("tpch.test_array_timestamp")); + } + } + + private DataType> arrayOfTimestampDataType(int precision, boolean insertWithPresto) + { + if (insertWithPresto) { + return arrayDataType(timestampDataType(precision)); + } + else { + return arrayDataType(timestampDataType(precision), format("timestamp(%d)[]", precision)); + } } @DataProvider diff --git a/presto-testing/src/main/java/io/prestosql/testing/datatype/DataType.java b/presto-testing/src/main/java/io/prestosql/testing/datatype/DataType.java index 11ecb5b0cfce..e9ff181a58da 100644 --- a/presto-testing/src/main/java/io/prestosql/testing/datatype/DataType.java +++ b/presto-testing/src/main/java/io/prestosql/testing/datatype/DataType.java @@ -33,6 +33,7 @@ import java.util.Optional; import java.util.function.Function; +import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.io.BaseEncoding.base16; import static io.prestosql.spi.type.CharType.createCharType; import static io.prestosql.spi.type.Chars.padSpaces; @@ -40,6 +41,7 @@ import static io.prestosql.spi.type.DecimalType.createDecimalType; import static io.prestosql.spi.type.TimeType.TIME; import static io.prestosql.spi.type.TimestampType.TIMESTAMP_MILLIS; +import static io.prestosql.spi.type.TimestampType.createTimestampType; import static io.prestosql.spi.type.VarcharType.createUnboundedVarcharType; import static io.prestosql.type.JsonType.JSON; import static java.lang.String.format; @@ -194,6 +196,25 @@ public static DataType timestampDataType() identity()); } + public static DataType timestampDataType(int precision) + { + // This code does not support precision > 9, due to limitations of DateTimeFormatter. For now it is not needed as + // none of currently supported JDBC databases supports precision over 9. + checkArgument(precision >= 0 && precision <= 9, "Unsupported precision: %s", precision); + DateTimeFormatter dateTimeFormatter; + if (precision == 0) { + dateTimeFormatter = DateTimeFormatter.ofPattern("'TIMESTAMP '''yyyy-MM-dd HH:mm:ss''"); + } + else { + dateTimeFormatter = DateTimeFormatter.ofPattern(format("'TIMESTAMP '''yyyy-MM-dd HH:mm:ss.%s''", "n".repeat(precision))); + } + return dataType( + format("timestamp(%s)", precision), + createTimestampType(precision), + dateTimeFormatter::format, + identity()); + } + public static DataType jsonDataType() { return dataType(