From 33cd11eb0854e7395afe110dbb629ef602f4484c Mon Sep 17 00:00:00 2001 From: Sergey Chernov Date: Tue, 21 Apr 2026 19:37:57 -0700 Subject: [PATCH 1/9] Added cluster name property to JDBC --- .../com/clickhouse/jdbc/ConnectionImpl.java | 8 ++++---- .../com/clickhouse/jdbc/DriverProperties.java | 5 +++++ .../com/clickhouse/jdbc/StatementTest.java | 18 ++++++++++++++++++ 3 files changed, 27 insertions(+), 4 deletions(-) diff --git a/jdbc-v2/src/main/java/com/clickhouse/jdbc/ConnectionImpl.java b/jdbc-v2/src/main/java/com/clickhouse/jdbc/ConnectionImpl.java index 7328048ce..1abbfb063 100644 --- a/jdbc-v2/src/main/java/com/clickhouse/jdbc/ConnectionImpl.java +++ b/jdbc-v2/src/main/java/com/clickhouse/jdbc/ConnectionImpl.java @@ -52,8 +52,8 @@ public class ConnectionImpl implements Connection, JdbcV2Wrapper { protected final JdbcConfiguration config; private boolean closed = false; - protected boolean onCluster;//TODO: Placeholder for cluster support - protected String cluster; + protected final boolean onCluster; + protected final String cluster; private String catalog; private String schema; private String appName; @@ -74,8 +74,8 @@ public ConnectionImpl(String url, Properties info) throws SQLException { try { this.url = url;//Raw URL this.config = new JdbcConfiguration(url, info); - this.onCluster = false; - this.cluster = null; + this.cluster = config.getDriverProperty(DriverProperties.CLUSTER_NAME.getKey(), DriverProperties.CLUSTER_NAME.getDefaultValue()); + this.onCluster = this.cluster != null && !this.cluster.trim().isEmpty(); this.appName = ""; this.readOnly = false; this.holdability = ResultSet.HOLD_CURSORS_OVER_COMMIT; diff --git a/jdbc-v2/src/main/java/com/clickhouse/jdbc/DriverProperties.java b/jdbc-v2/src/main/java/com/clickhouse/jdbc/DriverProperties.java index 7027c95c1..4184c582a 100644 --- a/jdbc-v2/src/main/java/com/clickhouse/jdbc/DriverProperties.java +++ b/jdbc-v2/src/main/java/com/clickhouse/jdbc/DriverProperties.java @@ -124,6 +124,11 @@ public enum DriverProperties { @Deprecated HTTP_CONNECTION_PROVIDER("http_connection_provider", null), + /** + * Define cluster name for statement executions like KILL + */ + CLUSTER_NAME("cluster_name", null), + ; diff --git a/jdbc-v2/src/test/java/com/clickhouse/jdbc/StatementTest.java b/jdbc-v2/src/test/java/com/clickhouse/jdbc/StatementTest.java index 575dde388..66bbd36a5 100644 --- a/jdbc-v2/src/test/java/com/clickhouse/jdbc/StatementTest.java +++ b/jdbc-v2/src/test/java/com/clickhouse/jdbc/StatementTest.java @@ -579,6 +579,24 @@ public void testConnectionExhaustion() throws Exception { } } + @Test(groups = {"integration"}) + public void testCancelOnCluster() throws Exception { + String testCluster = "test_cluster_" + RandomStringUtils.randomAlphanumeric(10); + Properties p = new Properties(); + p.setProperty(DriverProperties.CLUSTER_NAME.getKey(), testCluster); + try (Connection conn = getJdbcConnection(p)) { + try (StatementImpl stmt = (StatementImpl) conn.createStatement()) { + stmt.executeQuery("SELECT number FROM system.numbers LIMIT 1000000"); + try { + stmt.cancel(); + fail("Should have thrown an exception for missing cluster"); + } catch (SQLException e) { + assertTrue(e.getMessage().contains(testCluster), "Exception should mention the missing cluster"); + } + } + } + } + @Test(groups = {"integration"}) public void testConcurrentCancel() throws Exception { int maxNumConnections = 3; From bffdd81ddc6c6414266013a03837d0a374c2fe6a Mon Sep 17 00:00:00 2001 From: Sergey Chernov Date: Tue, 21 Apr 2026 19:40:12 -0700 Subject: [PATCH 2/9] Updated changelog --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f836a5c89..bbe9f421a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## 0.9.9 + +### New Features + +- **[jdbc-v2]** Added `cluster_name` configuration property to specify a target cluster for statements like `KILL QUERY` that require an `ON CLUSTER` clause to execute across all nodes. (https://github.com/ClickHouse/clickhouse-java/issues/2837) + ## 0.9.8 ### Improvements From e51b55f23844f3a00495801098c4a40b3bf0539f Mon Sep 17 00:00:00 2001 From: Sergey Chernov Date: Wed, 22 Apr 2026 11:06:45 -0700 Subject: [PATCH 3/9] fixed tests and SQL injection --- .../internal/BinaryReaderBackedRecord.java | 898 +++++++++--------- docs/features.md | 3 +- .../com/clickhouse/jdbc/ConnectionImpl.java | 3 +- .../com/clickhouse/jdbc/StatementImpl.java | 2 +- .../com/clickhouse/jdbc/StatementTest.java | 14 +- 5 files changed, 466 insertions(+), 454 deletions(-) diff --git a/client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/BinaryReaderBackedRecord.java b/client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/BinaryReaderBackedRecord.java index 7faa8e0fe..4739ca83d 100644 --- a/client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/BinaryReaderBackedRecord.java +++ b/client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/BinaryReaderBackedRecord.java @@ -1,449 +1,449 @@ -package com.clickhouse.client.api.data_formats.internal; - -import com.clickhouse.client.api.data_formats.ClickHouseBinaryFormatReader; -import com.clickhouse.client.api.metadata.TableSchema; -import com.clickhouse.client.api.query.GenericRecord; -import com.clickhouse.data.ClickHouseColumn; -import com.clickhouse.data.value.*; - -import java.math.BigDecimal; -import java.math.BigInteger; -import java.net.Inet4Address; -import java.net.Inet6Address; -import java.time.*; -import java.time.temporal.TemporalAmount; -import java.util.List; -import java.util.Map; -import java.util.UUID; -import java.util.stream.Collectors; - -public class BinaryReaderBackedRecord implements GenericRecord { - - private final ClickHouseBinaryFormatReader reader; - - public BinaryReaderBackedRecord(ClickHouseBinaryFormatReader reader) { - this.reader = reader; - } - - @Override - public String getString(String colName) { - return reader.getString(colName); - } - - @Override - public byte getByte(String colName) { - return reader.getByte(colName); - } - - @Override - public short getShort(String colName) { - return reader.getShort(colName); - } - - @Override - public int getInteger(String colName) { - return reader.getInteger(colName); - } - - @Override - public long getLong(String colName) { - return reader.getLong(colName); - } - - @Override - public float getFloat(String colName) { - return reader.getFloat(colName); - } - - @Override - public double getDouble(String colName) { - return reader.getDouble(colName); - } - - @Override - public boolean getBoolean(String colName) { - return reader.getBoolean(colName); - } - - @Override - public BigInteger getBigInteger(String colName) { - return reader.getBigInteger(colName); - } - - @Override - public BigDecimal getBigDecimal(String colName) { - return reader.getBigDecimal(colName); - } - - @Override - public Instant getInstant(String colName) { - return reader.getInstant(colName); - } - - @Override - public ZonedDateTime getZonedDateTime(String colName) { - return reader.getZonedDateTime(colName); - } - - @Override - public Duration getDuration(String colName) { - return reader.getDuration(colName); - } - - @Override - public TemporalAmount getTemporalAmount(String colName) { - return reader.getTemporalAmount(colName); - } - - @Override - public Inet4Address getInet4Address(String colName) { - return reader.getInet4Address(colName); - } - - @Override - public Inet6Address getInet6Address(String colName) { - return reader.getInet6Address(colName); - } - - @Override - public UUID getUUID(String colName) { - return reader.getUUID(colName); - } - - @Override - public ClickHouseGeoPointValue getGeoPoint(String colName) { - return reader.getGeoPoint(colName); - } - - @Override - public ClickHouseGeoRingValue getGeoRing(String colName) { - return reader.getGeoRing(colName); - } - - @Override - public ClickHouseGeoPolygonValue getGeoPolygon(String colName) { - return reader.getGeoPolygon(colName); - } - - @Override - public ClickHouseGeoMultiPolygonValue getGeoMultiPolygon(String colName) { - return reader.getGeoMultiPolygon(colName); - } - - @Override - public List getList(String colName) { - return reader.getList(colName); - } - - @Override - public byte[] getByteArray(String colName) { - return reader.getByteArray(colName); - } - - @Override - public int[] getIntArray(String colName) { - return reader.getIntArray(colName); - } - - @Override - public long[] getLongArray(String colName) { - return reader.getLongArray(colName); - } - - @Override - public float[] getFloatArray(String colName) { - return reader.getFloatArray(colName); - } - - @Override - public double[] getDoubleArray(String colName) { - return reader.getDoubleArray(colName); - } - - @Override - public boolean[] getBooleanArray(String colName) { - return reader.getBooleanArray(colName); - } - - @Override - public short[] getShortArray(String colName) { - return reader.getShortArray(colName); - } - - @Override - public String[] getStringArray(String colName) { - return reader.getStringArray(colName); - } - - @Override - public Object[] getObjectArray(String colName) { - return reader.getObjectArray(colName); - } - - @Override - public String getString(int index) { - return reader.getString(index); - } - - @Override - public boolean hasValue(int colIndex) { - return reader.hasValue(colIndex); - } - - @Override - public boolean hasValue(String colName) { - return reader.hasValue(colName); - } - - @Override - public byte getByte(int index) { - return reader.getByte(index); - } - - @Override - public short getShort(int index) { - return reader.getShort(index); - } - - @Override - public int getInteger(int index) { - return reader.getInteger(index); - } - - @Override - public long getLong(int index) { - return reader.getLong(index); - } - - @Override - public float getFloat(int index) { - return reader.getFloat(index); - } - - @Override - public double getDouble(int index) { - return reader.getDouble(index); - } - - @Override - public boolean getBoolean(int index) { - return reader.getBoolean(index); - } - - @Override - public BigInteger getBigInteger(int index) { - return reader.getBigInteger(index); - } - - @Override - public BigDecimal getBigDecimal(int index) { - return reader.getBigDecimal(index); - } - - @Override - public Instant getInstant(int index) { - return reader.getInstant(index); - } - - @Override - public ZonedDateTime getZonedDateTime(int index) { - return reader.getZonedDateTime(index); - } - - @Override - public Duration getDuration(int index) { - return reader.getDuration(index); - } - - @Override - public TemporalAmount getTemporalAmount(int index) { - return reader.getTemporalAmount(index); - } - - @Override - public Inet4Address getInet4Address(int index) { - return reader.getInet4Address(index); - } - - @Override - public Inet6Address getInet6Address(int index) { - return reader.getInet6Address(index); - } - - @Override - public UUID getUUID(int index) { - return reader.getUUID(index); - } - - @Override - public ClickHouseGeoPointValue getGeoPoint(int index) { - return reader.getGeoPoint(index); - } - - @Override - public ClickHouseGeoRingValue getGeoRing(int index) { - return reader.getGeoRing(index); - } - - @Override - public ClickHouseGeoPolygonValue getGeoPolygon(int index) { - return reader.getGeoPolygon(index); - } - - @Override - public ClickHouseGeoMultiPolygonValue getGeoMultiPolygon(int index) { - return reader.getGeoMultiPolygon(index); - } - - @Override - public List getList(int index) { - return reader.getList(index); - } - - @Override - public byte[] getByteArray(int index) { - return reader.getByteArray(index); - } - - @Override - public int[] getIntArray(int index) { - return reader.getIntArray(index); - } - - @Override - public long[] getLongArray(int index) { - return reader.getLongArray(index); - } - - @Override - public float[] getFloatArray(int index) { - return reader.getFloatArray(index); - } - - @Override - public double[] getDoubleArray(int index) { - return reader.getDoubleArray(index); - } - - @Override - public boolean[] getBooleanArray(int index) { - return reader.getBooleanArray(index); - } - - @Override - public short[] getShortArray(int index) { - return reader.getShortArray(index); - } - - @Override - public String[] getStringArray(int index) { - return reader.getStringArray(index); - } - - @Override - public Object[] getObjectArray(int index) { - return reader.getObjectArray(index); - } - - @Override - public Object[] getTuple(int index) { - return reader.getTuple(index); - } - - @Override - public Object[] getTuple(String colName) { - return reader.getTuple(colName); - } - - @Override - public byte getEnum8(String colName) { - return reader.getEnum8(colName); - } - - @Override - public byte getEnum8(int index) { - return reader.getEnum8(index); - } - - @Override - public short getEnum16(String colName) { - return reader.getEnum16(colName); - } - - @Override - public short getEnum16(int index) { - return reader.getEnum16(index); - } - - @Override - public LocalDate getLocalDate(String colName) { - return reader.getLocalDate(colName); - } - - @Override - public LocalDate getLocalDate(int index) { - return reader.getLocalDate(index); - } - - @Override - public LocalTime getLocalTime(String colName) { - return reader.getLocalTime(colName); - } - - @Override - public LocalTime getLocalTime(int index) { - return reader.getLocalTime(index); - } - - @Override - public LocalDateTime getLocalDateTime(String colName) { - return reader.getLocalDateTime(colName); - } - - @Override - public LocalDateTime getLocalDateTime(int index) { - return reader.getLocalDateTime(index); - } - - @Override - public OffsetDateTime getOffsetDateTime(String colName) { - return reader.getOffsetDateTime(colName); - } - - @Override - public OffsetDateTime getOffsetDateTime(int index) { - return reader.getOffsetDateTime(index); - } - - @Override - public Object getObject(String colName) { - return reader.readValue(colName); - } - - @Override - public Object getObject(int index) { - return reader.readValue(index); - } - - @Override - public ClickHouseBitmap getClickHouseBitmap(String colName) { - return reader.readValue(colName); - } - - @Override - public ClickHouseBitmap getClickHouseBitmap(int index) { - return reader.readValue(index); - } - - @Override - public TableSchema getSchema() { - return reader.getSchema(); - } - - @Override - public Map getValues() { - return this.getSchema().getColumns().stream().collect(Collectors.toMap( - ClickHouseColumn::getColumnName, - column -> this.getObject(column.getColumnName()))); - } -} +package com.clickhouse.client.api.data_formats.internal; + +import com.clickhouse.client.api.data_formats.ClickHouseBinaryFormatReader; +import com.clickhouse.client.api.metadata.TableSchema; +import com.clickhouse.client.api.query.GenericRecord; +import com.clickhouse.data.ClickHouseColumn; +import com.clickhouse.data.value.*; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.time.*; +import java.time.temporal.TemporalAmount; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.stream.Collectors; + +public class BinaryReaderBackedRecord implements GenericRecord { + + private final ClickHouseBinaryFormatReader reader; + + public BinaryReaderBackedRecord(ClickHouseBinaryFormatReader reader) { + this.reader = reader; + } + + @Override + public String getString(String colName) { + return reader.getString(colName); + } + + @Override + public byte getByte(String colName) { + return reader.getByte(colName); + } + + @Override + public short getShort(String colName) { + return reader.getShort(colName); + } + + @Override + public int getInteger(String colName) { + return reader.getInteger(colName); + } + + @Override + public long getLong(String colName) { + return reader.getLong(colName); + } + + @Override + public float getFloat(String colName) { + return reader.getFloat(colName); + } + + @Override + public double getDouble(String colName) { + return reader.getDouble(colName); + } + + @Override + public boolean getBoolean(String colName) { + return reader.getBoolean(colName); + } + + @Override + public BigInteger getBigInteger(String colName) { + return reader.getBigInteger(colName); + } + + @Override + public BigDecimal getBigDecimal(String colName) { + return reader.getBigDecimal(colName); + } + + @Override + public Instant getInstant(String colName) { + return reader.getInstant(colName); + } + + @Override + public ZonedDateTime getZonedDateTime(String colName) { + return reader.getZonedDateTime(colName); + } + + @Override + public Duration getDuration(String colName) { + return reader.getDuration(colName); + } + + @Override + public TemporalAmount getTemporalAmount(String colName) { + return reader.getTemporalAmount(colName); + } + + @Override + public Inet4Address getInet4Address(String colName) { + return reader.getInet4Address(colName); + } + + @Override + public Inet6Address getInet6Address(String colName) { + return reader.getInet6Address(colName); + } + + @Override + public UUID getUUID(String colName) { + return reader.getUUID(colName); + } + + @Override + public ClickHouseGeoPointValue getGeoPoint(String colName) { + return reader.getGeoPoint(colName); + } + + @Override + public ClickHouseGeoRingValue getGeoRing(String colName) { + return reader.getGeoRing(colName); + } + + @Override + public ClickHouseGeoPolygonValue getGeoPolygon(String colName) { + return reader.getGeoPolygon(colName); + } + + @Override + public ClickHouseGeoMultiPolygonValue getGeoMultiPolygon(String colName) { + return reader.getGeoMultiPolygon(colName); + } + + @Override + public List getList(String colName) { + return reader.getList(colName); + } + + @Override + public byte[] getByteArray(String colName) { + return reader.getByteArray(colName); + } + + @Override + public int[] getIntArray(String colName) { + return reader.getIntArray(colName); + } + + @Override + public long[] getLongArray(String colName) { + return reader.getLongArray(colName); + } + + @Override + public float[] getFloatArray(String colName) { + return reader.getFloatArray(colName); + } + + @Override + public double[] getDoubleArray(String colName) { + return reader.getDoubleArray(colName); + } + + @Override + public boolean[] getBooleanArray(String colName) { + return reader.getBooleanArray(colName); + } + + @Override + public short[] getShortArray(String colName) { + return reader.getShortArray(colName); + } + + @Override + public String[] getStringArray(String colName) { + return reader.getStringArray(colName); + } + + @Override + public Object[] getObjectArray(String colName) { + return reader.getObjectArray(colName); + } + + @Override + public String getString(int index) { + return reader.getString(index); + } + + @Override + public boolean hasValue(int colIndex) { + return reader.hasValue(colIndex); + } + + @Override + public boolean hasValue(String colName) { + return reader.hasValue(colName); + } + + @Override + public byte getByte(int index) { + return reader.getByte(index); + } + + @Override + public short getShort(int index) { + return reader.getShort(index); + } + + @Override + public int getInteger(int index) { + return reader.getInteger(index); + } + + @Override + public long getLong(int index) { + return reader.getLong(index); + } + + @Override + public float getFloat(int index) { + return reader.getFloat(index); + } + + @Override + public double getDouble(int index) { + return reader.getDouble(index); + } + + @Override + public boolean getBoolean(int index) { + return reader.getBoolean(index); + } + + @Override + public BigInteger getBigInteger(int index) { + return reader.getBigInteger(index); + } + + @Override + public BigDecimal getBigDecimal(int index) { + return reader.getBigDecimal(index); + } + + @Override + public Instant getInstant(int index) { + return reader.getInstant(index); + } + + @Override + public ZonedDateTime getZonedDateTime(int index) { + return reader.getZonedDateTime(index); + } + + @Override + public Duration getDuration(int index) { + return reader.getDuration(index); + } + + @Override + public TemporalAmount getTemporalAmount(int index) { + return reader.getTemporalAmount(index); + } + + @Override + public Inet4Address getInet4Address(int index) { + return reader.getInet4Address(index); + } + + @Override + public Inet6Address getInet6Address(int index) { + return reader.getInet6Address(index); + } + + @Override + public UUID getUUID(int index) { + return reader.getUUID(index); + } + + @Override + public ClickHouseGeoPointValue getGeoPoint(int index) { + return reader.getGeoPoint(index); + } + + @Override + public ClickHouseGeoRingValue getGeoRing(int index) { + return reader.getGeoRing(index); + } + + @Override + public ClickHouseGeoPolygonValue getGeoPolygon(int index) { + return reader.getGeoPolygon(index); + } + + @Override + public ClickHouseGeoMultiPolygonValue getGeoMultiPolygon(int index) { + return reader.getGeoMultiPolygon(index); + } + + @Override + public List getList(int index) { + return reader.getList(index); + } + + @Override + public byte[] getByteArray(int index) { + return reader.getByteArray(index); + } + + @Override + public int[] getIntArray(int index) { + return reader.getIntArray(index); + } + + @Override + public long[] getLongArray(int index) { + return reader.getLongArray(index); + } + + @Override + public float[] getFloatArray(int index) { + return reader.getFloatArray(index); + } + + @Override + public double[] getDoubleArray(int index) { + return reader.getDoubleArray(index); + } + + @Override + public boolean[] getBooleanArray(int index) { + return reader.getBooleanArray(index); + } + + @Override + public short[] getShortArray(int index) { + return reader.getShortArray(index); + } + + @Override + public String[] getStringArray(int index) { + return reader.getStringArray(index); + } + + @Override + public Object[] getObjectArray(int index) { + return reader.getObjectArray(index); + } + + @Override + public Object[] getTuple(int index) { + return reader.getTuple(index); + } + + @Override + public Object[] getTuple(String colName) { + return reader.getTuple(colName); + } + + @Override + public byte getEnum8(String colName) { + return reader.getEnum8(colName); + } + + @Override + public byte getEnum8(int index) { + return reader.getEnum8(index); + } + + @Override + public short getEnum16(String colName) { + return reader.getEnum16(colName); + } + + @Override + public short getEnum16(int index) { + return reader.getEnum16(index); + } + + @Override + public LocalDate getLocalDate(String colName) { + return reader.getLocalDate(colName); + } + + @Override + public LocalDate getLocalDate(int index) { + return reader.getLocalDate(index); + } + + @Override + public LocalTime getLocalTime(String colName) { + return reader.getLocalTime(colName); + } + + @Override + public LocalTime getLocalTime(int index) { + return reader.getLocalTime(index); + } + + @Override + public LocalDateTime getLocalDateTime(String colName) { + return reader.getLocalDateTime(colName); + } + + @Override + public LocalDateTime getLocalDateTime(int index) { + return reader.getLocalDateTime(index); + } + + @Override + public OffsetDateTime getOffsetDateTime(String colName) { + return reader.getOffsetDateTime(colName); + } + + @Override + public OffsetDateTime getOffsetDateTime(int index) { + return reader.getOffsetDateTime(index); + } + + @Override + public Object getObject(String colName) { + return reader.readValue(colName); + } + + @Override + public Object getObject(int index) { + return reader.readValue(index); + } + + @Override + public ClickHouseBitmap getClickHouseBitmap(String colName) { + return reader.readValue(colName); + } + + @Override + public ClickHouseBitmap getClickHouseBitmap(int index) { + return reader.readValue(index); + } + + @Override + public TableSchema getSchema() { + return reader.getSchema(); + } + + @Override + public Map getValues() { + return this.getSchema().getColumns().stream().collect(Collectors.toMap( + ClickHouseColumn::getColumnName, + column -> this.getObject(column.getColumnName()))); + } +} diff --git a/docs/features.md b/docs/features.md index f274ca578..47a0f6299 100644 --- a/docs/features.md +++ b/docs/features.md @@ -49,7 +49,7 @@ Compatibility-sensitive traits: - Schema and database context: Supports database selection through URL, `setSchema`, `USE`, and statement-level settings. - Non-transactional operation: Exposes ClickHouse-appropriate transaction behavior with auto-commit semantics and unsupported transactional features. - Statement execution: Supports `execute`, `executeQuery`, `executeUpdate`, large update counts, and forward-only/read-only statements. -- Query cancellation and timeout: Supports JDBC query timeout handling and query cancellation through server-side `KILL QUERY`. +- Query cancellation and timeout: Supports JDBC query timeout handling and query cancellation through server-side `KILL QUERY`, with optional JDBC `cluster_name` property support to add `ON CLUSTER ''` for cluster-wide cancellation. - Batch execution: Supports batched statements and prepared-statement batches, including multi-row rewrite for eligible `INSERT ... VALUES` statements. - Prepared statements: Supports `?` parameters through client-side SQL rendering and validates that all parameters are bound before execution. - SQL parsing and classification: Classifies SQL to distinguish queries, updates, inserts, `USE`, and role-changing statements, with selectable parser backends. @@ -67,6 +67,7 @@ Compatibility-sensitive traits: Compatibility-sensitive traits: - Prepared statements are client-side SQL rendering, not server-side prepared statements. Changes to literal encoding or placeholder parsing are externally visible behavior. +- JDBC `cluster_name` is compatibility-sensitive for cancellation behavior: when configured, `Statement.cancel()` issues `KILL QUERY ON CLUSTER ''`, and when omitted it falls back to a local `KILL QUERY`. - String parameters are escaped with backslash-based escaping: backslashes are doubled and single quotes are backslash-escaped before values are wrapped in single quotes. - `?` placeholder detection is SQL-aware and should not treat question marks inside quoted strings, quoted identifiers, comments, casts, or similar syntax as bind parameters. - String-like ClickHouse values have stable JDBC expectations: `String`, `FixedString`, and `Enum` values are returned as strings, while `UUID` is available both as `getString()` and `getObject(..., UUID.class)`. diff --git a/jdbc-v2/src/main/java/com/clickhouse/jdbc/ConnectionImpl.java b/jdbc-v2/src/main/java/com/clickhouse/jdbc/ConnectionImpl.java index 1abbfb063..e84d4a73f 100644 --- a/jdbc-v2/src/main/java/com/clickhouse/jdbc/ConnectionImpl.java +++ b/jdbc-v2/src/main/java/com/clickhouse/jdbc/ConnectionImpl.java @@ -74,7 +74,8 @@ public ConnectionImpl(String url, Properties info) throws SQLException { try { this.url = url;//Raw URL this.config = new JdbcConfiguration(url, info); - this.cluster = config.getDriverProperty(DriverProperties.CLUSTER_NAME.getKey(), DriverProperties.CLUSTER_NAME.getDefaultValue()); + final String tmpClusterName = config.getDriverProperty(DriverProperties.CLUSTER_NAME.getKey(), DriverProperties.CLUSTER_NAME.getDefaultValue()); + this.cluster = tmpClusterName == null ? null : tmpClusterName.trim(); this.onCluster = this.cluster != null && !this.cluster.trim().isEmpty(); this.appName = ""; this.readOnly = false; diff --git a/jdbc-v2/src/main/java/com/clickhouse/jdbc/StatementImpl.java b/jdbc-v2/src/main/java/com/clickhouse/jdbc/StatementImpl.java index f50546393..614f6603c 100644 --- a/jdbc-v2/src/main/java/com/clickhouse/jdbc/StatementImpl.java +++ b/jdbc-v2/src/main/java/com/clickhouse/jdbc/StatementImpl.java @@ -334,7 +334,7 @@ public void cancel() throws SQLException { } try (QueryResponse response = connection.getClient().query(String.format("KILL QUERY%sWHERE query_id = '%s'", - connection.onCluster ? " ON CLUSTER " + connection.cluster + " " : " ", + connection.onCluster ? " ON CLUSTER '" + connection.cluster + "' " : " ", lastQueryId), connection.getDefaultQuerySettings()).get()){ LOG.debug("Query {} was killed by {}", lastQueryId, response.getQueryId()); } catch (Exception e) { diff --git a/jdbc-v2/src/test/java/com/clickhouse/jdbc/StatementTest.java b/jdbc-v2/src/test/java/com/clickhouse/jdbc/StatementTest.java index 66bbd36a5..ab1144071 100644 --- a/jdbc-v2/src/test/java/com/clickhouse/jdbc/StatementTest.java +++ b/jdbc-v2/src/test/java/com/clickhouse/jdbc/StatementTest.java @@ -9,6 +9,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.testng.Assert; +import org.testng.SkipException; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; @@ -581,12 +582,13 @@ public void testConnectionExhaustion() throws Exception { @Test(groups = {"integration"}) public void testCancelOnCluster() throws Exception { + // Generate non-existing cluster name to cause error String testCluster = "test_cluster_" + RandomStringUtils.randomAlphanumeric(10); Properties p = new Properties(); p.setProperty(DriverProperties.CLUSTER_NAME.getKey(), testCluster); try (Connection conn = getJdbcConnection(p)) { - try (StatementImpl stmt = (StatementImpl) conn.createStatement()) { - stmt.executeQuery("SELECT number FROM system.numbers LIMIT 1000000"); + try (StatementImpl stmt = (StatementImpl) conn.createStatement(); + ResultSet rs = stmt.executeQuery("SELECT 1")) { // to fill queryId try { stmt.cancel(); fail("Should have thrown an exception for missing cluster"); @@ -595,6 +597,14 @@ public void testCancelOnCluster() throws Exception { } } } + + // no cluster set - check no exception. actual cancelation is tested elsewhere + try (Connection conn = getJdbcConnection()) { + try (StatementImpl stmt = (StatementImpl) conn.createStatement(); + ResultSet rs = stmt.executeQuery("SELECT 1")) { // to fill queryId + stmt.cancel(); + } + } } @Test(groups = {"integration"}) From 065e5105733b93401f0328b1f6010e146b17c4e5 Mon Sep 17 00:00:00 2001 From: Sergey Chernov Date: Wed, 22 Apr 2026 11:23:06 -0700 Subject: [PATCH 4/9] Updated review instructions --- docs/ai-review.md | 4 +++ docs/review-template.md | 67 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+) create mode 100644 docs/review-template.md diff --git a/docs/ai-review.md b/docs/ai-review.md index 4c8d73f13..82b5350ce 100644 --- a/docs/ai-review.md +++ b/docs/ai-review.md @@ -4,6 +4,8 @@ Use this guide across Cursor, Claude, Copilot, and other AI assistants when revi This repository contains Java libraries and drivers for ClickHouse, including shared data types, HTTP clients, the v2 client, JDBC drivers, and R2DBC integration. +For a reusable review write-up format, use `docs/review-template.md`. + ## Role Act as an experienced maintainer and reviewer of a public Java library. @@ -169,6 +171,8 @@ When responding to a review request: 3. Use file or symbol references where helpful. 4. Keep any change summary brief and secondary. 5. If no issues are found, say so explicitly and mention residual risk or testing gaps. +6. Use `docs/review-template.md` when a structured review summary is helpful. +7. End with exactly one verdict: `ready to merge`, `ready for human review`, or `need changes`. In the final summary, state: diff --git a/docs/review-template.md b/docs/review-template.md new file mode 100644 index 000000000..cd6b1ec5a --- /dev/null +++ b/docs/review-template.md @@ -0,0 +1,67 @@ +# Review Template + +Use this template when writing a review summary for a pull request or patch. + +## Findings + +List findings first and order them by severity. + +### High + +- None. + +### Medium + +- None. + +### Low + +- None. + +## Human Review Instructions for Important Changes + +Ask a human reviewer to inspect important changes directly when the diff touches any of the following: + +- public API surface such as public classes, interfaces, methods, constructors, fields, or enums +- behavioral compatibility such as defaults, retries, timeouts, parsing, serialization, formatting, or exception behavior +- JDBC or R2DBC semantics, driver metadata, or type mappings +- documented features in `docs/features.md` +- security-sensitive code, authentication, credentials, or network-facing behavior +- cross-module contracts where the same concept exists in multiple modules + +When human review is needed, call out the exact risk and what should be checked, for example: + +- verify that no public signature changed +- verify that output format and serialized values are unchanged +- verify that existing callers keep the same default behavior +- verify that focused tests cover compatibility-sensitive paths +- verify whether `docs/features.md` or other docs need updates + +## Verdict + +Choose exactly one verdict: + +- `ready to merge`: no blocking issues found and no additional human sign-off is needed beyond normal review flow +- `ready for human review`: no blocking issues found by AI, but the change is important enough that a human should confirm compatibility, behavior, or product intent +- `need changes`: blocking issues, regressions, compatibility risk, or missing validation were found and should be addressed before merge + +## Copy/Paste Template + +```md +## Findings + +### High +- None. + +### Medium +- None. + +### Low +- None. + +## Human Review Instructions for Important Changes +- None. + +## Verdict +`ready to merge` +``` From ca15a40f0f650caa51e0a476255f49dc8c64aabb Mon Sep 17 00:00:00 2001 From: Sergey Chernov Date: Wed, 22 Apr 2026 11:45:33 -0700 Subject: [PATCH 5/9] disabled one useless test --- CHANGELOG.md | 10 ++++------ .../test/java/com/clickhouse/jdbc/GenericJDBCTest.java | 2 +- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ec8a1753..1dc7f9cd9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,15 +1,13 @@ ## 0.9.9 -### Bug Fixes - -- **[client-v2]** Fixed inconsistent use of `executionTimeout` parameter in `Client` component. The timeout was previously set in milliseconds but mistakenly retrieved and used in seconds in some places. Now it correctly uses milliseconds consistently. (https://github.com/ClickHouse/clickhouse-java/issues/2358) - -## 0.9.9 - ### New Features - **[jdbc-v2]** Added `cluster_name` configuration property to specify a target cluster for statements like `KILL QUERY` that require an `ON CLUSTER` clause to execute across all nodes. (https://github.com/ClickHouse/clickhouse-java/issues/2837) +### Bug Fixes + +- **[client-v2]** Fixed inconsistent use of `executionTimeout` parameter in `Client` component. The timeout was previously set in milliseconds but mistakenly retrieved and used in seconds in some places. Now it correctly uses milliseconds consistently. (https://github.com/ClickHouse/clickhouse-java/issues/2358) + ## 0.9.8 ### Improvements diff --git a/clickhouse-jdbc/src/test/java/com/clickhouse/jdbc/GenericJDBCTest.java b/clickhouse-jdbc/src/test/java/com/clickhouse/jdbc/GenericJDBCTest.java index 36b312d04..0f9cf587c 100644 --- a/clickhouse-jdbc/src/test/java/com/clickhouse/jdbc/GenericJDBCTest.java +++ b/clickhouse-jdbc/src/test/java/com/clickhouse/jdbc/GenericJDBCTest.java @@ -31,7 +31,7 @@ public void connectionTest() throws SQLException { } } - @Test + @Test(enabled = false) // skipped to be removed after reviewing tests. public void connectionWithPropertiesTest() throws SQLException { Properties properties = new Properties(); properties.setProperty("user", "default"); From 920d36c870b2e8a7cd9622ed26d4f1c3c85f1b45 Mon Sep 17 00:00:00 2001 From: Sergey Chernov Date: Wed, 22 Apr 2026 14:26:21 -0700 Subject: [PATCH 6/9] rolled back encoding --- .../internal/BinaryReaderBackedRecord.java | 898 +++++++++--------- 1 file changed, 449 insertions(+), 449 deletions(-) diff --git a/client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/BinaryReaderBackedRecord.java b/client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/BinaryReaderBackedRecord.java index 4739ca83d..7faa8e0fe 100644 --- a/client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/BinaryReaderBackedRecord.java +++ b/client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/BinaryReaderBackedRecord.java @@ -1,449 +1,449 @@ -package com.clickhouse.client.api.data_formats.internal; - -import com.clickhouse.client.api.data_formats.ClickHouseBinaryFormatReader; -import com.clickhouse.client.api.metadata.TableSchema; -import com.clickhouse.client.api.query.GenericRecord; -import com.clickhouse.data.ClickHouseColumn; -import com.clickhouse.data.value.*; - -import java.math.BigDecimal; -import java.math.BigInteger; -import java.net.Inet4Address; -import java.net.Inet6Address; -import java.time.*; -import java.time.temporal.TemporalAmount; -import java.util.List; -import java.util.Map; -import java.util.UUID; -import java.util.stream.Collectors; - -public class BinaryReaderBackedRecord implements GenericRecord { - - private final ClickHouseBinaryFormatReader reader; - - public BinaryReaderBackedRecord(ClickHouseBinaryFormatReader reader) { - this.reader = reader; - } - - @Override - public String getString(String colName) { - return reader.getString(colName); - } - - @Override - public byte getByte(String colName) { - return reader.getByte(colName); - } - - @Override - public short getShort(String colName) { - return reader.getShort(colName); - } - - @Override - public int getInteger(String colName) { - return reader.getInteger(colName); - } - - @Override - public long getLong(String colName) { - return reader.getLong(colName); - } - - @Override - public float getFloat(String colName) { - return reader.getFloat(colName); - } - - @Override - public double getDouble(String colName) { - return reader.getDouble(colName); - } - - @Override - public boolean getBoolean(String colName) { - return reader.getBoolean(colName); - } - - @Override - public BigInteger getBigInteger(String colName) { - return reader.getBigInteger(colName); - } - - @Override - public BigDecimal getBigDecimal(String colName) { - return reader.getBigDecimal(colName); - } - - @Override - public Instant getInstant(String colName) { - return reader.getInstant(colName); - } - - @Override - public ZonedDateTime getZonedDateTime(String colName) { - return reader.getZonedDateTime(colName); - } - - @Override - public Duration getDuration(String colName) { - return reader.getDuration(colName); - } - - @Override - public TemporalAmount getTemporalAmount(String colName) { - return reader.getTemporalAmount(colName); - } - - @Override - public Inet4Address getInet4Address(String colName) { - return reader.getInet4Address(colName); - } - - @Override - public Inet6Address getInet6Address(String colName) { - return reader.getInet6Address(colName); - } - - @Override - public UUID getUUID(String colName) { - return reader.getUUID(colName); - } - - @Override - public ClickHouseGeoPointValue getGeoPoint(String colName) { - return reader.getGeoPoint(colName); - } - - @Override - public ClickHouseGeoRingValue getGeoRing(String colName) { - return reader.getGeoRing(colName); - } - - @Override - public ClickHouseGeoPolygonValue getGeoPolygon(String colName) { - return reader.getGeoPolygon(colName); - } - - @Override - public ClickHouseGeoMultiPolygonValue getGeoMultiPolygon(String colName) { - return reader.getGeoMultiPolygon(colName); - } - - @Override - public List getList(String colName) { - return reader.getList(colName); - } - - @Override - public byte[] getByteArray(String colName) { - return reader.getByteArray(colName); - } - - @Override - public int[] getIntArray(String colName) { - return reader.getIntArray(colName); - } - - @Override - public long[] getLongArray(String colName) { - return reader.getLongArray(colName); - } - - @Override - public float[] getFloatArray(String colName) { - return reader.getFloatArray(colName); - } - - @Override - public double[] getDoubleArray(String colName) { - return reader.getDoubleArray(colName); - } - - @Override - public boolean[] getBooleanArray(String colName) { - return reader.getBooleanArray(colName); - } - - @Override - public short[] getShortArray(String colName) { - return reader.getShortArray(colName); - } - - @Override - public String[] getStringArray(String colName) { - return reader.getStringArray(colName); - } - - @Override - public Object[] getObjectArray(String colName) { - return reader.getObjectArray(colName); - } - - @Override - public String getString(int index) { - return reader.getString(index); - } - - @Override - public boolean hasValue(int colIndex) { - return reader.hasValue(colIndex); - } - - @Override - public boolean hasValue(String colName) { - return reader.hasValue(colName); - } - - @Override - public byte getByte(int index) { - return reader.getByte(index); - } - - @Override - public short getShort(int index) { - return reader.getShort(index); - } - - @Override - public int getInteger(int index) { - return reader.getInteger(index); - } - - @Override - public long getLong(int index) { - return reader.getLong(index); - } - - @Override - public float getFloat(int index) { - return reader.getFloat(index); - } - - @Override - public double getDouble(int index) { - return reader.getDouble(index); - } - - @Override - public boolean getBoolean(int index) { - return reader.getBoolean(index); - } - - @Override - public BigInteger getBigInteger(int index) { - return reader.getBigInteger(index); - } - - @Override - public BigDecimal getBigDecimal(int index) { - return reader.getBigDecimal(index); - } - - @Override - public Instant getInstant(int index) { - return reader.getInstant(index); - } - - @Override - public ZonedDateTime getZonedDateTime(int index) { - return reader.getZonedDateTime(index); - } - - @Override - public Duration getDuration(int index) { - return reader.getDuration(index); - } - - @Override - public TemporalAmount getTemporalAmount(int index) { - return reader.getTemporalAmount(index); - } - - @Override - public Inet4Address getInet4Address(int index) { - return reader.getInet4Address(index); - } - - @Override - public Inet6Address getInet6Address(int index) { - return reader.getInet6Address(index); - } - - @Override - public UUID getUUID(int index) { - return reader.getUUID(index); - } - - @Override - public ClickHouseGeoPointValue getGeoPoint(int index) { - return reader.getGeoPoint(index); - } - - @Override - public ClickHouseGeoRingValue getGeoRing(int index) { - return reader.getGeoRing(index); - } - - @Override - public ClickHouseGeoPolygonValue getGeoPolygon(int index) { - return reader.getGeoPolygon(index); - } - - @Override - public ClickHouseGeoMultiPolygonValue getGeoMultiPolygon(int index) { - return reader.getGeoMultiPolygon(index); - } - - @Override - public List getList(int index) { - return reader.getList(index); - } - - @Override - public byte[] getByteArray(int index) { - return reader.getByteArray(index); - } - - @Override - public int[] getIntArray(int index) { - return reader.getIntArray(index); - } - - @Override - public long[] getLongArray(int index) { - return reader.getLongArray(index); - } - - @Override - public float[] getFloatArray(int index) { - return reader.getFloatArray(index); - } - - @Override - public double[] getDoubleArray(int index) { - return reader.getDoubleArray(index); - } - - @Override - public boolean[] getBooleanArray(int index) { - return reader.getBooleanArray(index); - } - - @Override - public short[] getShortArray(int index) { - return reader.getShortArray(index); - } - - @Override - public String[] getStringArray(int index) { - return reader.getStringArray(index); - } - - @Override - public Object[] getObjectArray(int index) { - return reader.getObjectArray(index); - } - - @Override - public Object[] getTuple(int index) { - return reader.getTuple(index); - } - - @Override - public Object[] getTuple(String colName) { - return reader.getTuple(colName); - } - - @Override - public byte getEnum8(String colName) { - return reader.getEnum8(colName); - } - - @Override - public byte getEnum8(int index) { - return reader.getEnum8(index); - } - - @Override - public short getEnum16(String colName) { - return reader.getEnum16(colName); - } - - @Override - public short getEnum16(int index) { - return reader.getEnum16(index); - } - - @Override - public LocalDate getLocalDate(String colName) { - return reader.getLocalDate(colName); - } - - @Override - public LocalDate getLocalDate(int index) { - return reader.getLocalDate(index); - } - - @Override - public LocalTime getLocalTime(String colName) { - return reader.getLocalTime(colName); - } - - @Override - public LocalTime getLocalTime(int index) { - return reader.getLocalTime(index); - } - - @Override - public LocalDateTime getLocalDateTime(String colName) { - return reader.getLocalDateTime(colName); - } - - @Override - public LocalDateTime getLocalDateTime(int index) { - return reader.getLocalDateTime(index); - } - - @Override - public OffsetDateTime getOffsetDateTime(String colName) { - return reader.getOffsetDateTime(colName); - } - - @Override - public OffsetDateTime getOffsetDateTime(int index) { - return reader.getOffsetDateTime(index); - } - - @Override - public Object getObject(String colName) { - return reader.readValue(colName); - } - - @Override - public Object getObject(int index) { - return reader.readValue(index); - } - - @Override - public ClickHouseBitmap getClickHouseBitmap(String colName) { - return reader.readValue(colName); - } - - @Override - public ClickHouseBitmap getClickHouseBitmap(int index) { - return reader.readValue(index); - } - - @Override - public TableSchema getSchema() { - return reader.getSchema(); - } - - @Override - public Map getValues() { - return this.getSchema().getColumns().stream().collect(Collectors.toMap( - ClickHouseColumn::getColumnName, - column -> this.getObject(column.getColumnName()))); - } -} +package com.clickhouse.client.api.data_formats.internal; + +import com.clickhouse.client.api.data_formats.ClickHouseBinaryFormatReader; +import com.clickhouse.client.api.metadata.TableSchema; +import com.clickhouse.client.api.query.GenericRecord; +import com.clickhouse.data.ClickHouseColumn; +import com.clickhouse.data.value.*; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.time.*; +import java.time.temporal.TemporalAmount; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.stream.Collectors; + +public class BinaryReaderBackedRecord implements GenericRecord { + + private final ClickHouseBinaryFormatReader reader; + + public BinaryReaderBackedRecord(ClickHouseBinaryFormatReader reader) { + this.reader = reader; + } + + @Override + public String getString(String colName) { + return reader.getString(colName); + } + + @Override + public byte getByte(String colName) { + return reader.getByte(colName); + } + + @Override + public short getShort(String colName) { + return reader.getShort(colName); + } + + @Override + public int getInteger(String colName) { + return reader.getInteger(colName); + } + + @Override + public long getLong(String colName) { + return reader.getLong(colName); + } + + @Override + public float getFloat(String colName) { + return reader.getFloat(colName); + } + + @Override + public double getDouble(String colName) { + return reader.getDouble(colName); + } + + @Override + public boolean getBoolean(String colName) { + return reader.getBoolean(colName); + } + + @Override + public BigInteger getBigInteger(String colName) { + return reader.getBigInteger(colName); + } + + @Override + public BigDecimal getBigDecimal(String colName) { + return reader.getBigDecimal(colName); + } + + @Override + public Instant getInstant(String colName) { + return reader.getInstant(colName); + } + + @Override + public ZonedDateTime getZonedDateTime(String colName) { + return reader.getZonedDateTime(colName); + } + + @Override + public Duration getDuration(String colName) { + return reader.getDuration(colName); + } + + @Override + public TemporalAmount getTemporalAmount(String colName) { + return reader.getTemporalAmount(colName); + } + + @Override + public Inet4Address getInet4Address(String colName) { + return reader.getInet4Address(colName); + } + + @Override + public Inet6Address getInet6Address(String colName) { + return reader.getInet6Address(colName); + } + + @Override + public UUID getUUID(String colName) { + return reader.getUUID(colName); + } + + @Override + public ClickHouseGeoPointValue getGeoPoint(String colName) { + return reader.getGeoPoint(colName); + } + + @Override + public ClickHouseGeoRingValue getGeoRing(String colName) { + return reader.getGeoRing(colName); + } + + @Override + public ClickHouseGeoPolygonValue getGeoPolygon(String colName) { + return reader.getGeoPolygon(colName); + } + + @Override + public ClickHouseGeoMultiPolygonValue getGeoMultiPolygon(String colName) { + return reader.getGeoMultiPolygon(colName); + } + + @Override + public List getList(String colName) { + return reader.getList(colName); + } + + @Override + public byte[] getByteArray(String colName) { + return reader.getByteArray(colName); + } + + @Override + public int[] getIntArray(String colName) { + return reader.getIntArray(colName); + } + + @Override + public long[] getLongArray(String colName) { + return reader.getLongArray(colName); + } + + @Override + public float[] getFloatArray(String colName) { + return reader.getFloatArray(colName); + } + + @Override + public double[] getDoubleArray(String colName) { + return reader.getDoubleArray(colName); + } + + @Override + public boolean[] getBooleanArray(String colName) { + return reader.getBooleanArray(colName); + } + + @Override + public short[] getShortArray(String colName) { + return reader.getShortArray(colName); + } + + @Override + public String[] getStringArray(String colName) { + return reader.getStringArray(colName); + } + + @Override + public Object[] getObjectArray(String colName) { + return reader.getObjectArray(colName); + } + + @Override + public String getString(int index) { + return reader.getString(index); + } + + @Override + public boolean hasValue(int colIndex) { + return reader.hasValue(colIndex); + } + + @Override + public boolean hasValue(String colName) { + return reader.hasValue(colName); + } + + @Override + public byte getByte(int index) { + return reader.getByte(index); + } + + @Override + public short getShort(int index) { + return reader.getShort(index); + } + + @Override + public int getInteger(int index) { + return reader.getInteger(index); + } + + @Override + public long getLong(int index) { + return reader.getLong(index); + } + + @Override + public float getFloat(int index) { + return reader.getFloat(index); + } + + @Override + public double getDouble(int index) { + return reader.getDouble(index); + } + + @Override + public boolean getBoolean(int index) { + return reader.getBoolean(index); + } + + @Override + public BigInteger getBigInteger(int index) { + return reader.getBigInteger(index); + } + + @Override + public BigDecimal getBigDecimal(int index) { + return reader.getBigDecimal(index); + } + + @Override + public Instant getInstant(int index) { + return reader.getInstant(index); + } + + @Override + public ZonedDateTime getZonedDateTime(int index) { + return reader.getZonedDateTime(index); + } + + @Override + public Duration getDuration(int index) { + return reader.getDuration(index); + } + + @Override + public TemporalAmount getTemporalAmount(int index) { + return reader.getTemporalAmount(index); + } + + @Override + public Inet4Address getInet4Address(int index) { + return reader.getInet4Address(index); + } + + @Override + public Inet6Address getInet6Address(int index) { + return reader.getInet6Address(index); + } + + @Override + public UUID getUUID(int index) { + return reader.getUUID(index); + } + + @Override + public ClickHouseGeoPointValue getGeoPoint(int index) { + return reader.getGeoPoint(index); + } + + @Override + public ClickHouseGeoRingValue getGeoRing(int index) { + return reader.getGeoRing(index); + } + + @Override + public ClickHouseGeoPolygonValue getGeoPolygon(int index) { + return reader.getGeoPolygon(index); + } + + @Override + public ClickHouseGeoMultiPolygonValue getGeoMultiPolygon(int index) { + return reader.getGeoMultiPolygon(index); + } + + @Override + public List getList(int index) { + return reader.getList(index); + } + + @Override + public byte[] getByteArray(int index) { + return reader.getByteArray(index); + } + + @Override + public int[] getIntArray(int index) { + return reader.getIntArray(index); + } + + @Override + public long[] getLongArray(int index) { + return reader.getLongArray(index); + } + + @Override + public float[] getFloatArray(int index) { + return reader.getFloatArray(index); + } + + @Override + public double[] getDoubleArray(int index) { + return reader.getDoubleArray(index); + } + + @Override + public boolean[] getBooleanArray(int index) { + return reader.getBooleanArray(index); + } + + @Override + public short[] getShortArray(int index) { + return reader.getShortArray(index); + } + + @Override + public String[] getStringArray(int index) { + return reader.getStringArray(index); + } + + @Override + public Object[] getObjectArray(int index) { + return reader.getObjectArray(index); + } + + @Override + public Object[] getTuple(int index) { + return reader.getTuple(index); + } + + @Override + public Object[] getTuple(String colName) { + return reader.getTuple(colName); + } + + @Override + public byte getEnum8(String colName) { + return reader.getEnum8(colName); + } + + @Override + public byte getEnum8(int index) { + return reader.getEnum8(index); + } + + @Override + public short getEnum16(String colName) { + return reader.getEnum16(colName); + } + + @Override + public short getEnum16(int index) { + return reader.getEnum16(index); + } + + @Override + public LocalDate getLocalDate(String colName) { + return reader.getLocalDate(colName); + } + + @Override + public LocalDate getLocalDate(int index) { + return reader.getLocalDate(index); + } + + @Override + public LocalTime getLocalTime(String colName) { + return reader.getLocalTime(colName); + } + + @Override + public LocalTime getLocalTime(int index) { + return reader.getLocalTime(index); + } + + @Override + public LocalDateTime getLocalDateTime(String colName) { + return reader.getLocalDateTime(colName); + } + + @Override + public LocalDateTime getLocalDateTime(int index) { + return reader.getLocalDateTime(index); + } + + @Override + public OffsetDateTime getOffsetDateTime(String colName) { + return reader.getOffsetDateTime(colName); + } + + @Override + public OffsetDateTime getOffsetDateTime(int index) { + return reader.getOffsetDateTime(index); + } + + @Override + public Object getObject(String colName) { + return reader.readValue(colName); + } + + @Override + public Object getObject(int index) { + return reader.readValue(index); + } + + @Override + public ClickHouseBitmap getClickHouseBitmap(String colName) { + return reader.readValue(colName); + } + + @Override + public ClickHouseBitmap getClickHouseBitmap(int index) { + return reader.readValue(index); + } + + @Override + public TableSchema getSchema() { + return reader.getSchema(); + } + + @Override + public Map getValues() { + return this.getSchema().getColumns().stream().collect(Collectors.toMap( + ClickHouseColumn::getColumnName, + column -> this.getObject(column.getColumnName()))); + } +} From d882f39bcd621ecf576d79b06ab7fc5ee0d63afb Mon Sep 17 00:00:00 2001 From: Sergey Chernov Date: Wed, 22 Apr 2026 14:31:56 -0700 Subject: [PATCH 7/9] corrected property name --- jdbc-v2/src/main/java/com/clickhouse/jdbc/DriverProperties.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jdbc-v2/src/main/java/com/clickhouse/jdbc/DriverProperties.java b/jdbc-v2/src/main/java/com/clickhouse/jdbc/DriverProperties.java index 4184c582a..f18071e3f 100644 --- a/jdbc-v2/src/main/java/com/clickhouse/jdbc/DriverProperties.java +++ b/jdbc-v2/src/main/java/com/clickhouse/jdbc/DriverProperties.java @@ -127,7 +127,7 @@ public enum DriverProperties { /** * Define cluster name for statement executions like KILL */ - CLUSTER_NAME("cluster_name", null), + CLUSTER_NAME("jdbc_cluster_name", null), ; From 3c8b1fc6d5a9ea85a1103f63d8cfbe3efceda7ca Mon Sep 17 00:00:00 2001 From: Sergey Chernov Date: Wed, 22 Apr 2026 14:47:10 -0700 Subject: [PATCH 8/9] fixed issue with sql injection --- jdbc-v2/src/main/java/com/clickhouse/jdbc/StatementImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jdbc-v2/src/main/java/com/clickhouse/jdbc/StatementImpl.java b/jdbc-v2/src/main/java/com/clickhouse/jdbc/StatementImpl.java index 614f6603c..c022ab0f0 100644 --- a/jdbc-v2/src/main/java/com/clickhouse/jdbc/StatementImpl.java +++ b/jdbc-v2/src/main/java/com/clickhouse/jdbc/StatementImpl.java @@ -334,7 +334,7 @@ public void cancel() throws SQLException { } try (QueryResponse response = connection.getClient().query(String.format("KILL QUERY%sWHERE query_id = '%s'", - connection.onCluster ? " ON CLUSTER '" + connection.cluster + "' " : " ", + connection.onCluster ? " ON CLUSTER `" + connection.cluster + "` " : " ", lastQueryId), connection.getDefaultQuerySettings()).get()){ LOG.debug("Query {} was killed by {}", lastQueryId, response.getQueryId()); } catch (Exception e) { From b777125bf1aa3ce18bea03f545f0fb224dbe0fe3 Mon Sep 17 00:00:00 2001 From: Sergey Chernov Date: Wed, 22 Apr 2026 14:52:42 -0700 Subject: [PATCH 9/9] used enquoteIdentifier to escape quotes in cluster name --- jdbc-v2/src/main/java/com/clickhouse/jdbc/ConnectionImpl.java | 2 +- jdbc-v2/src/main/java/com/clickhouse/jdbc/StatementImpl.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/jdbc-v2/src/main/java/com/clickhouse/jdbc/ConnectionImpl.java b/jdbc-v2/src/main/java/com/clickhouse/jdbc/ConnectionImpl.java index e84d4a73f..7417df63c 100644 --- a/jdbc-v2/src/main/java/com/clickhouse/jdbc/ConnectionImpl.java +++ b/jdbc-v2/src/main/java/com/clickhouse/jdbc/ConnectionImpl.java @@ -76,7 +76,7 @@ public ConnectionImpl(String url, Properties info) throws SQLException { this.config = new JdbcConfiguration(url, info); final String tmpClusterName = config.getDriverProperty(DriverProperties.CLUSTER_NAME.getKey(), DriverProperties.CLUSTER_NAME.getDefaultValue()); this.cluster = tmpClusterName == null ? null : tmpClusterName.trim(); - this.onCluster = this.cluster != null && !this.cluster.trim().isEmpty(); + this.onCluster = this.cluster != null && !this.cluster.isEmpty(); this.appName = ""; this.readOnly = false; this.holdability = ResultSet.HOLD_CURSORS_OVER_COMMIT; diff --git a/jdbc-v2/src/main/java/com/clickhouse/jdbc/StatementImpl.java b/jdbc-v2/src/main/java/com/clickhouse/jdbc/StatementImpl.java index c022ab0f0..2801450ed 100644 --- a/jdbc-v2/src/main/java/com/clickhouse/jdbc/StatementImpl.java +++ b/jdbc-v2/src/main/java/com/clickhouse/jdbc/StatementImpl.java @@ -334,7 +334,7 @@ public void cancel() throws SQLException { } try (QueryResponse response = connection.getClient().query(String.format("KILL QUERY%sWHERE query_id = '%s'", - connection.onCluster ? " ON CLUSTER `" + connection.cluster + "` " : " ", + connection.onCluster ? " ON CLUSTER " + SQLUtils.enquoteIdentifier(connection.cluster, true) + ' ' : ' ', lastQueryId), connection.getDefaultQuerySettings()).get()){ LOG.debug("Query {} was killed by {}", lastQueryId, response.getQueryId()); } catch (Exception e) {