diff --git a/jdbc-v2/src/main/java/com/clickhouse/jdbc/ResultSetImpl.java b/jdbc-v2/src/main/java/com/clickhouse/jdbc/ResultSetImpl.java index 8437d36d2..b85d438fa 100644 --- a/jdbc-v2/src/main/java/com/clickhouse/jdbc/ResultSetImpl.java +++ b/jdbc-v2/src/main/java/com/clickhouse/jdbc/ResultSetImpl.java @@ -33,6 +33,7 @@ import java.sql.Statement; import java.sql.Time; import java.sql.Timestamp; +import java.time.Instant; import java.time.ZonedDateTime; import java.util.Calendar; import java.util.Collections; @@ -1035,18 +1036,37 @@ public Time getTime(int columnIndex, Calendar cal) throws SQLException { @Override public Time getTime(String columnLabel, Calendar cal) throws SQLException { checkClosed(); + try { - ZonedDateTime zdt = reader.getZonedDateTime(columnLabel); - if (zdt == null) { - wasNull = true; - return null; + ClickHouseColumn column = getSchema().getColumnByName(columnLabel); + switch (column.getDataType()) { + case Time: + case Time64: + Instant instant = reader.getInstant(columnLabel); + if (instant == null) { + wasNull = true; + return null; + } + wasNull = false; + return new Time(instant.getEpochSecond() * 1000L + instant.getNano() / 1_000_000); + case DateTime: + case DateTime32: + case DateTime64: + ZonedDateTime zdt = reader.getZonedDateTime(columnLabel); + if (zdt == null) { + wasNull = true; + return null; + } + wasNull = false; + + Calendar c = (Calendar) (cal != null ? cal : defaultCalendar).clone(); + c.clear(); + c.set(1970, Calendar.JANUARY, 1, zdt.getHour(), zdt.getMinute(), zdt.getSecond()); + return new Time(c.getTimeInMillis()); + default: + throw new SQLException("Column \"" + columnLabel + "\" is not a time type."); } - wasNull = false; - Calendar c = (Calendar) (cal != null ? cal : defaultCalendar).clone(); - c.clear(); - c.set(1970, Calendar.JANUARY, 1, zdt.getHour(), zdt.getMinute(), zdt.getSecond()); - return new Time(c.getTimeInMillis()); } catch (Exception e) { throw ExceptionUtils.toSqlState(String.format("Method: getTime(\"%s\") encountered an exception.", columnLabel), String.format("SQL: [%s]", parentStatement.getLastStatementSql()), e); } diff --git a/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/JdbcUtils.java b/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/JdbcUtils.java index 81ceb5fa9..799417a69 100644 --- a/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/JdbcUtils.java +++ b/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/JdbcUtils.java @@ -1,17 +1,14 @@ package com.clickhouse.jdbc.internal; +import com.clickhouse.client.api.DataTypeUtils; import com.clickhouse.client.api.data_formats.internal.BinaryStreamReader; import com.clickhouse.client.api.data_formats.internal.InetAddressConverter; import com.clickhouse.data.ClickHouseColumn; import com.clickhouse.data.ClickHouseDataType; import com.clickhouse.data.Tuple; -import com.clickhouse.data.format.BinaryStreamUtils; -import com.clickhouse.jdbc.PreparedStatementImpl; import com.clickhouse.jdbc.types.Array; import com.google.common.collect.ImmutableMap; -import org.slf4j.Logger; -import java.awt.*; import java.math.BigInteger; import java.net.Inet4Address; import java.net.Inet6Address; @@ -20,12 +17,14 @@ import java.sql.JDBCType; import java.sql.SQLException; import java.sql.SQLType; -import java.sql.Types; -import java.time.*; -import java.time.chrono.ChronoZonedDateTime; +import java.sql.Time; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.OffsetDateTime; +import java.time.ZonedDateTime; import java.time.temporal.TemporalAccessor; -import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.EnumSet; import java.util.HashMap; @@ -35,7 +34,6 @@ import java.util.Stack; import java.util.TreeMap; import java.util.function.Function; -import java.util.stream.Collectors; public class JdbcUtils { //Define a map to store the mapping between ClickHouse data types and SQL data types @@ -297,10 +295,10 @@ public static Object convert(Object value, Class type, ClickHouseColumn colum return new Array(column, arrayValue.getArrayOfObjects()); } - return convertObject(value, type); + return convertObject(value, type, column); } - public static Object convertObject(Object value, Class type) throws SQLException { + public static Object convertObject(Object value, Class type, ClickHouseColumn column) throws SQLException { if (value == null || type == null) { return value; } @@ -343,6 +341,11 @@ public static Object convertObject(Object value, Class type) throws SQLExcept } else if (type == java.sql.Time.class) { return java.sql.Time.valueOf(LocalTime.from(temporalValue)); } + } else if (type == Time.class && value instanceof Integer) { // Time + return new Time((Integer) value * 1000L); + } else if (type == Time.class && value instanceof Long) { // Time64 + Instant instant = DataTypeUtils.instantFromTime64Integer(column.getScale(), (Long) value); + return new Time(instant.getEpochSecond() * 1000L + instant.getNano() / 1_000_000); } else if (type == Inet4Address.class && value instanceof Inet6Address) { // Convert Inet6Address to Inet4Address return InetAddressConverter.convertToIpv4((InetAddress) value); diff --git a/jdbc-v2/src/test/java/com/clickhouse/jdbc/DataTypeTests.java b/jdbc-v2/src/test/java/com/clickhouse/jdbc/DataTypeTests.java index 0f71de053..8c8ac2c6b 100644 --- a/jdbc-v2/src/test/java/com/clickhouse/jdbc/DataTypeTests.java +++ b/jdbc-v2/src/test/java/com/clickhouse/jdbc/DataTypeTests.java @@ -32,14 +32,17 @@ import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.sql.Statement; +import java.sql.Time; import java.sql.Timestamp; import java.sql.Types; import java.text.DecimalFormat; import java.time.Instant; import java.time.LocalDate; import java.time.LocalDateTime; +import java.time.LocalTime; import java.time.OffsetDateTime; import java.time.ZoneId; +import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.Arrays; @@ -635,18 +638,30 @@ public void testTimeTypes() throws SQLException { try (ResultSet rs = stmt.executeQuery("SELECT * FROM test_time64")) { assertTrue(rs.next()); assertEquals(rs.getInt("order"), 1); -// assertEquals(rs.getInt("time"), -(TimeUnit.HOURS.toSeconds(999) + TimeUnit.MINUTES.toSeconds(59) + 59)); -// assertEquals(rs.getInt("time64"), -(TimeUnit.HOURS.toSeconds(999) + TimeUnit.MINUTES.toSeconds(59) + 59)); + assertEquals(rs.getInt("time"), -(TimeUnit.HOURS.toSeconds(999) + TimeUnit.MINUTES.toSeconds(59) + 59)); + assertEquals(rs.getLong("time64"), -((TimeUnit.HOURS.toNanos(999) + TimeUnit.MINUTES.toNanos(59) + TimeUnit.SECONDS.toNanos(59)) + 999999999)); assertTrue(rs.next()); assertEquals(rs.getInt("order"), 2); assertEquals(rs.getInt("time"), (TimeUnit.HOURS.toSeconds(999) + TimeUnit.MINUTES.toSeconds(59) + 59)); assertEquals(rs.getLong("time64"), (TimeUnit.HOURS.toNanos(999) + TimeUnit.MINUTES.toNanos(59) + TimeUnit.SECONDS.toNanos(59)) + 999999999); - assertThrows(SQLException.class, () -> rs.getTime("time")); - assertThrows(SQLException.class, () -> rs.getDate("time")); - assertThrows(SQLException.class, () -> rs.getTimestamp("time")); - + Time time = rs.getTime("time"); + assertEquals(time.getTime(), rs.getInt("time") * 1000L); // time is in seconds + assertEquals(time.getTime(), rs.getObject("time", Time.class).getTime()); + Time time64 = rs.getTime("time64"); + assertEquals(time64.getTime(), rs.getLong("time64") / 1_000_000); // time64 is in nanoseconds + assertEquals(time64, rs.getObject("time64", Time.class)); + + // time has no date part and cannot be converted to Date or Timestamp + for (String col : Arrays.asList("time", "time64")) { + assertThrows(SQLException.class, () -> rs.getDate(col)); + assertThrows(SQLException.class, () -> rs.getTimestamp(col)); + assertThrows(SQLException.class, () -> rs.getObject(col, Date.class)); + assertThrows(SQLException.class, () -> rs.getObject(col, Timestamp.class)); + // LocalTime conversion is not supported + assertThrows(SQLException.class, () -> rs.getObject(col, LocalTime.class)); + } assertFalse(rs.next()); } }