Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@

- **[client-v2]** `Client.Builder#useBearerTokenAuth(String)` now stores the bearer token under the `access_token` configuration key (with the `Bearer ` prefix) instead of writing it directly into `http_header_authorization`. The HTTP wire format is unchanged, but the token is no longer observable through `Client#getReadOnlyConfig()` under the `http_header_authorization` key.

- **[client-v2]** The public `ClickHouseBinaryFormatWriter` interface gained two methods, `setString(String, byte[])` and `setString(int, byte[])`, for writing raw `String`/`FixedString` bytes. Code that only *uses* the interface is unaffected, but any third party that *implements* `ClickHouseBinaryFormatWriter` directly is source- and binary-incompatible until it adds these methods (recompiling against the new version is required; otherwise an `AbstractMethodError` can occur at runtime).

### New Features

- **[client-v2]** Added `Session` API to encapsulate and manage ClickHouse session settings (`session_id`, `session_check`, `session_timeout`, `session_timezone`) as a reusable object. The `Session` instance can be applied to any request settings using `applyTo()`, and session state can be cleared via `clearSession()`. Additionally, added `resetOption(String)` to `InsertSettings`, `QuerySettings`, and `CommonSettings` to allow removing specific settings. Settings explicitly set to `null` will not be sent to the server, which is useful for overriding global settings.
Expand All @@ -37,6 +39,8 @@

- **[client-v2, jdbc-v2]** Added support for ClickHouse `Geometry` type for ClickHouse `25.11+`, where `Geometry` changed from a `String` alias to `Variant(Point, Ring, LineString, MultiLineString, Polygon, MultiPolygon)` (client still compatible with older versions). Includes client read/write handling and JDBC type mapping for retrieving and inserting geometry values. Current writes infer the target geometry variant from array nesting depth, so `Ring` vs `LineString` and `Polygon` vs `MultiLineString` are not yet distinguishable through the generic `Geometry` write path. (https://github.com/ClickHouse/clickhouse-java/pull/2815)

- **[client-v2, jdbc-v2]** Added opt-in binary string support through the `binary_string_support` configuration property (or `Client.Builder#binaryStringSupport(boolean)`), disabled by default. The setting is resolved per operation from the merged client and query settings, so it can be overridden for a single request via the `binary_string_support` operation option (e.g. `QuerySettings#setOption(ClientConfigProperties.BINARY_STRING_SUPPORT.getKey(), true)`) independently of the client-level default. When enabled, top-level `String` and `FixedString` columns are read into a `StringValue` that preserves the raw bytes instead of decoding them into a `String`, allowing non-UTF-8/binary content to round-trip byte-for-byte. `StringValue` exposes the bytes via `toByteArray()`/`asByteBuffer()` and lazily decodes a `String` via `asString()` (UTF-8 by default, or a caller-supplied `Charset`). Values nested inside containers (`Array`, `Map`, `Tuple`, `Nested`, `Variant`) continue to be read as `String`, since those types are not expected to carry large/binary strings. On the JDBC side, `ResultSet#getBinaryStream(int)` and `ResultSet#getBinaryStream(String)` are now implemented (previously unsupported) and, together with `getBytes(...)`, return the raw column bytes.

- **[jdbc-v2]** `ResultSet#getObject(int|String, Map<String, Class<?>>)` now accepts ClickHouse type names as map keys in addition to the JDBC `SQLType` names it has always accepted. Only unwrapped type names are used for the lookup — `Nullable(...)` and `LowCardinality(...)` wrappers are stripped and do not affect resolution, so a key like `"Int32"` matches both `Int32` and `Nullable(Int32)` columns; keys like `"Nullable(Int32)"` are not recognized. Lookup order is the `ClickHouseDataType` enum name (e.g. `"Int32"`, `"String"`, `"DateTime"`) then the JDBC `SQLType` name (e.g. `"INTEGER"`, `"VARCHAR"`, `"TIMESTAMP"`); a missing entry leaves the value uncoerced. The feature is supported for primitive ClickHouse types only — `Array`, `Tuple`, `Map`, `Nested`, and geometry types are not supported and continue to be returned in their native form regardless of the user-supplied map. Existing maps keyed only by JDBC `SQLType` names continue to work unchanged.

### Bug Fixes
Expand Down
15 changes: 15 additions & 0 deletions client-v2/src/main/java/com/clickhouse/client/api/Client.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.clickhouse.client.api.command.CommandResponse;
import com.clickhouse.client.api.command.CommandSettings;
import com.clickhouse.client.api.data_formats.ClickHouseBinaryFormatReader;
import com.clickhouse.client.api.data_formats.ClickHouseFormatReader;
import com.clickhouse.client.api.data_formats.NativeFormatReader;
import com.clickhouse.client.api.data_formats.RowBinaryFormatReader;
import com.clickhouse.client.api.data_formats.RowBinaryWithNamesAndTypesFormatReader;
Expand Down Expand Up @@ -1119,6 +1120,20 @@ public Builder typeHintMapping(Map<ClickHouseDataType, Class<?>> typeHintMapping
return this;
}

/**
* Enables reading {@code String} and {@code FixedString} columns into an intermediate {@code byte[]}
* (a new array each time) instead of decoding them into a {@link String}. This improves working with
* large strings and allows {@link ClickHouseFormatReader#getByteArray} to be used more effectively. Can also be configured
* per operation.
*
* @param enable - if the feature is enabled
* @return this builder instance
*/
public Builder binaryStringSupport(boolean enable) {
this.configuration.put(ClientConfigProperties.BINARY_STRING_SUPPORT.getKey(), String.valueOf(enable));
return this;
}


/**
* SNI SSL parameter that will be set for each outbound SSL socket.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.clickhouse.client.api;

import com.clickhouse.client.api.data_formats.ClickHouseFormatReader;
import com.clickhouse.client.api.data_formats.internal.AbstractBinaryFormatReader;
import com.clickhouse.client.api.enums.SSLMode;
import com.clickhouse.client.api.internal.ClickHouseLZ4OutputStream;
Expand Down Expand Up @@ -181,6 +182,13 @@ public Object parseValue(String value) {
*/
TYPE_HINT_MAPPING("type_hint_mapping", Map.class),

/**
* When enabled, {@code String} and {@code FixedString} columns are read into an intermediate {@code byte[]}
* instead of decoding them into a {@link String}. Improves working with large strings and lets
* {@link ClickHouseFormatReader#getByteArray} be used more effectively. Can be configured per operation.
*/
BINARY_STRING_SUPPORT("binary_string_support", Boolean.class, "false"),

/**
* SNI SSL parameter that will be set for each outbound SSL socket.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,10 @@ public interface ClickHouseBinaryFormatWriter {

void setString(int colIndex, String value);

void setString(String column, byte[] value);

void setString(int colIndex, byte[] value);

void setDate(String column, LocalDate value);

void setDate(int colIndex, LocalDate value);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,10 +126,18 @@ public void writeString(String value) throws IOException {
BinaryStreamUtils.writeString(out, value);
}

public void writeString(byte[] value) throws IOException {
BinaryStreamUtils.writeString(out, value);
}

public void writeFixedString(String value, int len) throws IOException {
BinaryStreamUtils.writeFixedString(out, value, len);
}

public void writeFixedString(byte[] value, int len) throws IOException {
SerializerUtils.writeFixedStringBytes(out, value, len);
}

public void writeDate(ZonedDateTime value) throws IOException {
SerializerUtils.writeDate(out, value, value.getZone());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,16 @@ public void setString(int colIndex, String value) {
setValue(colIndex, value);
}

@Override
public void setString(String column, byte[] value) {
setValue(column, value);
}

@Override
public void setString(int colIndex, byte[] value) {
setValue(colIndex, value);
}

@Override
public void setDate(String column, LocalDate value) {
setValue(column, value);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public RowBinaryWithNamesFormatReader(InputStream inputStream,
TableSchema schema,
BinaryStreamReader.ByteBufferAllocator byteBufferAllocator,
Map<ClickHouseDataType, Class<?>> typeHintMapping) {
super(inputStream, querySettings, schema, byteBufferAllocator, typeHintMapping);
super(inputStream, querySettings, schema, byteBufferAllocator, typeHintMapping);
int nCol = 0;
try {
nCol = BinaryStreamReader.readVarInt(input);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@
private long row = -1; // before first row
private long lastNextCallTs; // for exception to detect slow reader

protected AbstractBinaryFormatReader(InputStream inputStream, QuerySettings querySettings, TableSchema schema,BinaryStreamReader.ByteBufferAllocator byteBufferAllocator, Map<ClickHouseDataType, Class<?>> defaultTypeHintMap) {
protected AbstractBinaryFormatReader(InputStream inputStream, QuerySettings querySettings, TableSchema schema, BinaryStreamReader.ByteBufferAllocator byteBufferAllocator, Map<ClickHouseDataType, Class<?>> defaultTypeHintMap) {
this.input = inputStream;
Map<String, Object> settings = querySettings == null ? Collections.emptyMap() : querySettings.getAllSettings();
Boolean useServerTimeZone = (Boolean) settings.get(ClientConfigProperties.USE_SERVER_TIMEZONE.getKey());
Expand All @@ -88,8 +88,12 @@
}
boolean jsonAsString = MapUtils.getFlag(settings,
ClientConfigProperties.serverSetting(ServerSettings.OUTPUT_FORMAT_BINARY_WRITE_JSON_AS_STRING), false);
// Binary string support is resolved from the (already merged client + operation) query settings so it can be
// toggled per operation, not just per client.
boolean binaryStringSupport = MapUtils.getFlag(settings,
ClientConfigProperties.BINARY_STRING_SUPPORT.getKey(), false);
this.binaryStreamReader = new BinaryStreamReader(inputStream, timeZone, LOG, byteBufferAllocator, jsonAsString,
defaultTypeHintMap);
defaultTypeHintMap, binaryStringSupport);
if (schema != null) {
setSchema(schema);
}
Expand Down Expand Up @@ -532,8 +536,9 @@
}
return (T)array;
} else if (componentType == byte.class) {
if (value instanceof String) {
return (T) ((String) value).getBytes(StandardCharsets.UTF_8);
byte[] bytes = stringLikeToBytes(value);
if (bytes != null) {
return (T) bytes;
} else if (value instanceof InetAddress) {
return (T) ((InetAddress) value).getAddress();
}
Expand Down Expand Up @@ -676,6 +681,24 @@
throw new ClientException("Column of type " + column.getDataType() + " cannot be converted to Instant");
}

/**
* Converts a string-like value into its raw bytes. For a {@link StringValue} the original bytes are
* returned without re-encoding (so binary content is preserved). For a {@link String} the bytes are
* produced using UTF-8, matching the historical behaviour. Returns {@code null} when the value is not
* a string-like type so callers can fall back to other handling.
*
* @param value value to convert
* @return raw bytes or {@code null} if the value is not string-like
*/
public static byte[] stringLikeToBytes(Object value) {
if (value instanceof StringValue) {
return ((StringValue) value).toByteArray();
} else if (value instanceof String) {
return ((String) value).getBytes(StandardCharsets.UTF_8);
}
return null;

Check warning on line 699 in client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/AbstractBinaryFormatReader.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Return an empty array instead of null.

See more on https://sonarcloud.io/project/issues?id=ClickHouse_clickhouse-java&issues=AZ7xVs7yBSP9yiGow8s3&open=AZ7xVs7yBSP9yiGow8s3&pullRequest=2887
}

static Instant objectToInstant(Object value) {
if (value instanceof LocalDateTime) {
LocalDateTime dateTime = (LocalDateTime) value;
Expand Down Expand Up @@ -866,6 +889,10 @@
BinaryStreamReader.ArrayValue array = (BinaryStreamReader.ArrayValue) value;
if (array.itemType == String.class) {
return (String[]) array.getArray();
} else if (array.itemType == StringValue.class) {
StringValue[] stringValues = (StringValue[]) array.getArray();
return Arrays.stream(stringValues)
.map(sv -> sv == null ? null : sv.asString()).toArray(String[]::new);
} else if (array.itemType == BinaryStreamReader.EnumValue.class) {
BinaryStreamReader.EnumValue[] enumValues = (BinaryStreamReader.EnumValue[]) array.getArray();
return Arrays.stream(enumValues).map(BinaryStreamReader.EnumValue::getName).toArray(String[]::new);
Expand Down
Loading
Loading