Skip to content

Commit

Permalink
Represent TIMESTAMP W/TZ as ZonedDateTime in JDBC
Browse files Browse the repository at this point in the history
  • Loading branch information
findepi committed Feb 25, 2019
1 parent a0ebeec commit e42a351
Show file tree
Hide file tree
Showing 4 changed files with 50 additions and 37 deletions.
4 changes: 2 additions & 2 deletions presto-jdbc/src/main/java/io/prestosql/jdbc/ColumnInfo.java
Expand Up @@ -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":
Expand Down
33 changes: 21 additions & 12 deletions presto-jdbc/src/main/java/io/prestosql/jdbc/PrestoResultSet.java
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down
21 changes: 11 additions & 10 deletions presto-jdbc/src/test/java/io/prestosql/jdbc/TestJdbcResultSet.java
Expand Up @@ -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;
Expand Down Expand Up @@ -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)
});
}

Expand Down
29 changes: 16 additions & 13 deletions presto-jdbc/src/test/java/io/prestosql/jdbc/TestPrestoDriver.java
Expand Up @@ -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;
Expand Down Expand Up @@ -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()));
Expand Down

0 comments on commit e42a351

Please sign in to comment.