From e42a351dbb6ab6535124ab686d098e156f213aab Mon Sep 17 00:00:00 2001 From: Piotr Findeisen Date: Tue, 13 Feb 2018 10:14:59 +0100 Subject: [PATCH] Represent TIMESTAMP W/TZ as ZonedDateTime in JDBC --- .../java/io/prestosql/jdbc/ColumnInfo.java | 4 +-- .../io/prestosql/jdbc/PrestoResultSet.java | 33 ++++++++++++------- .../io/prestosql/jdbc/TestJdbcResultSet.java | 21 ++++++------ .../io/prestosql/jdbc/TestPrestoDriver.java | 29 ++++++++-------- 4 files changed, 50 insertions(+), 37 deletions(-) diff --git a/presto-jdbc/src/main/java/io/prestosql/jdbc/ColumnInfo.java b/presto-jdbc/src/main/java/io/prestosql/jdbc/ColumnInfo.java index 14699290a0284..a57a831b541aa 100644 --- a/presto-jdbc/src/main/java/io/prestosql/jdbc/ColumnInfo.java +++ b/presto-jdbc/src/main/java/io/prestosql/jdbc/ColumnInfo.java @@ -227,11 +227,11 @@ private static int getType(ClientTypeSignature type) case "time": return Types.TIME; case "time with time zone": - return Types.TIME; + return Types.TIME; // TODO TIME_WITH_TIME_ZONE case "timestamp": return Types.TIMESTAMP; case "timestamp with time zone": - return Types.TIMESTAMP; + return Types.TIMESTAMP_WITH_TIMEZONE; case "date": return Types.DATE; case "decimal": diff --git a/presto-jdbc/src/main/java/io/prestosql/jdbc/PrestoResultSet.java b/presto-jdbc/src/main/java/io/prestosql/jdbc/PrestoResultSet.java index 77ea0950ffe46..a397abd5abeb7 100644 --- a/presto-jdbc/src/main/java/io/prestosql/jdbc/PrestoResultSet.java +++ b/presto-jdbc/src/main/java/io/prestosql/jdbc/PrestoResultSet.java @@ -23,6 +23,7 @@ import io.prestosql.client.QueryStatusInfo; import io.prestosql.client.StatementClient; import io.prestosql.jdbc.ColumnInfo.Nullable; +import io.prestosql.spi.type.SqlTimestampWithTimeZone; import org.joda.time.DateTimeZone; import org.joda.time.format.DateTimeFormat; import org.joda.time.format.DateTimeFormatter; @@ -51,6 +52,7 @@ import java.sql.Time; import java.sql.Timestamp; import java.sql.Types; +import java.time.ZonedDateTime; import java.util.Calendar; import java.util.HashMap; import java.util.Iterator; @@ -84,15 +86,8 @@ public class PrestoResultSet .toFormatter() .withOffsetParsed(); - static final DateTimeFormatter TIMESTAMP_FORMATTER = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss.SSS"); - static final DateTimeFormatter TIMESTAMP_WITH_TIME_ZONE_FORMATTER = new DateTimeFormatterBuilder() - .append(DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss.SSS ZZZ").getPrinter(), - new DateTimeParser[] { - DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss.SSS Z").getParser(), - DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss.SSS ZZZ").getParser(), - }) - .toFormatter() - .withOffsetParsed(); + static final DateTimeFormatter TIMESTAMP_FORMATTER = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss.SSS"); + static final java.time.format.DateTimeFormatter TIMESTAMP_WITH_TIME_ZONE_FORMATTER = java.time.format.DateTimeFormatter.ofPattern(SqlTimestampWithTimeZone.JSON_FORMAT); private final StatementClient client; private final DateTimeZone sessionTimeZone; @@ -331,16 +326,28 @@ private Timestamp getTimestamp(int columnIndex, DateTimeZone localTimeZone) } } + throw new IllegalArgumentException("Expected column to be a timestamp type but is " + columnInfo.getColumnTypeName()); + } + + private ZonedDateTime getTimestampWithTimeZone(int columnIndex) + throws SQLException + { + Object value = column(columnIndex); + if (value == null) { + return null; + } + + ColumnInfo columnInfo = columnInfo(columnIndex); if (columnInfo.getColumnTypeName().equalsIgnoreCase("timestamp with time zone")) { try { - return new Timestamp(TIMESTAMP_WITH_TIME_ZONE_FORMATTER.parseMillis(String.valueOf(value))); + return TIMESTAMP_WITH_TIME_ZONE_FORMATTER.parse(String.valueOf(value), ZonedDateTime::from); } catch (IllegalArgumentException e) { - throw new SQLException("Invalid timestamp from server: " + value, e); + throw new SQLException("Invalid value from server: " + value, e); } } - throw new IllegalArgumentException("Expected column to be a timestamp type but is " + columnInfo.getColumnTypeName()); + throw new IllegalArgumentException("Expected column to be a timestamp with time zone type but is " + columnInfo.getColumnTypeName()); } @Override @@ -520,6 +527,8 @@ public Object getObject(int columnIndex) return getTime(columnIndex); case Types.TIMESTAMP: return getTimestamp(columnIndex); + case Types.TIMESTAMP_WITH_TIMEZONE: + return getTimestampWithTimeZone(columnIndex); case Types.ARRAY: return getArray(columnIndex); case Types.DECIMAL: diff --git a/presto-jdbc/src/test/java/io/prestosql/jdbc/TestJdbcResultSet.java b/presto-jdbc/src/test/java/io/prestosql/jdbc/TestJdbcResultSet.java index 1cda0653faa73..5e78bc5822c46 100644 --- a/presto-jdbc/src/test/java/io/prestosql/jdbc/TestJdbcResultSet.java +++ b/presto-jdbc/src/test/java/io/prestosql/jdbc/TestJdbcResultSet.java @@ -35,6 +35,8 @@ import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; +import java.time.ZoneId; +import java.time.ZonedDateTime; import static io.prestosql.jdbc.TestPrestoDriver.closeQuietly; import static java.lang.String.format; @@ -183,26 +185,25 @@ public void testObjectTypes() // ... // }); - checkRepresentation("TIMESTAMP '2018-02-13 13:14:15.227 Europe/Warsaw'", Types.TIMESTAMP /* TODO TIMESTAMP_WITH_TIMEZONE */, (rs, column) -> { - assertEquals(rs.getObject(column), Timestamp.valueOf(LocalDateTime.of(2018, 2, 13, 6, 14, 15, 227_000_000))); // TODO this should represent TIMESTAMP '2018-02-13 13:14:15.227 Europe/Warsaw' + checkRepresentation("TIMESTAMP '2018-02-13 13:14:15.227 Europe/Warsaw'", Types.TIMESTAMP_WITH_TIMEZONE, (rs, column) -> { + assertEquals(rs.getObject(column), ZonedDateTime.of(2018, 2, 13, 13, 14, 15, 227_000_000, ZoneId.of("Europe/Warsaw"))); assertThrows(() -> rs.getDate(column)); assertThrows(() -> rs.getTime(column)); - assertEquals(rs.getTimestamp(column), Timestamp.valueOf(LocalDateTime.of(2018, 2, 13, 6, 14, 15, 227_000_000))); // TODO this should fail, or represent TIMESTAMP '2018-02-13 13:14:15.227' + assertThrows(() -> rs.getTimestamp(column)); // this could also return java.sql.Timestamp.valueOf(LocalDateTime.of(2018, 2, 13, 13, 14, 15, 227_000_000)) }); - checkRepresentation("TIMESTAMP '1970-01-01 09:14:15.227 Europe/Warsaw'", Types.TIMESTAMP /* TODO TIMESTAMP_WITH_TIMEZONE */, (rs, column) -> { - assertEquals(rs.getObject(column), Timestamp.valueOf(LocalDateTime.of(1970, 1, 1, 1, 14, 15, 227_000_000))); // TODO this should represent TIMESTAMP '1970-01-01 09:14:15.227 Europe/Warsaw' + checkRepresentation("TIMESTAMP '1970-01-01 09:14:15.227 Europe/Warsaw'", Types.TIMESTAMP_WITH_TIMEZONE, (rs, column) -> { + assertEquals(rs.getObject(column), ZonedDateTime.of(1970, 1, 1, 9, 14, 15, 227_000_000, ZoneId.of("Europe/Warsaw"))); assertThrows(() -> rs.getDate(column)); assertThrows(() -> rs.getTime(column)); - assertEquals(rs.getTimestamp(column), Timestamp.valueOf(LocalDateTime.of(1970, 1, 1, 1, 14, 15, 227_000_000))); // TODO this should fail, or represent TIMESTAMP '1970-01-01 09:14:15.227' + assertThrows(() -> rs.getTimestamp(column)); // this could also return java.sql.Timestamp.valueOf(LocalDateTime.of(1970, 1, 1, 9, 14, 15, 227_000_000)) }); - checkRepresentation("TIMESTAMP '1970-01-01 00:14:15.227 Europe/Warsaw'", Types.TIMESTAMP /* TODO TIMESTAMP_WITH_TIMEZONE */, (rs, column) -> { - assertEquals(rs.getObject(column), Timestamp.valueOf(LocalDateTime.of(1969, 12, 31, 15, 14, 15, 227_000_000))); // TODO this should represent TIMESTAMP '1970-01-01 00:14:15.227 Europe/Warsaw' + checkRepresentation("TIMESTAMP '1970-01-01 00:14:15.227 Europe/Warsaw'", Types.TIMESTAMP_WITH_TIMEZONE, (rs, column) -> { + assertEquals(rs.getObject(column), ZonedDateTime.of(1970, 1, 1, 0, 14, 15, 227_000_000, ZoneId.of("Europe/Warsaw"))); assertThrows(() -> rs.getDate(column)); assertThrows(() -> rs.getTime(column)); - // TODO this should fail, as there no java.sql.Timestamp representation for TIMESTAMP '1970-01-01 00:14:15.227รณ' in America/Bahia_Banderas - assertEquals(rs.getTimestamp(column), Timestamp.valueOf(LocalDateTime.of(1969, 12, 31, 15, 14, 15, 227_000_000))); + assertThrows(() -> rs.getTimestamp(column)); // there is no representation of TIMESTAMP '1970-01-01 00:14:15.227' in JVM zone (America/Bahia_Banderas) }); } diff --git a/presto-jdbc/src/test/java/io/prestosql/jdbc/TestPrestoDriver.java b/presto-jdbc/src/test/java/io/prestosql/jdbc/TestPrestoDriver.java index 0c1cebefb1009..ed298549aabb6 100644 --- a/presto-jdbc/src/test/java/io/prestosql/jdbc/TestPrestoDriver.java +++ b/presto-jdbc/src/test/java/io/prestosql/jdbc/TestPrestoDriver.java @@ -61,6 +61,9 @@ import java.sql.Time; import java.sql.Timestamp; import java.sql.Types; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.GregorianCalendar; import java.util.List; @@ -332,19 +335,19 @@ public void testTypes() assertEquals(rs.getTimestamp("d", ASIA_ORAL_CALENDAR), new Timestamp(new DateTime(2001, 2, 3, 3, 4, 5, ASIA_ORAL_ZONE).getMillis())); assertEquals(rs.getObject("d"), new Timestamp(new DateTime(2001, 2, 3, 3, 4, 5).getMillis())); - assertEquals(rs.getTimestamp(5), new Timestamp(new DateTime(2004, 5, 6, 6, 7, 8, DateTimeZone.forOffsetHoursMinutes(6, 17)).getMillis())); - assertEquals(rs.getTimestamp(5, ASIA_ORAL_CALENDAR), new Timestamp(new DateTime(2004, 5, 6, 6, 7, 8, DateTimeZone.forOffsetHoursMinutes(6, 17)).getMillis())); - assertEquals(rs.getObject(5), new Timestamp(new DateTime(2004, 5, 6, 6, 7, 8, DateTimeZone.forOffsetHoursMinutes(6, 17)).getMillis())); - assertEquals(rs.getTimestamp("e"), new Timestamp(new DateTime(2004, 5, 6, 6, 7, 8, DateTimeZone.forOffsetHoursMinutes(6, 17)).getMillis())); - assertEquals(rs.getTimestamp("e", ASIA_ORAL_CALENDAR), new Timestamp(new DateTime(2004, 5, 6, 6, 7, 8, DateTimeZone.forOffsetHoursMinutes(6, 17)).getMillis())); - assertEquals(rs.getObject("e"), new Timestamp(new DateTime(2004, 5, 6, 6, 7, 8, DateTimeZone.forOffsetHoursMinutes(6, 17)).getMillis())); - - assertEquals(rs.getTimestamp(6), new Timestamp(new DateTime(2007, 8, 9, 9, 10, 11, DateTimeZone.forID("Europe/Berlin")).getMillis())); - assertEquals(rs.getTimestamp(6, ASIA_ORAL_CALENDAR), new Timestamp(new DateTime(2007, 8, 9, 9, 10, 11, DateTimeZone.forID("Europe/Berlin")).getMillis())); - assertEquals(rs.getObject(6), new Timestamp(new DateTime(2007, 8, 9, 9, 10, 11, DateTimeZone.forID("Europe/Berlin")).getMillis())); - assertEquals(rs.getTimestamp("f"), new Timestamp(new DateTime(2007, 8, 9, 9, 10, 11, DateTimeZone.forID("Europe/Berlin")).getMillis())); - assertEquals(rs.getTimestamp("f", ASIA_ORAL_CALENDAR), new Timestamp(new DateTime(2007, 8, 9, 9, 10, 11, DateTimeZone.forID("Europe/Berlin")).getMillis())); - assertEquals(rs.getObject("f"), new Timestamp(new DateTime(2007, 8, 9, 9, 10, 11, DateTimeZone.forID("Europe/Berlin")).getMillis())); + // TODO assertEquals(rs.getTimestamp(5), new Timestamp(new DateTime(2004, 5, 6, 6, 7, 8, DateTimeZone.forOffsetHoursMinutes(6, 17)).getMillis())); + // TODO assertEquals(rs.getTimestamp(5, ASIA_ORAL_CALENDAR), new Timestamp(new DateTime(2004, 5, 6, 6, 7, 8, DateTimeZone.forOffsetHoursMinutes(6, 17)).getMillis())); + assertEquals(rs.getObject(5), ZonedDateTime.of(2004, 5, 6, 6, 7, 8, 0, ZoneOffset.ofHoursMinutes(6, 17))); + // TODO assertEquals(rs.getTimestamp("e"), new Timestamp(new DateTime(2004, 5, 6, 6, 7, 8, DateTimeZone.forOffsetHoursMinutes(6, 17)).getMillis())); + // TODO assertEquals(rs.getTimestamp("e", ASIA_ORAL_CALENDAR), new Timestamp(new DateTime(2004, 5, 6, 6, 7, 8, DateTimeZone.forOffsetHoursMinutes(6, 17)).getMillis())); + assertEquals(rs.getObject("e"), ZonedDateTime.of(2004, 5, 6, 6, 7, 8, 0, ZoneOffset.ofHoursMinutes(6, 17))); + + // TODO assertEquals(rs.getTimestamp(6), new Timestamp(new DateTime(2007, 8, 9, 9, 10, 11, DateTimeZone.forID("Europe/Berlin")).getMillis())); + // TODO assertEquals(rs.getTimestamp(6, ASIA_ORAL_CALENDAR), new Timestamp(new DateTime(2007, 8, 9, 9, 10, 11, DateTimeZone.forID("Europe/Berlin")).getMillis())); + assertEquals(rs.getObject(6), ZonedDateTime.of(2007, 8, 9, 9, 10, 11, 0, ZoneId.of("Europe/Berlin"))); + // TODO assertEquals(rs.getTimestamp("f"), new Timestamp(new DateTime(2007, 8, 9, 9, 10, 11, DateTimeZone.forID("Europe/Berlin")).getMillis())); + // TODO assertEquals(rs.getTimestamp("f", ASIA_ORAL_CALENDAR), new Timestamp(new DateTime(2007, 8, 9, 9, 10, 11, DateTimeZone.forID("Europe/Berlin")).getMillis())); + assertEquals(rs.getObject("f"), ZonedDateTime.of(2007, 8, 9, 9, 10, 11, 0, ZoneId.of("Europe/Berlin"))); assertEquals(rs.getDate(7), new Date(new DateTime(2013, 3, 22, 0, 0).getMillis())); assertEquals(rs.getDate(7, ASIA_ORAL_CALENDAR), new Date(new DateTime(2013, 3, 22, 0, 0, ASIA_ORAL_ZONE).getMillis()));