diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 95ba9477d4e19..3115ad2495917 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -67,6 +67,7 @@ 1. Pipeline: Improve "alter transmission rule": verify STREAM_CHANNEL TYPE NAME - [#36864](https://github.com/apache/shardingsphere/pull/36864) 1. Pipeline: InventoryDumperContextSplitter supports multi-columns unique key first integer column splitting - [#36935](https://github.com/apache/shardingsphere/pull/36935) 1. Encrypt: Support handling show create view result decoration in encrypt - [#37299](https://github.com/apache/shardingsphere/pull/37299) +1. JDBC: Enhance ResultSetUtils to support flexible string date/time conversions - [37424](https://github.com/apache/shardingsphere/pull/37424) ### Bug Fixes diff --git a/infra/executor/src/main/java/org/apache/shardingsphere/infra/executor/sql/execute/result/query/impl/driver/jdbc/type/util/ResultSetUtils.java b/infra/executor/src/main/java/org/apache/shardingsphere/infra/executor/sql/execute/result/query/impl/driver/jdbc/type/util/ResultSetUtils.java index a69984341b3cb..908599bd110c1 100644 --- a/infra/executor/src/main/java/org/apache/shardingsphere/infra/executor/sql/execute/result/query/impl/driver/jdbc/type/util/ResultSetUtils.java +++ b/infra/executor/src/main/java/org/apache/shardingsphere/infra/executor/sql/execute/result/query/impl/driver/jdbc/type/util/ResultSetUtils.java @@ -37,7 +37,10 @@ import java.time.LocalTime; import java.time.OffsetDateTime; import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.time.temporal.TemporalAccessor; import java.util.Date; +import java.util.Optional; /** * Result set utility class. @@ -45,6 +48,14 @@ @NoArgsConstructor(access = AccessLevel.PRIVATE) public final class ResultSetUtils { + private static final DateTimeFormatter LOOSE_DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern( + "[yyyy-MM-dd][yyyy_MM_dd][yyyyMMdd][yyyy-M-d][MM/dd/yy][yyMMdd]" + + "['T'][ ]" + + "[HH:mm:ss][HHmmss][HH:mm][HHmm]" + + "[.SSSSSSSSS][.SSSSSSSS][.SSSSSSS][.SSSSSS][.SSSSS][.SSSS][.SSS][.SS][.S]" + + "[ ]" + + "[XXXXX][XXXX][XXX][XX][X]"); + /** * Convert value via expected class type. * @@ -88,6 +99,12 @@ public static Object convertValue(final Object value, final Class convertType if (String.class.equals(convertType)) { return value.toString(); } + if (value instanceof String) { + Optional result = convertStringValue((String) value, convertType); + if (result.isPresent()) { + return result.get(); + } + } try { return convertType.cast(value); } catch (final ClassCastException ignored) { @@ -95,6 +112,21 @@ public static Object convertValue(final Object value, final Class convertType } } + private static Optional convertStringValue(final String value, final Class convertType) { + if (Timestamp.class.equals(convertType)) { + TemporalAccessor temporalAccessor = LOOSE_DATE_TIME_FORMATTER.parseBest(value, LocalDateTime::from, LocalDate::from); + LocalDateTime localDateTime = (temporalAccessor instanceof LocalDateTime) ? (LocalDateTime) temporalAccessor : ((LocalDate) temporalAccessor).atStartOfDay(); + return Optional.of(Timestamp.valueOf(localDateTime)); + } + if (java.sql.Date.class.equals(convertType)) { + return Optional.of(java.sql.Date.valueOf(LocalDate.from(LOOSE_DATE_TIME_FORMATTER.parse(value)))); + } + if (java.sql.Time.class.equals(convertType)) { + return Optional.of(java.sql.Time.valueOf(LocalTime.from(LOOSE_DATE_TIME_FORMATTER.parse(value)))); + } + return Optional.empty(); + } + private static Object convertNullValue(final Class convertType) { switch (convertType.getName()) { case "boolean": diff --git a/infra/executor/src/test/java/org/apache/shardingsphere/infra/executor/sql/execute/result/query/impl/driver/jdbc/type/util/ResultSetUtilsTest.java b/infra/executor/src/test/java/org/apache/shardingsphere/infra/executor/sql/execute/result/query/impl/driver/jdbc/type/util/ResultSetUtilsTest.java index 2a77a5962edbe..13cba861a56ed 100644 --- a/infra/executor/src/test/java/org/apache/shardingsphere/infra/executor/sql/execute/result/query/impl/driver/jdbc/type/util/ResultSetUtilsTest.java +++ b/infra/executor/src/test/java/org/apache/shardingsphere/infra/executor/sql/execute/result/query/impl/driver/jdbc/type/util/ResultSetUtilsTest.java @@ -199,4 +199,165 @@ void assertConvertDateValueToLocalDateWithCurrentDate() throws SQLException { java.sql.Date sqlDate = new java.sql.Date(now.getTime()); assertThat(result, is(sqlDate.toLocalDate())); } + + @Test + void assertConvertStringValueToTimestamp() throws SQLException { + String dateTimeStr = "2021-12-23 19:30:45"; + Timestamp result = (Timestamp) ResultSetUtils.convertValue(dateTimeStr, Timestamp.class); + assertThat(result, is(Timestamp.valueOf(LocalDateTime.of(2021, 12, 23, 19, 30, 45)))); + } + + @Test + void assertConvertStringValueToTimestampWithISOFormat() throws SQLException { + String dateTimeStr = "2021-12-23T19:30:45"; + Timestamp result = (Timestamp) ResultSetUtils.convertValue(dateTimeStr, Timestamp.class); + assertThat(result, is(Timestamp.valueOf(LocalDateTime.of(2021, 12, 23, 19, 30, 45)))); + } + + @Test + void assertConvertStringValueToTimestampWithMilliseconds() throws SQLException { + String dateTimeStr = "2021-12-23 19:30:45.123"; + Timestamp result = (Timestamp) ResultSetUtils.convertValue(dateTimeStr, Timestamp.class); + Timestamp expected = Timestamp.valueOf(LocalDateTime.of(2021, 12, 23, 19, 30, 45, 123000000)); + assertThat(result, is(expected)); + } + + @Test + void assertConvertStringValueToTimestampWithDateOnly() throws SQLException { + String dateStr = "2021-12-23"; + Timestamp result = (Timestamp) ResultSetUtils.convertValue(dateStr, Timestamp.class); + assertThat(result, is(Timestamp.valueOf(LocalDateTime.of(2021, 12, 23, 0, 0, 0)))); + } + + @Test + void assertConvertStringValueToTimestampWithCompactFormat() throws SQLException { + String dateTimeStr = "20211223 193045"; + Timestamp result = (Timestamp) ResultSetUtils.convertValue(dateTimeStr, Timestamp.class); + assertThat(result, is(Timestamp.valueOf(LocalDateTime.of(2021, 12, 23, 19, 30, 45)))); + } + + @Test + void assertConvertStringValueToTimestampWithUnderscoreFormat() throws SQLException { + String dateTimeStr = "2021_12_23 19:30:45"; + Timestamp result = (Timestamp) ResultSetUtils.convertValue(dateTimeStr, Timestamp.class); + assertThat(result, is(Timestamp.valueOf(LocalDateTime.of(2021, 12, 23, 19, 30, 45)))); + } + + @Test + void assertConvertStringValueToSqlDate() throws SQLException { + String dateStr = "2021-12-23"; + java.sql.Date result = (java.sql.Date) ResultSetUtils.convertValue(dateStr, java.sql.Date.class); + assertThat(result, is(java.sql.Date.valueOf(LocalDate.of(2021, 12, 23)))); + } + + @Test + void assertConvertStringValueToSqlDateWithCompactFormat() throws SQLException { + String dateStr = "20211223"; + java.sql.Date result = (java.sql.Date) ResultSetUtils.convertValue(dateStr, java.sql.Date.class); + assertThat(result, is(java.sql.Date.valueOf(LocalDate.of(2021, 12, 23)))); + } + + @Test + void assertConvertStringValueToSqlDateWithUnderscoreFormat() throws SQLException { + String dateStr = "2021_12_23"; + java.sql.Date result = (java.sql.Date) ResultSetUtils.convertValue(dateStr, java.sql.Date.class); + assertThat(result, is(java.sql.Date.valueOf(LocalDate.of(2021, 12, 23)))); + } + + @Test + void assertConvertStringValueToSqlDateWithSlashFormat() throws SQLException { + String dateStr = "12/23/21"; + java.sql.Date result = (java.sql.Date) ResultSetUtils.convertValue(dateStr, java.sql.Date.class); + assertThat(result, is(java.sql.Date.valueOf(LocalDate.of(2021, 12, 23)))); + } + + @Test + void assertConvertStringValueToSqlDateWithSingleDigitMonth() throws SQLException { + String dateStr = "2021-1-5"; + java.sql.Date result = (java.sql.Date) ResultSetUtils.convertValue(dateStr, java.sql.Date.class); + assertThat(result, is(java.sql.Date.valueOf(LocalDate.of(2021, 1, 5)))); + } + + @Test + void assertConvertStringValueToSqlTime() throws SQLException { + String timeStr = "19:30:45"; + Time result = (Time) ResultSetUtils.convertValue(timeStr, Time.class); + assertThat(result, is(Time.valueOf(LocalTime.of(19, 30, 45)))); + } + + @Test + void assertConvertStringValueToSqlTimeWithMilliseconds() throws SQLException { + String timeStr = "19:30:45.123"; + Time result = (Time) ResultSetUtils.convertValue(timeStr, Time.class); + LocalTime localTime = LocalTime.of(19, 30, 45, 123000000); + assertThat(result, is(Time.valueOf(localTime))); + } + + @Test + void assertConvertStringValueToSqlTimeWithShortFormat() throws SQLException { + String timeStr = "19:30"; + Time result = (Time) ResultSetUtils.convertValue(timeStr, Time.class); + assertThat(result, is(Time.valueOf(LocalTime.of(19, 30, 0)))); + } + + @Test + void assertConvertStringValueWithUnsupportedTypeReturnsString() throws SQLException { + String value = "test string"; + String result = (String) ResultSetUtils.convertValue(value, String.class); + assertThat(result, is("test string")); + } + + @Test + void assertConvertStringValueWithInvalidTimestampFormat() { + String invalidDateStr = "invalid-date-time"; + assertThrows(Exception.class, () -> ResultSetUtils.convertValue(invalidDateStr, Timestamp.class)); + } + + @Test + void assertConvertStringValueWithInvalidDateFormat() { + String invalidDateStr = "not-a-date"; + assertThrows(Exception.class, () -> ResultSetUtils.convertValue(invalidDateStr, java.sql.Date.class)); + } + + @Test + void assertConvertStringValueWithInvalidTimeFormat() { + String invalidTimeStr = "not-a-time"; + assertThrows(Exception.class, () -> ResultSetUtils.convertValue(invalidTimeStr, Time.class)); + } + + @Test + void assertConvertStringValueToTimestampWithEpoch() throws SQLException { + String dateTimeStr = "1970-01-01 00:00:00"; + Timestamp result = (Timestamp) ResultSetUtils.convertValue(dateTimeStr, Timestamp.class); + assertThat(result, is(Timestamp.valueOf(LocalDateTime.of(1970, 1, 1, 0, 0, 0)))); + } + + @Test + void assertConvertStringValueToTimestampWithNanoseconds() throws SQLException { + String dateTimeStr = "2021-12-23 19:30:45.123456789"; + Timestamp result = (Timestamp) ResultSetUtils.convertValue(dateTimeStr, Timestamp.class); + Timestamp expected = Timestamp.valueOf(LocalDateTime.of(2021, 12, 23, 19, 30, 45, 123456789)); + assertThat(result, is(expected)); + } + + @Test + void assertConvertStringValueToSqlDateWithEpoch() throws SQLException { + String dateStr = "1970-01-01"; + java.sql.Date result = (java.sql.Date) ResultSetUtils.convertValue(dateStr, java.sql.Date.class); + assertThat(result, is(java.sql.Date.valueOf(LocalDate.of(1970, 1, 1)))); + } + + @Test + void assertConvertStringValueToSqlTimeMidnight() throws SQLException { + String timeStr = "00:00:00"; + Time result = (Time) ResultSetUtils.convertValue(timeStr, Time.class); + assertThat(result, is(Time.valueOf(LocalTime.of(0, 0, 0)))); + } + + @Test + void assertConvertStringValueToSqlTimeEndOfDay() throws SQLException { + String timeStr = "23:59:59"; + Time result = (Time) ResultSetUtils.convertValue(timeStr, Time.class); + assertThat(result, is(Time.valueOf(LocalTime.of(23, 59, 59)))); + } }