Skip to content

Commit dd09060

Browse files
authored
Merge pull request #2952 from ClickHouse/sync/release_0.10.0
Sync/release 0.10.0
2 parents b54a2de + c74bd36 commit dd09060

27 files changed

Lines changed: 2089 additions & 50 deletions

CHANGELOG.md

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@
6767
The Gradle wrapper and build scripts were removed and each project now has a standalone `pom.xml`.
6868
(https://github.com/ClickHouse/clickhouse-java/issues/2915)
6969

70-
## 0.10.0-rc1,
70+
## 0.10.0,
7171

7272
[Release Migration Guide](docs/releases/0_10_0.md)
7373

@@ -115,6 +115,12 @@
115115
previously set in milliseconds but mistakenly retrieved and used in seconds in some places. Now it correctly uses
116116
milliseconds consistently. (https://github.com/ClickHouse/clickhouse-java/issues/2358)
117117

118+
- **[client-v2]** The public `ClickHouseBinaryFormatWriter` interface gained two methods, `setString(String, byte[])`
119+
and `setString(int, byte[])`, for writing raw `String`/`FixedString` bytes. Code that only *uses* the interface is
120+
unaffected, but any third party that *implements* `ClickHouseBinaryFormatWriter` directly is source- and
121+
binary-incompatible until it adds these methods (recompiling against the new version is required; otherwise an
122+
`AbstractMethodError` can occur at runtime).
123+
118124
- **[client-v2]** HTTP `503 Service Unavailable` responses are now surfaced as a connection-style failure (
119125
`java.net.ConnectException`) and are retried by default. Previously a `503` was treated as a server error (
120126
`ServerException`) and fell under the `ServerRetryable` fault cause. It has been moved to the `ConnectTimeout` fault
@@ -186,12 +192,25 @@
186192
to be returned in their native form regardless of the user-supplied map. Existing maps keyed only by JDBC `SQLType`
187193
names continue to work unchanged. (https://github.com/ClickHouse/clickhouse-java/pull/2865)
188194

189-
- **[jdbc-v2]** Added support of custom mapping for JDBC types. Mainly used in cases when big integers should be
195+
- **[jdbc-v2]** Added support of custom mapping for JDBC types. Mainly used in cases when big integers should be
190196
presented as string. Use `DriverProperties.JDBC_TYPE_MAPPINGS` (`jdbc_type_mappings`) and set needed type mapping
191197
as `key=value[,]` list (For example, `Int32=Long,UInt64=String`). Deprecation notice: V1 property `typeMappings` is
192198
supported but will be removed. Please migrate to the new property.
193199
(https://github.com/ClickHouse/clickhouse-java/issues/2858)
194200

201+
- **[client-v2, jdbc-v2]** Added opt-in binary string support through the `binary_string_support` configuration property
202+
(or `Client.Builder#binaryStringSupport(boolean)`), disabled by default. The setting is resolved per operation from
203+
the merged client and query settings, so it can be overridden for a single request via the `binary_string_support`
204+
operation option (e.g. `QuerySettings#setOption(ClientConfigProperties.BINARY_STRING_SUPPORT.getKey(), true)`)
205+
independently of the client-level default. When enabled, top-level `String` and `FixedString` columns are read
206+
into a `StringValue` that preserves the raw bytes instead of decoding them into a `String`, allowing non-UTF-8/binary
207+
content to round-trip byte-for-byte. `StringValue` exposes the bytes via `toByteArray()`/`asByteBuffer()` and
208+
lazily decodes a `String` via `asString()` (UTF-8 by default, or a caller-supplied `Charset`). Values nested inside
209+
containers (`Array`, `Map`, `Tuple`, `Nested`, `Variant`) continue to be read as `String`, since those types are not
210+
expected to carry large/binary strings. On the JDBC side, `ResultSet#getBinaryStream(int)` and
211+
`ResultSet#getBinaryStream(String)` are now implemented (previously unsupported) and, together with `getBytes(...)`,
212+
return the raw column bytes.
213+
195214
- **[client-v2]** Added `Client#cancelTransportRequest(String queryId)` to cancel an in-flight request that has not yet
196215
received a response from the server, identified by the query id supplied in the operation settings. This aborts the
197216
request on the client side (cancels the underlying IO operation) but does **not** issue a `KILL QUERY` on the server,
@@ -214,7 +233,10 @@ like `ch_db_01`. This is mostly used in k8s environment. (https://github.com/Cli
214233

215234
- **[repo]** Added a contribution guide. Please review and send us your feedback. (https://github.com/ClickHouse/clickhouse-java/pull/2859)
216235

217-
- **[client-v2]** Added endpoint failover support: when multiple endpoints are configured and a request fails with a retryable error (connect timeout, connection refused, HTTP 503, etc.), the client now automatically retries against the next available endpoint instead of always targeting the first one. Failed endpoints are quarantined for 30 seconds before being retried. (https://github.com/ClickHouse/clickhouse-java/issues/2855)
236+
- **[client-v2]** Added endpoint failover support: when multiple endpoints are configured and a request fails with a
237+
retryable error (connect timeout, connection refused, HTTP 503, etc.), the client now automatically retries against
238+
the next available endpoint instead of always targeting the first one. Failed endpoints are quarantined for 30 seconds
239+
before being retried. (https://github.com/ClickHouse/clickhouse-java/issues/2855)
218240

219241
### Bug Fixes
220242

@@ -241,6 +263,11 @@ of `NULL` was not set and read. (https://github.com/ClickHouse/clickhouse-java/i
241263

242264
- **[jdbc-v2, client-v2]** Fixed writing nullable marker for nested `Tuple` and `Map values. (https://github.com/ClickHouse/clickhouse-java/issues/2721)
243265

266+
- **[jdbc-v2]** Fixed `ResultSet.getObject` leaking the internal `StringValue` holder for `String`/`FixedString`
267+
columns when `binary_string_support` is enabled. `getObject(column, byte[].class)` now returns the exact raw bytes,
268+
and `getObject(column, Object.class)` and the no-type `getObject(column)` overloads now return a decoded `String`
269+
instead of the internal holder.
270+
244271
## 0.9.8
245272

246273
### Improvements

client-v2/src/main/java/com/clickhouse/client/api/Client.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import com.clickhouse.client.api.command.CommandResponse;
44
import com.clickhouse.client.api.command.CommandSettings;
55
import com.clickhouse.client.api.data_formats.ClickHouseBinaryFormatReader;
6+
import com.clickhouse.client.api.data_formats.ClickHouseFormatReader;
67
import com.clickhouse.client.api.data_formats.NativeFormatReader;
78
import com.clickhouse.client.api.data_formats.RowBinaryFormatReader;
89
import com.clickhouse.client.api.data_formats.RowBinaryWithNamesAndTypesFormatReader;
@@ -1178,6 +1179,20 @@ public Builder typeHintMapping(Map<ClickHouseDataType, Class<?>> typeHintMapping
11781179
return this;
11791180
}
11801181

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

11821197
/**
11831198
* SNI SSL parameter that will be set for each outbound SSL socket.

client-v2/src/main/java/com/clickhouse/client/api/ClientConfigProperties.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.clickhouse.client.api;
22

3+
import com.clickhouse.client.api.data_formats.ClickHouseFormatReader;
34
import com.clickhouse.client.api.data_formats.internal.AbstractBinaryFormatReader;
45
import com.clickhouse.client.api.enums.SSLMode;
56
import com.clickhouse.client.api.internal.ClickHouseLZ4OutputStream;
@@ -185,6 +186,13 @@ public Object parseValue(String value) {
185186
*/
186187
TYPE_HINT_MAPPING("type_hint_mapping", Map.class),
187188

189+
/**
190+
* When enabled, {@code String} and {@code FixedString} columns are read into an intermediate {@code byte[]}
191+
* instead of decoding them into a {@link String}. Improves working with large strings and lets
192+
* {@link ClickHouseFormatReader#getByteArray} be used more effectively. Can be configured per operation.
193+
*/
194+
BINARY_STRING_SUPPORT("binary_string_support", Boolean.class, "false"),
195+
188196
/**
189197
* SNI SSL parameter that will be set for each outbound SSL socket.
190198
*/

client-v2/src/main/java/com/clickhouse/client/api/data_formats/ClickHouseBinaryFormatWriter.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,10 @@ public interface ClickHouseBinaryFormatWriter {
8484

8585
void setString(int colIndex, String value);
8686

87+
void setString(String column, byte[] value);
88+
89+
void setString(int colIndex, byte[] value);
90+
8791
void setDate(String column, LocalDate value);
8892

8993
void setDate(int colIndex, LocalDate value);

client-v2/src/main/java/com/clickhouse/client/api/data_formats/RowBinaryFormatSerializer.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,10 +126,18 @@ public void writeString(String value) throws IOException {
126126
BinaryStreamUtils.writeString(out, value);
127127
}
128128

129+
public void writeString(byte[] value) throws IOException {
130+
BinaryStreamUtils.writeString(out, value);
131+
}
132+
129133
public void writeFixedString(String value, int len) throws IOException {
130134
BinaryStreamUtils.writeFixedString(out, value, len);
131135
}
132136

137+
public void writeFixedString(byte[] value, int len) throws IOException {
138+
SerializerUtils.writeFixedStringBytes(out, value, len);
139+
}
140+
133141
public void writeDate(ZonedDateTime value) throws IOException {
134142
SerializerUtils.writeDate(out, value, value.getZone());
135143
}

client-v2/src/main/java/com/clickhouse/client/api/data_formats/RowBinaryFormatWriter.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,16 @@ public void setString(int colIndex, String value) {
203203
setValue(colIndex, value);
204204
}
205205

206+
@Override
207+
public void setString(String column, byte[] value) {
208+
setValue(column, value);
209+
}
210+
211+
@Override
212+
public void setString(int colIndex, byte[] value) {
213+
setValue(colIndex, value);
214+
}
215+
206216
@Override
207217
public void setDate(String column, LocalDate value) {
208218
setValue(column, value);

client-v2/src/main/java/com/clickhouse/client/api/data_formats/RowBinaryWithNamesFormatReader.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ public RowBinaryWithNamesFormatReader(InputStream inputStream,
2323
TableSchema schema,
2424
BinaryStreamReader.ByteBufferAllocator byteBufferAllocator,
2525
Map<ClickHouseDataType, Class<?>> typeHintMapping) {
26-
super(inputStream, querySettings, schema, byteBufferAllocator, typeHintMapping);
26+
super(inputStream, querySettings, schema, byteBufferAllocator, typeHintMapping);
2727
int nCol = 0;
2828
try {
2929
nCol = BinaryStreamReader.readVarInt(input);

client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/AbstractBinaryFormatReader.java

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ public abstract class AbstractBinaryFormatReader implements ClickHouseBinaryForm
7676
private long row = -1; // before first row
7777
private long lastNextCallTs; // for exception to detect slow reader
7878

79-
protected AbstractBinaryFormatReader(InputStream inputStream, QuerySettings querySettings, TableSchema schema,BinaryStreamReader.ByteBufferAllocator byteBufferAllocator, Map<ClickHouseDataType, Class<?>> defaultTypeHintMap) {
79+
protected AbstractBinaryFormatReader(InputStream inputStream, QuerySettings querySettings, TableSchema schema, BinaryStreamReader.ByteBufferAllocator byteBufferAllocator, Map<ClickHouseDataType, Class<?>> defaultTypeHintMap) {
8080
this.input = inputStream;
8181
Map<String, Object> settings = querySettings == null ? Collections.emptyMap() : querySettings.getAllSettings();
8282
Boolean useServerTimeZone = (Boolean) settings.get(ClientConfigProperties.USE_SERVER_TIMEZONE.getKey());
@@ -88,8 +88,12 @@ protected AbstractBinaryFormatReader(InputStream inputStream, QuerySettings quer
8888
}
8989
boolean jsonAsString = MapUtils.getFlag(settings,
9090
ClientConfigProperties.serverSetting(ServerSettings.OUTPUT_FORMAT_BINARY_WRITE_JSON_AS_STRING), false);
91+
// Binary string support is resolved from the (already merged client + operation) query settings so it can be
92+
// toggled per operation, not just per client.
93+
boolean binaryStringSupport = MapUtils.getFlag(settings,
94+
ClientConfigProperties.BINARY_STRING_SUPPORT.getKey(), false);
9195
this.binaryStreamReader = new BinaryStreamReader(inputStream, timeZone, LOG, byteBufferAllocator, jsonAsString,
92-
defaultTypeHintMap);
96+
defaultTypeHintMap, binaryStringSupport);
9397
if (schema != null) {
9498
setSchema(schema);
9599
}
@@ -533,8 +537,9 @@ private <T> T getPrimitiveArray(int index, Class<?> componentType) {
533537
}
534538
return (T)array;
535539
} else if (componentType == byte.class) {
536-
if (value instanceof String) {
537-
return (T) ((String) value).getBytes(StandardCharsets.UTF_8);
540+
byte[] bytes = stringLikeToBytes(value);
541+
if (bytes != null) {
542+
return (T) bytes;
538543
} else if (value instanceof InetAddress) {
539544
return (T) ((InetAddress) value).getAddress();
540545
}
@@ -677,6 +682,24 @@ public Instant getInstant(int index) {
677682
throw new ClientException("Column of type " + column.getDataType() + " cannot be converted to Instant");
678683
}
679684

685+
/**
686+
* Converts a string-like value into its raw bytes. For a {@link StringValue} the original bytes are
687+
* returned without re-encoding (so binary content is preserved). For a {@link String} the bytes are
688+
* produced using UTF-8, matching the historical behaviour. Returns {@code null} when the value is not
689+
* a string-like type so callers can fall back to other handling.
690+
*
691+
* @param value value to convert
692+
* @return raw bytes or {@code null} if the value is not string-like
693+
*/
694+
public static byte[] stringLikeToBytes(Object value) {
695+
if (value instanceof StringValue) {
696+
return ((StringValue) value).toByteArray();
697+
} else if (value instanceof String) {
698+
return ((String) value).getBytes(StandardCharsets.UTF_8);
699+
}
700+
return null;
701+
}
702+
680703
static Instant objectToInstant(Object value) {
681704
if (value instanceof LocalDateTime) {
682705
LocalDateTime dateTime = (LocalDateTime) value;
@@ -867,6 +890,10 @@ public String[] getStringArray(int index) {
867890
BinaryStreamReader.ArrayValue array = (BinaryStreamReader.ArrayValue) value;
868891
if (array.itemType == String.class) {
869892
return (String[]) array.getArray();
893+
} else if (array.itemType == StringValue.class) {
894+
StringValue[] stringValues = (StringValue[]) array.getArray();
895+
return Arrays.stream(stringValues)
896+
.map(sv -> sv == null ? null : sv.asString()).toArray(String[]::new);
870897
} else if (array.itemType == BinaryStreamReader.EnumValue.class) {
871898
BinaryStreamReader.EnumValue[] enumValues = (BinaryStreamReader.EnumValue[]) array.getArray();
872899
return Arrays.stream(enumValues).map(BinaryStreamReader.EnumValue::getName).toArray(String[]::new);

0 commit comments

Comments
 (0)