SQL: Fix issue timezone issues with JDBC getDate/getTime (#40360)
Previously, `getDate(int columnIdx)/getDate(String columnLabel)` and were using legacy`java.util.Calendar` instead of the the `java.time.*` classes to reset to the start of day. This resulted in different results for certain timestamps and timezones when calling `getDate(col)` vs`getObject(col, java.sql.Date)` Now only the methods (that must be implemented due to the JDBC spec) `getDate(int columnIdx, Calendar cal)/getDate(String columnLabel, Calendar cal)` are still using the `java.util.Calendar` for those conversion. The same change was applied to `getTime(int columnIdx)/getTime(String columnLabel)` and `getTimestamp(int columnIdx)/getTimestamp(String columnLabel)` Fixes: #40289 (cherry picked from commit 44560671f18397e0c58e3647732880fcb73a5034)
This commit is contained in:
parent
10bbb082a4
commit
143db10980
|
@ -58,12 +58,24 @@ final class JdbcDateUtils {
|
|||
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) {
|
||||
return new Time(utcMillisRemoveDate(asMillisSinceEpoch(date)));
|
||||
return asTime(asMillisSinceEpoch(date));
|
||||
}
|
||||
|
||||
static Timestamp asTimestamp(long millisSinceEpoch) {
|
||||
return new Timestamp(millisSinceEpoch);
|
||||
}
|
||||
|
||||
static Timestamp asTimestamp(String date) {
|
||||
|
|
|
@ -178,17 +178,17 @@ class JdbcResultSet implements ResultSet, JdbcWrapper {
|
|||
|
||||
@Override
|
||||
public Date getDate(int columnIndex) throws SQLException {
|
||||
return getDate(columnIndex, null);
|
||||
return asDate(columnIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Time getTime(int columnIndex) throws SQLException {
|
||||
return getTime(columnIndex, null);
|
||||
return asTime(columnIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Timestamp getTimestamp(int columnIndex) throws SQLException {
|
||||
return getTimestamp(columnIndex, null);
|
||||
return asTimeStamp(columnIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -244,7 +244,7 @@ class JdbcResultSet implements ResultSet, JdbcWrapper {
|
|||
return getDate(column(columnLabel));
|
||||
}
|
||||
|
||||
private Long dateTime(int columnIndex) throws SQLException {
|
||||
private Long dateTimeAsMillis(int columnIndex) throws SQLException {
|
||||
Object val = column(columnIndex);
|
||||
EsType type = columnType(columnIndex);
|
||||
try {
|
||||
|
@ -270,13 +270,68 @@ class JdbcResultSet implements ResultSet, JdbcWrapper {
|
|||
}
|
||||
}
|
||||
|
||||
private Date asDate(int columnIndex) throws SQLException {
|
||||
Object val = column(columnIndex);
|
||||
|
||||
if (val == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return JdbcDateUtils.asDate(val.toString());
|
||||
} catch (Exception e) {
|
||||
EsType type = columnType(columnIndex);
|
||||
throw new SQLException(
|
||||
format(Locale.ROOT, "Unable to convert value [%.128s] of type [%s] to a Date", val, type.getName()), e);
|
||||
}
|
||||
}
|
||||
|
||||
private Time asTime(int columnIndex) throws SQLException {
|
||||
Object val = column(columnIndex);
|
||||
|
||||
if (val == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
EsType type = columnType(columnIndex);
|
||||
if (type == EsType.DATE) {
|
||||
return new Time(0L);
|
||||
}
|
||||
|
||||
try {
|
||||
return JdbcDateUtils.asTime(val.toString());
|
||||
} catch (Exception e) {
|
||||
throw new SQLException(
|
||||
format(Locale.ROOT, "Unable to convert value [%.128s] of type [%s] to a Time", val, type.getName()), e);
|
||||
}
|
||||
}
|
||||
|
||||
private Timestamp asTimeStamp(int columnIndex) throws SQLException {
|
||||
Object val = column(columnIndex);
|
||||
|
||||
if (val == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
if (val instanceof Number) {
|
||||
return JdbcDateUtils.asTimestamp(((Number) val).longValue());
|
||||
}
|
||||
return JdbcDateUtils.asTimestamp(val.toString());
|
||||
} catch (Exception e) {
|
||||
EsType type = columnType(columnIndex);
|
||||
throw new SQLException(
|
||||
format(Locale.ROOT, "Unable to convert value [%.128s] of type [%s] to a Timestamp", val, type.getName()), e);
|
||||
}
|
||||
}
|
||||
|
||||
private Calendar safeCalendar(Calendar calendar) {
|
||||
return calendar == null ? defaultCalendar : calendar;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Date getDate(int columnIndex, Calendar cal) throws SQLException {
|
||||
return TypeConverter.convertDate(dateTime(columnIndex), safeCalendar(cal));
|
||||
return TypeConverter.convertDate(dateTimeAsMillis(columnIndex), safeCalendar(cal));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -290,7 +345,7 @@ class JdbcResultSet implements ResultSet, JdbcWrapper {
|
|||
if (type == EsType.DATE) {
|
||||
return new Time(0L);
|
||||
}
|
||||
return TypeConverter.convertTime(dateTime(columnIndex), safeCalendar(cal));
|
||||
return TypeConverter.convertTime(dateTimeAsMillis(columnIndex), safeCalendar(cal));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -300,7 +355,7 @@ class JdbcResultSet implements ResultSet, JdbcWrapper {
|
|||
|
||||
@Override
|
||||
public Timestamp getTimestamp(int columnIndex, Calendar cal) throws SQLException {
|
||||
return TypeConverter.convertTimestamp(dateTime(columnIndex), safeCalendar(cal));
|
||||
return TypeConverter.convertTimestamp(dateTimeAsMillis(columnIndex), safeCalendar(cal));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -32,6 +32,9 @@ import java.sql.SQLFeatureNotSupportedException;
|
|||
import java.sql.SQLType;
|
||||
import java.sql.Timestamp;
|
||||
import java.sql.Types;
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.Arrays;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
|
@ -874,17 +877,13 @@ public class ResultSetTestCase extends JdbcIntegrationTestCase {
|
|||
Long randomLongDate = randomNonNegativeLong();
|
||||
indexSimpleDocumentWithTrueValues(randomLongDate);
|
||||
|
||||
Calendar connCalendar = Calendar.getInstance(TimeZone.getTimeZone(timeZoneId), Locale.ROOT);
|
||||
|
||||
doWithQuery(SELECT_ALL_FIELDS, (results) -> {
|
||||
results.next();
|
||||
connCalendar.setTimeInMillis(randomLongDate);
|
||||
connCalendar.set(HOUR_OF_DAY, 0);
|
||||
connCalendar.set(MINUTE, 0);
|
||||
connCalendar.set(SECOND, 0);
|
||||
connCalendar.set(MILLISECOND, 0);
|
||||
|
||||
java.sql.Date expectedDate = new java.sql.Date(connCalendar.getTimeInMillis());
|
||||
ZoneId zoneId = ZoneId.of(timeZoneId);
|
||||
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(9));
|
||||
|
@ -892,7 +891,7 @@ public class ResultSetTestCase extends JdbcIntegrationTestCase {
|
|||
assertEquals(expectedDate, results.getObject(9, java.sql.Date.class));
|
||||
|
||||
// bulk validation for all fields which are not of type date
|
||||
validateErrorsForDateTimeTestsWithoutCalendar(results::getDate);
|
||||
validateErrorsForDateTestsWithoutCalendar(results::getDate);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -941,24 +940,17 @@ public class ResultSetTestCase extends JdbcIntegrationTestCase {
|
|||
Long randomLongDate = randomNonNegativeLong();
|
||||
indexSimpleDocumentWithTrueValues(randomLongDate);
|
||||
|
||||
Calendar c = Calendar.getInstance(TimeZone.getTimeZone(timeZoneId), Locale.ROOT);
|
||||
|
||||
doWithQuery(SELECT_ALL_FIELDS, (results) -> {
|
||||
results.next();
|
||||
c.setTimeInMillis(randomLongDate);
|
||||
c.set(ERA, GregorianCalendar.AD);
|
||||
c.set(YEAR, 1970);
|
||||
c.set(MONTH, 0);
|
||||
c.set(DAY_OF_MONTH, 1);
|
||||
|
||||
assertEquals(results.getTime("test_date"), new java.sql.Time(c.getTimeInMillis()));
|
||||
assertEquals(results.getTime(9), new java.sql.Time(c.getTimeInMillis()));
|
||||
assertEquals(results.getObject("test_date", java.sql.Time.class),
|
||||
new java.sql.Time(randomLongDate % 86400000L));
|
||||
assertEquals(results.getObject(9, java.sql.Time.class),
|
||||
new java.sql.Time(randomLongDate % 86400000L));
|
||||
|
||||
validateErrorsForDateTimeTestsWithoutCalendar(results::getTime);
|
||||
|
||||
java.sql.Time expectedTime = new java.sql.Time(randomLongDate % 86400000L);
|
||||
|
||||
assertEquals(expectedTime, results.getTime("test_date"));
|
||||
assertEquals(expectedTime, results.getTime(9));
|
||||
assertEquals(expectedTime, results.getObject("test_date", java.sql.Time.class));
|
||||
assertEquals(expectedTime, results.getObject(9, java.sql.Time.class));
|
||||
|
||||
validateErrorsForTimeTestsWithoutCalendar(results::getTime);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1689,15 +1681,25 @@ public class ResultSetTestCase extends JdbcIntegrationTestCase {
|
|||
assertThrowsUnsupportedAndExpectErrorMessage(r, "Writes not supported");
|
||||
}
|
||||
|
||||
private void validateErrorsForDateTimeTestsWithoutCalendar(CheckedFunction<String,Object,SQLException> method) {
|
||||
private void validateErrorsForDateTestsWithoutCalendar(CheckedFunction<String,Object,SQLException> method) {
|
||||
SQLException sqle;
|
||||
for (Entry<Tuple<String, Object>, SQLType> field : dateTimeTestingFields.entrySet()) {
|
||||
sqle = expectThrows(SQLException.class, () -> method.apply(field.getKey().v1()));
|
||||
assertEquals(
|
||||
format(Locale.ROOT, "Unable to convert value [%.128s] of type [%s] to a Long",
|
||||
format(Locale.ROOT, "Unable to convert value [%.128s] of type [%s] to a Date",
|
||||
field.getKey().v2(), field.getValue()), sqle.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void validateErrorsForTimeTestsWithoutCalendar(CheckedFunction<String,Object,SQLException> method) {
|
||||
SQLException sqle;
|
||||
for (Entry<Tuple<String, Object>, SQLType> field : dateTimeTestingFields.entrySet()) {
|
||||
sqle = expectThrows(SQLException.class, () -> method.apply(field.getKey().v1()));
|
||||
assertEquals(
|
||||
format(Locale.ROOT, "Unable to convert value [%.128s] of type [%s] to a Time",
|
||||
field.getKey().v2(), field.getValue()), sqle.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void validateErrorsForDateTimeTestsWithCalendar(Calendar c, CheckedBiFunction<String,Calendar,Object,SQLException> method) {
|
||||
SQLException sqle;
|
||||
|
|
Loading…
Reference in New Issue