From fcb1740e1b399f9a7c968570948fe3c3eb0012bb Mon Sep 17 00:00:00 2001 From: Sergey Chernov Date: Wed, 4 Feb 2026 10:22:02 -0800 Subject: [PATCH 1/3] added test to verify that datetime and time can be used for TIME --- .../jdbc/metadata/DatabaseMetaDataImpl.java | 1 + .../clickhouse/jdbc/JdbcDataTypeTests.java | 141 ++++++++++++++++++ 2 files changed, 142 insertions(+) diff --git a/jdbc-v2/src/main/java/com/clickhouse/jdbc/metadata/DatabaseMetaDataImpl.java b/jdbc-v2/src/main/java/com/clickhouse/jdbc/metadata/DatabaseMetaDataImpl.java index 8c3ae2025..20aa110e9 100644 --- a/jdbc-v2/src/main/java/com/clickhouse/jdbc/metadata/DatabaseMetaDataImpl.java +++ b/jdbc-v2/src/main/java/com/clickhouse/jdbc/metadata/DatabaseMetaDataImpl.java @@ -1180,6 +1180,7 @@ private static String getDataTypeInfoSql() { sql.append(") as attrs ON (dt.name = attrs.c1)") .append(" WHERE alias_to == ''"); + sql.append(" ORDER BY name"); return sql.toString(); } diff --git a/jdbc-v2/src/test/java/com/clickhouse/jdbc/JdbcDataTypeTests.java b/jdbc-v2/src/test/java/com/clickhouse/jdbc/JdbcDataTypeTests.java index e3784f17a..125a8e2d7 100644 --- a/jdbc-v2/src/test/java/com/clickhouse/jdbc/JdbcDataTypeTests.java +++ b/jdbc-v2/src/test/java/com/clickhouse/jdbc/JdbcDataTypeTests.java @@ -2313,4 +2313,145 @@ public Object[][] testJSONReadDP() { }; } + + /** + * Tests that both Time and DateTime columns are readable as JDBC TIME type. + * ClickHouse added Time and Time64 support in version 25.6. + * On older versions DateTime types were used to emulate TIME. + * This test ensures compatibility for reading time values from both column types. + */ + @Test(groups = { "integration" }) + public void testTimeAndDateTimeCompatibleWithJDBCTime() throws Exception { + boolean hasTimeType = !ClickHouseVersion.of(getServerVersion()).check("(,25.5]"); + + Properties createProperties = new Properties(); + if (hasTimeType) { + createProperties.put(ClientConfigProperties.serverSetting("allow_experimental_time_time64_type"), "1"); + } + + // Create table with DateTime columns (always supported) and Time columns (if available) + String tableDDL = hasTimeType + ? "CREATE TABLE test_time_compat (order Int8, " + + "time Time, time64 Time64(3), " + + "dateTime DateTime, dateTime64 DateTime64(3) " + + ") ENGINE = MergeTree ORDER BY ()" + : "CREATE TABLE test_time_compat (order Int8, " + + "dateTime DateTime, dateTime64 DateTime64(3) " + + ") ENGINE = MergeTree ORDER BY ()"; + + runQuery(tableDDL, createProperties); + + // Insert values representing times: 12:34:56 and 23:59:59.999 + String insertSQL = hasTimeType + ? "INSERT INTO test_time_compat (order, time, time64, dateTime, dateTime64) VALUES " + + "(1, '12:34:56', '12:34:56.789', '1970-01-01 12:34:56', '1970-01-01 12:34:56.789'), " + + "(2, '23:59:59', '23:59:59.999', '1970-01-01 23:59:59', '1970-01-01 23:59:59.999')" + : "INSERT INTO test_time_compat (order, dateTime, dateTime64) VALUES " + + "(1, '1970-01-01 12:34:56', '1970-01-01 12:34:56.789'), " + + "(2, '1970-01-01 23:59:59', '1970-01-01 23:59:59.999')"; + + runQuery(insertSQL, createProperties); + + // Check that all columns are readable as java.sql.Time + try (Connection conn = getJdbcConnection()) { + try (Statement stmt = conn.createStatement()) { + try (ResultSet rs = stmt.executeQuery("SELECT * FROM test_time_compat ORDER BY order")) { + // First row: 12:34:56 + assertTrue(rs.next()); + assertEquals(rs.getInt("order"), 1); + + Calendar utcCalendar = Calendar.getInstance(TimeZone.getTimeZone("UTC")); + + // Test DateTime columns as Time (always available) + Time dateTimeAsTime = rs.getTime("dateTime", utcCalendar); + assertNotNull(dateTimeAsTime); + assertEquals(getHours(dateTimeAsTime, utcCalendar), 12); + assertEquals(getMinutes(dateTimeAsTime, utcCalendar), 34); + assertEquals(getSeconds(dateTimeAsTime, utcCalendar), 56); + + Time dateTime64AsTime = rs.getTime("dateTime64", utcCalendar); + assertNotNull(dateTime64AsTime); + assertEquals(getHours(dateTime64AsTime, utcCalendar), 12); + assertEquals(getMinutes(dateTime64AsTime, utcCalendar), 34); + assertEquals(getSeconds(dateTime64AsTime, utcCalendar), 56); + // Verify milliseconds for DateTime64 + assertEquals(dateTime64AsTime.getTime() % 1000, 789); + + if (hasTimeType) { + // Test Time columns as Time + Time timeAsTime = rs.getTime("time", utcCalendar); + assertNotNull(timeAsTime); + assertEquals(getHours(timeAsTime, utcCalendar), 12); + assertEquals(getMinutes(timeAsTime, utcCalendar), 34); + assertEquals(getSeconds(timeAsTime, utcCalendar), 56); + + Time time64AsTime = rs.getTime("time64", utcCalendar); + assertNotNull(time64AsTime); + assertEquals(getHours(time64AsTime, utcCalendar), 12); + assertEquals(getMinutes(time64AsTime, utcCalendar), 34); + assertEquals(getSeconds(time64AsTime, utcCalendar), 56); + assertEquals(time64AsTime.getTime() % 1000, 789); + + // Verify Time and DateTime return equivalent time components + assertEquals(getHours(timeAsTime, utcCalendar), getHours(dateTimeAsTime, utcCalendar)); + assertEquals(getMinutes(timeAsTime, utcCalendar), getMinutes(dateTimeAsTime, utcCalendar)); + assertEquals(getSeconds(timeAsTime, utcCalendar), getSeconds(dateTimeAsTime, utcCalendar)); + } + + // Second row: 23:59:59 + assertTrue(rs.next()); + assertEquals(rs.getInt("order"), 2); + + dateTimeAsTime = rs.getTime("dateTime", utcCalendar); + assertNotNull(dateTimeAsTime); + assertEquals(getHours(dateTimeAsTime, utcCalendar), 23); + assertEquals(getMinutes(dateTimeAsTime, utcCalendar), 59); + assertEquals(getSeconds(dateTimeAsTime, utcCalendar), 59); + + dateTime64AsTime = rs.getTime("dateTime64", utcCalendar); + assertNotNull(dateTime64AsTime); + assertEquals(getHours(dateTime64AsTime, utcCalendar), 23); + assertEquals(getMinutes(dateTime64AsTime, utcCalendar), 59); + assertEquals(getSeconds(dateTime64AsTime, utcCalendar), 59); + assertEquals(dateTime64AsTime.getTime() % 1000, 999); + + if (hasTimeType) { + Time timeAsTime = rs.getTime("time", utcCalendar); + assertNotNull(timeAsTime); + assertEquals(getHours(timeAsTime, utcCalendar), 23); + assertEquals(getMinutes(timeAsTime, utcCalendar), 59); + assertEquals(getSeconds(timeAsTime, utcCalendar), 59); + + Time time64AsTime = rs.getTime("time64", utcCalendar); + assertNotNull(time64AsTime); + assertEquals(getHours(time64AsTime, utcCalendar), 23); + assertEquals(getMinutes(time64AsTime, utcCalendar), 59); + assertEquals(getSeconds(time64AsTime, utcCalendar), 59); + assertEquals(time64AsTime.getTime() % 1000, 999); + } + + assertFalse(rs.next()); + } + } + } + } + + private static void assertNotNull(Object obj) { + Assert.assertNotNull(obj); + } + + private static int getHours(Time time, Calendar calendar) { + calendar.setTime(time); + return calendar.get(Calendar.HOUR_OF_DAY); + } + + private static int getMinutes(Time time, Calendar calendar) { + calendar.setTime(time); + return calendar.get(Calendar.MINUTE); + } + + private static int getSeconds(Time time, Calendar calendar) { + calendar.setTime(time); + return calendar.get(Calendar.SECOND); + } } From 25a11517af022a1e5a1cf1b4035b8558a8838ea6 Mon Sep 17 00:00:00 2001 From: Sergey Chernov Date: Wed, 4 Feb 2026 13:03:00 -0800 Subject: [PATCH 2/3] omptimized code a bit. added checking date part. added 'UTC' timezone for consistent results --- .../clickhouse/jdbc/JdbcDataTypeTests.java | 132 ++++++------------ 1 file changed, 45 insertions(+), 87 deletions(-) diff --git a/jdbc-v2/src/test/java/com/clickhouse/jdbc/JdbcDataTypeTests.java b/jdbc-v2/src/test/java/com/clickhouse/jdbc/JdbcDataTypeTests.java index 125a8e2d7..87852c268 100644 --- a/jdbc-v2/src/test/java/com/clickhouse/jdbc/JdbcDataTypeTests.java +++ b/jdbc-v2/src/test/java/com/clickhouse/jdbc/JdbcDataTypeTests.java @@ -40,7 +40,6 @@ 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; @@ -2333,10 +2332,10 @@ public void testTimeAndDateTimeCompatibleWithJDBCTime() throws Exception { String tableDDL = hasTimeType ? "CREATE TABLE test_time_compat (order Int8, " + "time Time, time64 Time64(3), " - + "dateTime DateTime, dateTime64 DateTime64(3) " + + "dateTime DateTime, dateTime64 DateTime64(3, 'UTC') " + ") ENGINE = MergeTree ORDER BY ()" : "CREATE TABLE test_time_compat (order Int8, " - + "dateTime DateTime, dateTime64 DateTime64(3) " + + "dateTime DateTime, dateTime64 DateTime64(3, 'UTC') " + ") ENGINE = MergeTree ORDER BY ()"; runQuery(tableDDL, createProperties); @@ -2352,84 +2351,37 @@ public void testTimeAndDateTimeCompatibleWithJDBCTime() throws Exception { runQuery(insertSQL, createProperties); - // Check that all columns are readable as java.sql.Time + // Expected values for each row: [order, year, month, day, hours, minutes, seconds, milliseconds] + // Note: month is 1-based (1 = January) + int[][] expectedValues = { + {1, 1970, 1, 1, 12, 34, 56, 789}, + {2, 1970, 1, 1, 23, 59, 59, 999} + }; + + Calendar utcCalendar = Calendar.getInstance(TimeZone.getTimeZone("UTC")); + + // Check that all columns are readable as java.sql.Time and verify date components try (Connection conn = getJdbcConnection()) { try (Statement stmt = conn.createStatement()) { try (ResultSet rs = stmt.executeQuery("SELECT * FROM test_time_compat ORDER BY order")) { - // First row: 12:34:56 - assertTrue(rs.next()); - assertEquals(rs.getInt("order"), 1); - - Calendar utcCalendar = Calendar.getInstance(TimeZone.getTimeZone("UTC")); - - // Test DateTime columns as Time (always available) - Time dateTimeAsTime = rs.getTime("dateTime", utcCalendar); - assertNotNull(dateTimeAsTime); - assertEquals(getHours(dateTimeAsTime, utcCalendar), 12); - assertEquals(getMinutes(dateTimeAsTime, utcCalendar), 34); - assertEquals(getSeconds(dateTimeAsTime, utcCalendar), 56); - - Time dateTime64AsTime = rs.getTime("dateTime64", utcCalendar); - assertNotNull(dateTime64AsTime); - assertEquals(getHours(dateTime64AsTime, utcCalendar), 12); - assertEquals(getMinutes(dateTime64AsTime, utcCalendar), 34); - assertEquals(getSeconds(dateTime64AsTime, utcCalendar), 56); - // Verify milliseconds for DateTime64 - assertEquals(dateTime64AsTime.getTime() % 1000, 789); - - if (hasTimeType) { - // Test Time columns as Time - Time timeAsTime = rs.getTime("time", utcCalendar); - assertNotNull(timeAsTime); - assertEquals(getHours(timeAsTime, utcCalendar), 12); - assertEquals(getMinutes(timeAsTime, utcCalendar), 34); - assertEquals(getSeconds(timeAsTime, utcCalendar), 56); - - Time time64AsTime = rs.getTime("time64", utcCalendar); - assertNotNull(time64AsTime); - assertEquals(getHours(time64AsTime, utcCalendar), 12); - assertEquals(getMinutes(time64AsTime, utcCalendar), 34); - assertEquals(getSeconds(time64AsTime, utcCalendar), 56); - assertEquals(time64AsTime.getTime() % 1000, 789); - - // Verify Time and DateTime return equivalent time components - assertEquals(getHours(timeAsTime, utcCalendar), getHours(dateTimeAsTime, utcCalendar)); - assertEquals(getMinutes(timeAsTime, utcCalendar), getMinutes(dateTimeAsTime, utcCalendar)); - assertEquals(getSeconds(timeAsTime, utcCalendar), getSeconds(dateTimeAsTime, utcCalendar)); - } - - // Second row: 23:59:59 - assertTrue(rs.next()); - assertEquals(rs.getInt("order"), 2); - - dateTimeAsTime = rs.getTime("dateTime", utcCalendar); - assertNotNull(dateTimeAsTime); - assertEquals(getHours(dateTimeAsTime, utcCalendar), 23); - assertEquals(getMinutes(dateTimeAsTime, utcCalendar), 59); - assertEquals(getSeconds(dateTimeAsTime, utcCalendar), 59); - - dateTime64AsTime = rs.getTime("dateTime64", utcCalendar); - assertNotNull(dateTime64AsTime); - assertEquals(getHours(dateTime64AsTime, utcCalendar), 23); - assertEquals(getMinutes(dateTime64AsTime, utcCalendar), 59); - assertEquals(getSeconds(dateTime64AsTime, utcCalendar), 59); - assertEquals(dateTime64AsTime.getTime() % 1000, 999); - - if (hasTimeType) { - Time timeAsTime = rs.getTime("time", utcCalendar); - assertNotNull(timeAsTime); - assertEquals(getHours(timeAsTime, utcCalendar), 23); - assertEquals(getMinutes(timeAsTime, utcCalendar), 59); - assertEquals(getSeconds(timeAsTime, utcCalendar), 59); - - Time time64AsTime = rs.getTime("time64", utcCalendar); - assertNotNull(time64AsTime); - assertEquals(getHours(time64AsTime, utcCalendar), 23); - assertEquals(getMinutes(time64AsTime, utcCalendar), 59); - assertEquals(getSeconds(time64AsTime, utcCalendar), 59); - assertEquals(time64AsTime.getTime() % 1000, 999); + for (int[] expected : expectedValues) { + assertTrue(rs.next()); + assertEquals(rs.getInt("order"), expected[0]); + + // Test DateTime columns as Time (always available) + verifyTimeValue(rs.getTime("dateTime", utcCalendar), expected[4], expected[5], expected[6], 0, utcCalendar); + verifyTimeValue(rs.getTime("dateTime64", utcCalendar), expected[4], expected[5], expected[6], expected[7], utcCalendar); + + // Verify date components for DateTime columns + verifyDateValue(rs.getDate("dateTime", utcCalendar), expected[1], expected[2], expected[3], utcCalendar); + verifyDateValue(rs.getDate("dateTime64", utcCalendar), expected[1], expected[2], expected[3], utcCalendar); + + if (hasTimeType) { + // Test Time columns as Time + verifyTimeValue(rs.getTime("time", utcCalendar), expected[4], expected[5], expected[6], 0, utcCalendar); + verifyTimeValue(rs.getTime("time64", utcCalendar), expected[4], expected[5], expected[6], expected[7], utcCalendar); + } } - assertFalse(rs.next()); } } @@ -2440,18 +2392,24 @@ private static void assertNotNull(Object obj) { Assert.assertNotNull(obj); } - private static int getHours(Time time, Calendar calendar) { + private static void verifyTimeValue(Time time, int expectedHours, int expectedMinutes, + int expectedSeconds, int expectedMillis, Calendar calendar) { + assertNotNull(time); calendar.setTime(time); - return calendar.get(Calendar.HOUR_OF_DAY); - } - - private static int getMinutes(Time time, Calendar calendar) { - calendar.setTime(time); - return calendar.get(Calendar.MINUTE); + assertEquals(calendar.get(Calendar.HOUR_OF_DAY), expectedHours); + assertEquals(calendar.get(Calendar.MINUTE), expectedMinutes); + assertEquals(calendar.get(Calendar.SECOND), expectedSeconds); + if (expectedMillis > 0) { + assertEquals(time.getTime() % 1000, expectedMillis); + } } - private static int getSeconds(Time time, Calendar calendar) { - calendar.setTime(time); - return calendar.get(Calendar.SECOND); + private static void verifyDateValue(Date date, int expectedYear, int expectedMonth, + int expectedDay, Calendar calendar) { + assertNotNull(date); + calendar.setTime(date); + assertEquals(calendar.get(Calendar.YEAR), expectedYear); + assertEquals(calendar.get(Calendar.MONTH) + 1, expectedMonth); // Calendar.MONTH is 0-based, convert to 1-based + assertEquals(calendar.get(Calendar.DAY_OF_MONTH), expectedDay); } } From 9853a0dc0e1d9a148fc6c6eadc4dd204f8a2388d Mon Sep 17 00:00:00 2001 From: Sergey Chernov Date: Wed, 4 Feb 2026 15:23:49 -0800 Subject: [PATCH 3/3] Added UTC to datetime in tests --- .../src/test/java/com/clickhouse/jdbc/JdbcDataTypeTests.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jdbc-v2/src/test/java/com/clickhouse/jdbc/JdbcDataTypeTests.java b/jdbc-v2/src/test/java/com/clickhouse/jdbc/JdbcDataTypeTests.java index 87852c268..6cb3b4e65 100644 --- a/jdbc-v2/src/test/java/com/clickhouse/jdbc/JdbcDataTypeTests.java +++ b/jdbc-v2/src/test/java/com/clickhouse/jdbc/JdbcDataTypeTests.java @@ -2332,10 +2332,10 @@ public void testTimeAndDateTimeCompatibleWithJDBCTime() throws Exception { String tableDDL = hasTimeType ? "CREATE TABLE test_time_compat (order Int8, " + "time Time, time64 Time64(3), " - + "dateTime DateTime, dateTime64 DateTime64(3, 'UTC') " + + "dateTime DateTime('UTC'), dateTime64 DateTime64(3, 'UTC') " + ") ENGINE = MergeTree ORDER BY ()" : "CREATE TABLE test_time_compat (order Int8, " - + "dateTime DateTime, dateTime64 DateTime64(3, 'UTC') " + + "dateTime DateTime('UTC'), dateTime64 DateTime64(3, 'UTC') " + ") ENGINE = MergeTree ORDER BY ()"; runQuery(tableDDL, createProperties);