diff --git a/clickhouse-jdbc/src/main/java/ru/yandex/clickhouse/domain/ClickHouseDataType.java b/clickhouse-jdbc/src/main/java/ru/yandex/clickhouse/domain/ClickHouseDataType.java index fe0fb9b0b..182120b90 100644 --- a/clickhouse-jdbc/src/main/java/ru/yandex/clickhouse/domain/ClickHouseDataType.java +++ b/clickhouse-jdbc/src/main/java/ru/yandex/clickhouse/domain/ClickHouseDataType.java @@ -8,7 +8,6 @@ import java.sql.Timestamp; import java.util.Collections; import java.util.HashMap; -import java.util.List; import java.util.Map; import java.util.UUID; @@ -103,14 +102,25 @@ public enum ClickHouseDataType { static { Map map = new HashMap<>(); + String errorMsg = "[%s] is used by type [%s]"; + ClickHouseDataType used = null; for (ClickHouseDataType t : ClickHouseDataType.values()) { - assert map.put(t.name(), t) == null; + used = map.put(t.name(), t); + if (used != null) { + throw new IllegalStateException(java.lang.String.format(errorMsg, t.name(), used.name())); + } String nameInUpperCase = t.name().toUpperCase(); if (!nameInUpperCase.equals(t.name())) { - assert map.put(nameInUpperCase, t) == null; + used = map.put(nameInUpperCase, t); + if (used != null) { + throw new IllegalStateException(java.lang.String.format(errorMsg, nameInUpperCase, used.name())); + } } for (String alias: t.aliases) { - assert map.put(alias.toUpperCase(), t) == null; + used = map.put(alias.toUpperCase(), t); + if (used != null) { + throw new IllegalStateException(java.lang.String.format(errorMsg, alias, used.name())); + } } } name2type = Collections.unmodifiableMap(map); diff --git a/clickhouse-jdbc/src/main/java/ru/yandex/clickhouse/response/ClickHouseResultSet.java b/clickhouse-jdbc/src/main/java/ru/yandex/clickhouse/response/ClickHouseResultSet.java index e04d45c65..89ec7f9ec 100644 --- a/clickhouse-jdbc/src/main/java/ru/yandex/clickhouse/response/ClickHouseResultSet.java +++ b/clickhouse-jdbc/src/main/java/ru/yandex/clickhouse/response/ClickHouseResultSet.java @@ -727,7 +727,7 @@ public T getObject(int columnIndex, Class type) throws SQLException { ClickHouseColumnInfo columnInfo = getColumnInfo(columnIndex); TimeZone tz = getEffectiveTimeZone(columnInfo); return columnInfo.isArray() - ? (T) getArray(columnIndex) + ? (Array.class.isAssignableFrom(type) ? (T) getArray(columnIndex) : (T) getArray(columnIndex).getArray()) : ClickHouseValueParser.getParser(type).parse(getValue(columnIndex), columnInfo, tz); } diff --git a/clickhouse-jdbc/src/main/java/ru/yandex/clickhouse/response/parser/ClickHouseDateValueParser.java b/clickhouse-jdbc/src/main/java/ru/yandex/clickhouse/response/parser/ClickHouseDateValueParser.java index 527c8fe21..d05645b8f 100644 --- a/clickhouse-jdbc/src/main/java/ru/yandex/clickhouse/response/parser/ClickHouseDateValueParser.java +++ b/clickhouse-jdbc/src/main/java/ru/yandex/clickhouse/response/parser/ClickHouseDateValueParser.java @@ -189,7 +189,7 @@ protected final LocalDate parseAsLocalDate(String value) { } protected final LocalDateTime parseAsLocalDateTime(String value) { - int index = value == null ? -1 : value.indexOf('.'); + int index = Objects.requireNonNull(value).indexOf('.'); if (index > 0) { int endIndex = -1; for (int i = index + 1, len = value.length(); i < len; i++) { diff --git a/clickhouse-jdbc/src/main/java/ru/yandex/clickhouse/settings/ClickHouseConnectionSettings.java b/clickhouse-jdbc/src/main/java/ru/yandex/clickhouse/settings/ClickHouseConnectionSettings.java index 04a194863..ed9bedadb 100644 --- a/clickhouse-jdbc/src/main/java/ru/yandex/clickhouse/settings/ClickHouseConnectionSettings.java +++ b/clickhouse-jdbc/src/main/java/ru/yandex/clickhouse/settings/ClickHouseConnectionSettings.java @@ -50,6 +50,7 @@ public enum ClickHouseConnectionSettings implements DriverPropertyCreator { "Whether to use timezone from server on Date parsing in getDate(). " + "If false, Date returned is a wrapper of a timestamp at start of the day in client timezone. " + "If true - at start of the day in server or use_timezone timezone."), + CLIENT_NAME("client_name", "", "client_name or http_user_agent show up in system.query_log table, depending on the protocol you're using."), @Deprecated USE_NEW_PARSER("use_new_parser", true, "Whether to use JavaCC based SQL parser or not.") ; @@ -57,7 +58,7 @@ public enum ClickHouseConnectionSettings implements DriverPropertyCreator { private final String key; private final Object defaultValue; private final String description; - private final Class clazz; + private final Class clazz; ClickHouseConnectionSettings(String key, Object defaultValue, String description) { this.key = key; @@ -74,7 +75,7 @@ public Object getDefaultValue() { return defaultValue; } - public Class getClazz() { + public Class getClazz() { return clazz; } diff --git a/clickhouse-jdbc/src/main/java/ru/yandex/clickhouse/settings/ClickHouseProperties.java b/clickhouse-jdbc/src/main/java/ru/yandex/clickhouse/settings/ClickHouseProperties.java index 31af3b25e..b99b476e1 100644 --- a/clickhouse-jdbc/src/main/java/ru/yandex/clickhouse/settings/ClickHouseProperties.java +++ b/clickhouse-jdbc/src/main/java/ru/yandex/clickhouse/settings/ClickHouseProperties.java @@ -97,6 +97,7 @@ public class ClickHouseProperties { private Boolean anyJoinDistinctRightTableKeys; private Boolean sendProgressInHttpHeaders; private Boolean waitEndOfQuery; + private String clientName; @Deprecated private boolean useNewParser; @@ -129,6 +130,7 @@ public ClickHouseProperties(Properties info) { this.useTimeZone = (String)getSetting(info, ClickHouseConnectionSettings.USE_TIME_ZONE); this.useServerTimeZoneForDates = (Boolean)getSetting(info, ClickHouseConnectionSettings.USE_SERVER_TIME_ZONE_FOR_DATES); this.useObjectsInArrays = (Boolean)getSetting(info, ClickHouseConnectionSettings.USE_OBJECTS_IN_ARRAYS); + this.clientName = (String)getSetting(info, ClickHouseConnectionSettings.CLIENT_NAME); this.useNewParser = (Boolean)getSetting(info, ClickHouseConnectionSettings.USE_NEW_PARSER); this.maxParallelReplicas = getSetting(info, ClickHouseQueryParam.MAX_PARALLEL_REPLICAS); @@ -197,6 +199,7 @@ public Properties asProperties() { ret.put(ClickHouseConnectionSettings.USE_TIME_ZONE.getKey(), String.valueOf(useTimeZone)); ret.put(ClickHouseConnectionSettings.USE_SERVER_TIME_ZONE_FOR_DATES.getKey(), String.valueOf(useServerTimeZoneForDates)); ret.put(ClickHouseConnectionSettings.USE_OBJECTS_IN_ARRAYS.getKey(), String.valueOf(useObjectsInArrays)); + ret.put(ClickHouseConnectionSettings.CLIENT_NAME.getKey(), String.valueOf(clientName)); ret.put(ClickHouseConnectionSettings.USE_NEW_PARSER.getKey(), String.valueOf(useNewParser)); ret.put(ClickHouseQueryParam.MAX_PARALLEL_REPLICAS.getKey(), maxParallelReplicas); @@ -268,6 +271,7 @@ public ClickHouseProperties(ClickHouseProperties properties) { setUseTimeZone(properties.useTimeZone); setUseServerTimeZoneForDates(properties.useServerTimeZoneForDates); setUseObjectsInArrays(properties.useObjectsInArrays); + setClientName(properties.clientName); setUseNewParser(properties.useNewParser); setMaxParallelReplicas(properties.maxParallelReplicas); setMaxPartitionsPerInsertBlock(properties.maxPartitionsPerInsertBlock); @@ -684,6 +688,14 @@ public void setUseObjectsInArrays(boolean useObjectsInArrays) { this.useObjectsInArrays = useObjectsInArrays; } + public String getClientName() { + return this.clientName; + } + + public void setClientName(String clientName) { + this.clientName = clientName; + } + @Deprecated public boolean isUseNewParser() { return useNewParser; diff --git a/clickhouse-jdbc/src/main/java/ru/yandex/clickhouse/util/ClickHouseHttpClientBuilder.java b/clickhouse-jdbc/src/main/java/ru/yandex/clickhouse/util/ClickHouseHttpClientBuilder.java index 66bc9980b..9dbb8af6b 100644 --- a/clickhouse-jdbc/src/main/java/ru/yandex/clickhouse/util/ClickHouseHttpClientBuilder.java +++ b/clickhouse-jdbc/src/main/java/ru/yandex/clickhouse/util/ClickHouseHttpClientBuilder.java @@ -67,7 +67,7 @@ public ClickHouseHttpClientBuilder(ClickHouseProperties properties) { } public CloseableHttpClient buildClient() throws Exception { - return HttpClientBuilder.create() + HttpClientBuilder builder = HttpClientBuilder.create() .setConnectionManager(getConnectionManager()) .setRetryHandler(getRequestRetryHandler()) .setConnectionReuseStrategy(getConnectionReuseStrategy()) @@ -76,8 +76,14 @@ public CloseableHttpClient buildClient() throws Exception { .setDefaultHeaders(getDefaultHeaders()) .setDefaultCredentialsProvider(getDefaultCredentialsProvider()) .disableContentCompression() // gzip is not needed. Use lz4 when compress=1 - .disableRedirectHandling() - .build(); + .disableRedirectHandling(); + + String clientName = properties != null ? properties.getClientName() : null; + if (!Utils.isNullOrEmptyString(clientName)) { + builder.setUserAgent(clientName); + } + + return builder.build(); } private HttpRequestRetryHandler getRequestRetryHandler() { diff --git a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/integration/ClickHouseDataTypeTest.java b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/integration/ClickHouseDataTypeTest.java index 88aed0ba2..4a02f4300 100644 --- a/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/integration/ClickHouseDataTypeTest.java +++ b/clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/integration/ClickHouseDataTypeTest.java @@ -1,5 +1,6 @@ package ru.yandex.clickhouse.integration; +import static org.testng.Assert.assertNull; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertTrue; @@ -24,12 +25,14 @@ import java.util.Calendar; import java.util.Objects; import java.util.TimeZone; +import java.util.UUID; import org.testng.annotations.AfterTest; import org.testng.annotations.BeforeTest; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; +import ru.yandex.clickhouse.ClickHouseArray; import ru.yandex.clickhouse.ClickHouseConnection; import ru.yandex.clickhouse.ClickHouseContainerForTest; import ru.yandex.clickhouse.ClickHouseDataSource; @@ -47,7 +50,7 @@ private LocalDate instantToLocalDate(Instant instant, ZoneId zone) { ZoneRules rules = zone.getRules(); ZoneOffset offset = rules.getOffset(instant); long localSecond = instant.getEpochSecond() + offset.getTotalSeconds(); - long localEpochDay = Math.floorDiv(localSecond, 24 * 3600); + long localEpochDay = Math.floorDiv(localSecond, 24 * 3600L); return LocalDate.ofEpochDay(localEpochDay); } @@ -56,7 +59,7 @@ private LocalTime instantToLocalTime(Instant instant, ZoneId zone) { Objects.requireNonNull(zone, "zone"); ZoneOffset offset = zone.getRules().getOffset(instant); long localSecond = instant.getEpochSecond() + offset.getTotalSeconds(); - int secondsADay = 24 * 3600; + long secondsADay = 24 * 3600L; int secsOfDay = (int) (localSecond - Math.floorDiv(localSecond, secondsADay) * secondsADay); return LocalTime.ofNanoOfDay(secsOfDay * 1000_000_000L + instant.getNano()); } @@ -322,8 +325,9 @@ public void testDateTimeWithTimeZone(String d1TimeZone, String d2TimeZone, Strin assertEquals(r.getDate("d2"), expectedDate); assertEquals(r.getObject("d2", Date.class), expectedDate); } - //expectedDate = new Date(testInstant.atZone(connServerTz.getServerTimeZone().toZoneId()) - // .truncatedTo(ChronoUnit.DAYS).toInstant().toEpochMilli()); + // expectedDate = new + // Date(testInstant.atZone(connServerTz.getServerTimeZone().toZoneId()) + // .truncatedTo(ChronoUnit.DAYS).toInstant().toEpochMilli()); try (Statement s = connServerTz.createStatement(); ResultSet r = s.executeQuery(query);) { assertTrue(r.next()); assertEquals(r.getDate("d0"), expectedDate); @@ -877,6 +881,77 @@ public void testDateWithTimeZone(String testTimeZone) throws Exception { } } + @Test + public void testUUID() throws Exception { + try (Statement s = conn.createStatement()) { + s.execute("DROP TABLE IF EXISTS test_uuid"); + s.execute( + "CREATE TABLE IF NOT EXISTS test_uuid(u0 UUID, u1 Nullable(UUID), u2 Array(UUID), u3 Array(Nullable(UUID))) ENGINE = Memory"); + } catch (ClickHouseException e) { + return; + } + + try (Statement s = conn.createStatement()) { + UUID uuid = UUID.randomUUID(); + String str = uuid.toString(); + s.execute("insert into test_uuid values ('" + str + "', null, ['" + str + "'], [null])"); + + try (ResultSet rs = s.executeQuery("select * from test_uuid")) { + assertTrue(rs.next()); + + assertEquals(rs.getString(1), str); + assertEquals(rs.getObject(1), uuid); + assertEquals(rs.getObject(1, UUID.class), uuid); + + assertNull(rs.getString(2)); + assertNull(rs.getObject(2)); + assertNull(rs.getObject(2, UUID.class)); + + assertEquals(rs.getString(3), "['" + str + "']"); + assertEquals(rs.getArray(3).getArray(), new UUID[] { uuid }); + assertEquals(rs.getObject(3, ClickHouseArray.class).getArray(), new UUID[] { uuid }); + assertEquals(rs.getObject(3, UUID[].class), new UUID[] { uuid }); + + assertEquals(rs.getString(4), "[NULL]"); + assertEquals(rs.getArray(4).getArray(), new UUID[] { null }); + assertEquals(rs.getObject(4, ClickHouseArray.class).getArray(), new UUID[] { null }); + assertEquals(rs.getObject(4, UUID[].class), new UUID[] { null }); + } + + s.execute("truncate table test_uuid"); + } + } + + @Test + public void testDateTime64() throws Exception { + try (Statement s = conn.createStatement()) { + s.execute("DROP TABLE IF EXISTS test_datetime64"); + s.execute( + "CREATE TABLE IF NOT EXISTS test_datetime64(d0 DateTime64(3, 'UTC'), d1 Nullable(DateTime64)) ENGINE = Memory"); + } catch (ClickHouseException e) { + return; + } + + try (Statement s = conn.createStatement()) { + s.execute("insert into test_datetime64 values (1, null)"); + + try (ResultSet rs = s.executeQuery("select * from test_datetime64")) { + assertTrue(rs.next()); + assertEquals(rs.getObject(1), new Timestamp(1L)); + assertEquals(rs.getObject(1, Instant.class), Instant.ofEpochMilli(1L)); + assertEquals(rs.getObject(1, LocalDate.class), LocalDate.ofEpochDay(0)); + assertEquals(rs.getObject(1, LocalDateTime.class), + Instant.ofEpochMilli(1L).atZone(ZoneId.of("UTC")).toLocalDateTime()); + assertNull(rs.getObject(2)); + assertNull(rs.getObject(2, Instant.class)); + assertNull(rs.getObject(2, LocalDate.class)); + assertNull(rs.getObject(2, LocalDateTime.class)); + } + + s.execute("truncate table test_datetime64"); + } + } + @Test public void testDateTimes() throws Exception { try (Statement s = conn.createStatement()) {