SQL: Fix getTime() methods in JDBC (#40484)

Previously, `getTime(colIdx/colLabel)` and
`getObject(colIdx/colLabel, java.sql.Time.class)` methods were computing
the time from a `ZonedDateTime` by applying day in millis modulo on the epoch millis
of the `ZonedDateTime` object. This is wrong as we need to keep the time
related fields at the timezone of the `ZonedDateTime` object and just
set the date info to the epoch date (01/01/1970).

Additionally fixes a testing issue as the original timezone id is converted
to an offset string when parsing the response from the server.
This commit is contained in:
Marios Trivyzas 2019-03-27 17:13:18 +01:00
parent 707d40ce06
commit 8e049c5f58
No known key found for this signature in database
GPG Key ID: 8817B46B0CF36A3F
3 changed files with 47 additions and 55 deletions

View File

@ -9,6 +9,7 @@ package org.elasticsearch.xpack.sql.jdbc;
import java.sql.Date; import java.sql.Date;
import java.sql.Time; import java.sql.Time;
import java.sql.Timestamp; import java.sql.Timestamp;
import java.time.LocalDate;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder; import java.time.format.DateTimeFormatterBuilder;
@ -27,10 +28,9 @@ import static java.time.temporal.ChronoField.SECOND_OF_MINUTE;
*/ */
final class JdbcDateUtils { final class JdbcDateUtils {
private JdbcDateUtils() { private JdbcDateUtils() {}
}
private static final long DAY_IN_MILLIS = 60 * 60 * 24 * 1000L; private static final LocalDate EPOCH = LocalDate.of(1970, 1, 1);
static final DateTimeFormatter ISO_WITH_MILLIS = new DateTimeFormatterBuilder() static final DateTimeFormatter ISO_WITH_MILLIS = new DateTimeFormatterBuilder()
.parseCaseInsensitive() .parseCaseInsensitive()
@ -58,20 +58,9 @@ final class JdbcDateUtils {
return new Date(zdt.toLocalDate().atStartOfDay(zdt.getZone()).toInstant().toEpochMilli()); return new Date(zdt.toLocalDate().atStartOfDay(zdt.getZone()).toInstant().toEpochMilli());
} }
/**
* In contrast to {@link JdbcDateUtils#asDate(String)} here we just want to eliminate
* the date part and just set it to EPOCH (1970-01-1)
*/
static Time asTime(long millisSinceEpoch) {
return new Time(utcMillisRemoveDate(millisSinceEpoch));
}
/**
* In contrast to {@link JdbcDateUtils#asDate(String)} here we just want to eliminate
* the date part and just set it to EPOCH (1970-01-1)
*/
static Time asTime(String date) { static Time asTime(String date) {
return asTime(asMillisSinceEpoch(date)); ZonedDateTime zdt = asDateTime(date);
return new Time(zdt.toLocalTime().atDate(EPOCH).atZone(zdt.getZone()).toInstant().toEpochMilli());
} }
static Timestamp asTimestamp(long millisSinceEpoch) { static Timestamp asTimestamp(long millisSinceEpoch) {
@ -93,8 +82,4 @@ final class JdbcDateUtils {
return ctor.apply(((Number) value).longValue()); return ctor.apply(((Number) value).longValue());
} }
} }
private static long utcMillisRemoveDate(long l) {
return l % DAY_IN_MILLIS;
}
} }

View File

@ -23,10 +23,13 @@ import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor; import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes; import java.nio.file.attribute.BasicFileAttributes;
import java.sql.Date;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.ResultSetMetaData; import java.sql.ResultSetMetaData;
import java.sql.SQLException; import java.sql.SQLException;
import java.sql.Time;
import java.time.Instant; import java.time.Instant;
import java.time.LocalDate;
import java.time.ZoneId; import java.time.ZoneId;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import java.util.ArrayList; import java.util.ArrayList;
@ -37,15 +40,17 @@ import java.util.zip.ZipEntry;
import static org.elasticsearch.xpack.sql.action.BasicFormatter.FormatOption.CLI; import static org.elasticsearch.xpack.sql.action.BasicFormatter.FormatOption.CLI;
public abstract class JdbcTestUtils { final class JdbcTestUtils {
public static final String SQL_TRACE = "org.elasticsearch.xpack.sql:TRACE"; private JdbcTestUtils() {}
public static final String JDBC_TIMEZONE = "timezone"; private static final int MAX_WIDTH = 20;
public static ZoneId UTC = ZoneId.of("Z"); static final String SQL_TRACE = "org.elasticsearch.xpack.sql:TRACE";
static final String JDBC_TIMEZONE = "timezone";
static final LocalDate EPOCH = LocalDate.of(1970, 1, 1);
public static void logResultSetMetadata(ResultSet rs, Logger logger) throws SQLException { static void logResultSetMetadata(ResultSet rs, Logger logger) throws SQLException {
ResultSetMetaData metaData = rs.getMetaData(); ResultSetMetaData metaData = rs.getMetaData();
// header // header
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
@ -75,35 +80,24 @@ public abstract class JdbcTestUtils {
logger.info(sb.toString()); logger.info(sb.toString());
} }
private static final int MAX_WIDTH = 20; static void logResultSetData(ResultSet rs, Logger log) throws SQLException {
public static void logResultSetData(ResultSet rs, Logger log) throws SQLException {
ResultSetMetaData metaData = rs.getMetaData(); ResultSetMetaData metaData = rs.getMetaData();
StringBuilder sb = new StringBuilder();
StringBuilder column = new StringBuilder();
int columns = metaData.getColumnCount(); int columns = metaData.getColumnCount();
while (rs.next()) { while (rs.next()) {
sb.setLength(0); log.info(rowAsString(rs, columns));
for (int i = 1; i <= columns; i++) {
column.setLength(0);
if (i > 1) {
sb.append(" | ");
}
sb.append(trimOrPad(column.append(rs.getString(i))));
}
log.info(sb);
} }
} }
public static String resultSetCurrentData(ResultSet rs) throws SQLException { static String resultSetCurrentData(ResultSet rs) throws SQLException {
ResultSetMetaData metaData = rs.getMetaData(); ResultSetMetaData metaData = rs.getMetaData();
StringBuilder column = new StringBuilder(); return rowAsString(rs, metaData.getColumnCount());
}
int columns = metaData.getColumnCount();
private static String rowAsString(ResultSet rs, int columns) throws SQLException {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
StringBuilder column = new StringBuilder();
for (int i = 1; i <= columns; i++) { for (int i = 1; i <= columns; i++) {
column.setLength(0); column.setLength(0);
if (i > 1) { if (i > 1) {
@ -153,7 +147,7 @@ public abstract class JdbcTestUtils {
logger.info("\n" + formatter.formatWithHeader(cols, data)); logger.info("\n" + formatter.formatWithHeader(cols, data));
} }
public static String of(long millis, String zoneId) { static String of(long millis, String zoneId) {
return StringUtils.toString(ZonedDateTime.ofInstant(Instant.ofEpochMilli(millis), ZoneId.of(zoneId))); return StringUtils.toString(ZonedDateTime.ofInstant(Instant.ofEpochMilli(millis), ZoneId.of(zoneId)));
} }
@ -165,7 +159,7 @@ public abstract class JdbcTestUtils {
* folders in the file-system (typically IDEs) or * folders in the file-system (typically IDEs) or
* inside jars (gradle). * inside jars (gradle).
*/ */
public static List<URL> classpathResources(String pattern) throws Exception { static List<URL> classpathResources(String pattern) throws Exception {
while (pattern.startsWith("/")) { while (pattern.startsWith("/")) {
pattern = pattern.substring(1); pattern = pattern.substring(1);
} }
@ -234,4 +228,15 @@ public abstract class JdbcTestUtils {
} }
return new Tuple<>(folder, file); return new Tuple<>(folder, file);
} }
static Date asDate(long millis, ZoneId zoneId) {
return new java.sql.Date(
ZonedDateTime.ofInstant(Instant.ofEpochMilli(millis), zoneId)
.toLocalDate().atStartOfDay(zoneId).toInstant().toEpochMilli());
}
static Time asTime(long millis, ZoneId zoneId) {
return new Time(ZonedDateTime.ofInstant(Instant.ofEpochMilli(millis), zoneId)
.toLocalTime().atDate(JdbcTestUtils.EPOCH).atZone(zoneId).toInstant().toEpochMilli());
}
} }

View File

@ -34,7 +34,6 @@ import java.sql.Timestamp;
import java.sql.Types; import java.sql.Types;
import java.time.Instant; import java.time.Instant;
import java.time.ZoneId; import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Arrays; import java.util.Arrays;
import java.util.Calendar; import java.util.Calendar;
import java.util.Date; import java.util.Date;
@ -61,6 +60,8 @@ import static java.util.Calendar.MONTH;
import static java.util.Calendar.SECOND; import static java.util.Calendar.SECOND;
import static java.util.Calendar.YEAR; import static java.util.Calendar.YEAR;
import static org.elasticsearch.xpack.sql.qa.jdbc.JdbcTestUtils.JDBC_TIMEZONE; import static org.elasticsearch.xpack.sql.qa.jdbc.JdbcTestUtils.JDBC_TIMEZONE;
import static org.elasticsearch.xpack.sql.qa.jdbc.JdbcTestUtils.asDate;
import static org.elasticsearch.xpack.sql.qa.jdbc.JdbcTestUtils.asTime;
import static org.elasticsearch.xpack.sql.qa.jdbc.JdbcTestUtils.of; import static org.elasticsearch.xpack.sql.qa.jdbc.JdbcTestUtils.of;
public class ResultSetTestCase extends JdbcIntegrationTestCase { public class ResultSetTestCase extends JdbcIntegrationTestCase {
@ -880,10 +881,7 @@ public class ResultSetTestCase extends JdbcIntegrationTestCase {
doWithQuery(SELECT_ALL_FIELDS, (results) -> { doWithQuery(SELECT_ALL_FIELDS, (results) -> {
results.next(); results.next();
ZoneId zoneId = ZoneId.of(timeZoneId); java.sql.Date expectedDate = asDate(randomLongDate, getZoneFromOffset(randomLongDate));
java.sql.Date expectedDate = new java.sql.Date(
ZonedDateTime.ofInstant(Instant.ofEpochMilli(randomLongDate), zoneId)
.toLocalDate().atStartOfDay(zoneId).toInstant().toEpochMilli());
assertEquals(expectedDate, results.getDate("test_date")); assertEquals(expectedDate, results.getDate("test_date"));
assertEquals(expectedDate, results.getDate(9)); assertEquals(expectedDate, results.getDate(9));
@ -943,7 +941,7 @@ public class ResultSetTestCase extends JdbcIntegrationTestCase {
doWithQuery(SELECT_ALL_FIELDS, (results) -> { doWithQuery(SELECT_ALL_FIELDS, (results) -> {
results.next(); results.next();
java.sql.Time expectedTime = new java.sql.Time(randomLongDate % 86400000L); java.sql.Time expectedTime = asTime(randomLongDate, getZoneFromOffset(randomLongDate));
assertEquals(expectedTime, results.getTime("test_date")); assertEquals(expectedTime, results.getTime("test_date"));
assertEquals(expectedTime, results.getTime(9)); assertEquals(expectedTime, results.getTime(9));
@ -1748,4 +1746,8 @@ public class ResultSetTestCase extends JdbcIntegrationTestCase {
private String asDateString(long millis) { private String asDateString(long millis) {
return of(millis, timeZoneId); return of(millis, timeZoneId);
} }
private ZoneId getZoneFromOffset(Long randomLongDate) {
return ZoneId.of(ZoneId.of(timeZoneId).getRules().getOffset(Instant.ofEpochMilli(randomLongDate)).toString());
}
} }