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..6cb3b4e65 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; @@ -2313,4 +2312,104 @@ 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('UTC'), dateTime64 DateTime64(3, 'UTC') " + + ") ENGINE = MergeTree ORDER BY ()" + : "CREATE TABLE test_time_compat (order Int8, " + + "dateTime DateTime('UTC'), dateTime64 DateTime64(3, 'UTC') " + + ") 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); + + // 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")) { + 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()); + } + } + } + } + + private static void assertNotNull(Object obj) { + Assert.assertNotNull(obj); + } + + private static void verifyTimeValue(Time time, int expectedHours, int expectedMinutes, + int expectedSeconds, int expectedMillis, Calendar calendar) { + assertNotNull(time); + calendar.setTime(time); + 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 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); + } }