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:
parent
707d40ce06
commit
8e049c5f58
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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());
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue