Skip to content

Commit ba935c1

Browse files
committed
Merge remote-tracking branch 'origin/v0.10.0' into sync/release_0.10.0
2 parents b54a2de + 9dd3229 commit ba935c1

27 files changed

Lines changed: 2072 additions & 44 deletions

CHANGELOG.md

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,8 @@
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[])` 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).
119+
118120
- **[client-v2]** HTTP `503 Service Unavailable` responses are now surfaced as a connection-style failure (
119121
`java.net.ConnectException`) and are retried by default. Previously a `503` was treated as a server error (
120122
`ServerException`) and fell under the `ServerRetryable` fault cause. It has been moved to the `ConnectTimeout` fault
@@ -186,12 +188,25 @@
186188
to be returned in their native form regardless of the user-supplied map. Existing maps keyed only by JDBC `SQLType`
187189
names continue to work unchanged. (https://github.com/ClickHouse/clickhouse-java/pull/2865)
188190

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

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

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

259+
- **[jdbc-v2]** Fixed `ResultSet.getObject` leaking the internal `StringValue` holder for `String`/`FixedString`
260+
columns when `binary_string_support` is enabled. `getObject(column, byte[].class)` now returns the exact raw bytes,
261+
and `getObject(column, Object.class)` and the no-type `getObject(column)` overloads now return a decoded `String`
262+
instead of the internal holder.
263+
244264
## 0.9.8
245265

246266
### 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)