Skip to content

Commit ea311ea

Browse files
Merge remote-tracking branch 'origin/main' into polyglot/enum-null-non-nullable-npe
2 parents 13010eb + 1a1e0cc commit ea311ea

21 files changed

Lines changed: 1461 additions & 16 deletions

File tree

CHANGELOG.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,21 @@
22

33
[Release Migration Guide](docs/releases/0_11_0.md)
44

5+
### New Features
6+
7+
- **[client-v2, jdbc-v2]** Added support for the `BFloat16` data type (ClickHouse `24.11+`). `BFloat16` columns are read as
8+
Java `float` values (widening is lossless) and written from `float`/`Float` values, including through generic records, POJO
9+
binding, `Nullable(BFloat16)`, and `BFloat16` values held in `Dynamic`/`Variant` columns. On write the client keeps the
10+
high 16 bits of the `float`, matching the ClickHouse server's own `Float32``BFloat16` conversion. In the JDBC driver
11+
(`jdbc-v2`) `BFloat16` maps to `java.sql.Types.FLOAT` / `java.lang.Float` and is read and written through the standard
12+
`getFloat`/`setFloat` and `getObject` accessors, and reported as such by `ResultSetMetaData` and `DatabaseMetaData`.
13+
Previously reading or writing a `BFloat16` column failed with an
14+
unsupported-data-type error. (https://github.com/ClickHouse/clickhouse-java/issues/2279)
15+
- **[client-v2, jdbc-v2]** Added TLS cipher suite selection. `Client.Builder.setSSLCipherSuites(String...)` (client-v2)
16+
and the comma-separated `ssl_cipher_suites` connection property (client-v2 and jdbc-v2) restrict the cipher suites
17+
enabled on secure connections; when unset, the transport defaults are used. Cipher-suite selection is independent of the
18+
trust configuration and `ssl_mode`. (https://github.com/ClickHouse/clickhouse-java/issues/2882)
19+
520
### Bug Fixes
621

722
- **[client-v2]** Fixed binary array decoding for nullable element types so `Array(Nullable(Float64))` and similar columns now return boxed arrays such as `Double[]` instead of `Object[]`. This keeps null-supporting arrays aligned with their element type while preserving the existing `Object[]` fallback for Variant/Dynamic/Geometry arrays. (https://github.com/ClickHouse/clickhouse-java/issues/2846)
@@ -95,6 +110,17 @@
95110

96111
### New Features
97112

113+
- **[client-v2, jdbc-v2]** Added support for an application-supplied `javax.net.ssl.SSLContext`. In client-v2,
114+
`Client.Builder.setSSLContext(SSLContext)` hands the client a fully pre-built context that is used as is; in
115+
jdbc-v2 the same context may be passed as a live object in the connection `Properties` under the `ssl_context`
116+
key (added with `Properties.put`, since it is not a string). Trust/key material options cannot be combined with
117+
a custom context and are rejected; `ssl_mode` still applies but only to server hostname verification. This
118+
supports in-memory TLS material that must never be written to disk, including behind connection pools that only
119+
expose `java.util.Properties`.
120+
- Examples for client-v2 https://github.com/ClickHouse/clickhouse-java/blob/main/examples/client-v2/src/main/java/com/clickhouse/examples/client_v2/SSLExamples.java
121+
- Examples for jdbc-v2 https://github.com/ClickHouse/clickhouse-java/blob/main/examples/jdbc/src/main/java/com/clickhouse/examples/jdbc/SSLExamples.java
122+
(https://github.com/ClickHouse/clickhouse-java/pull/2918, https://github.com/ClickHouse/clickhouse-java/issues/2909)
123+
98124
- **[jdbc-v2, client-v2]** Implemented SSL modes configuration. Now it is possible to set `ssl_mode` to `DISABLED`,
99125
`TRUST`, `VERIFY_CA` and `STRICT`. Note for V1 users: `NONE` is supported only by JDBC driver and mapped to `TRUST`.
100126
Please migrate to the new naming.

clickhouse-data/src/main/java/com/clickhouse/data/ClickHouseDataType.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,7 @@ static Map<ClickHouseDataType, Set<Class<?>>> dataTypeClassMap() {
202202
map.put(String, setOf(String.class));
203203
map.put(Float64, setOf(float.class, Float.class, double.class, Double.class));
204204
map.put(Float32, setOf(float.class, Float.class));
205+
map.put(BFloat16, setOf(float.class, Float.class));
205206
map.put(Decimal, setOf(float.class, Float.class, double.class, Double.class, BigDecimal.class));
206207
map.put(Decimal256, setOf(float.class, Float.class, double.class, Double.class, BigDecimal.class));
207208
map.put(Decimal128, setOf(float.class, Float.class, double.class, Double.class, BigDecimal.class));

clickhouse-data/src/main/java/com/clickhouse/data/format/BinaryStreamUtils.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -985,6 +985,21 @@ public static void writeFloat32(OutputStream output, float value) throws IOExcep
985985
writeInt32(output, Float.floatToIntBits(value));
986986
}
987987

988+
/**
989+
* Write a bfloat16 value to given output stream. {@code BFloat16} occupies the
990+
* high 16 bits of the IEEE-754 {@code float} representation, so the low 16 bits of
991+
* the mantissa are dropped (truncated toward zero), which matches how the ClickHouse
992+
* server converts {@code Float32} to {@code BFloat16}.
993+
*
994+
* @param output non-null output stream
995+
* @param value float value to be stored as bfloat16
996+
* @throws IOException when failed to write value to output stream or reached
997+
* end of the stream
998+
*/
999+
public static void writeBFloat16(OutputStream output, float value) throws IOException {
1000+
writeInt16(output, (short) (Float.floatToIntBits(value) >>> 16));
1001+
}
1002+
9881003
/**
9891004
* Read a double value from given input stream.
9901005
*

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

Lines changed: 75 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
import java.time.ZoneId;
6161
import java.time.temporal.ChronoUnit;
6262
import java.util.ArrayList;
63+
import java.util.Arrays;
6364
import java.util.Collection;
6465
import java.util.Collections;
6566
import java.util.HashMap;
@@ -81,6 +82,8 @@
8182
import java.util.function.Supplier;
8283
import java.util.stream.Collectors;
8384

85+
import javax.net.ssl.SSLContext;
86+
8487
/**
8588
* <p>Client is the starting point for all interactions with ClickHouse. </p>
8689
*
@@ -148,8 +151,12 @@ public class Client implements AutoCloseable {
148151

149152
private Client(Collection<Endpoint> endpoints, Map<String,String> configuration,
150153
ExecutorService sharedOperationExecutor, ColumnToMethodMatchingStrategy columnToMethodMatchingStrategy,
151-
Object metricsRegistry, Supplier<String> queryIdGenerator, CredentialsManager cManager) {
154+
Object metricsRegistry, Supplier<String> queryIdGenerator, CredentialsManager cManager,
155+
SSLContext sslContext) {
152156
Map<String, Object> parsedConfiguration = new ConcurrentHashMap<>(ClientConfigProperties.parseConfigMap(configuration));
157+
if (sslContext != null) {
158+
parsedConfiguration.put(ClientConfigProperties.SSL_CONTEXT.getKey(), sslContext);
159+
}
153160
this.credentialsManager = cManager;
154161
this.session = Session.extractFrom(parsedConfiguration);
155162
this.configuration = new ConcurrentHashMap<>(parsedConfiguration);
@@ -270,6 +277,18 @@ public static class Builder {
270277
private ColumnToMethodMatchingStrategy columnToMethodMatchingStrategy;
271278
private Object metricRegistry = null;
272279
private Supplier<String> queryIdGenerator;
280+
private SSLContext sslContext = null;
281+
282+
// Trust/key material options that feed a context the client would otherwise build; none of them
283+
// may be combined with an application-supplied SSLContext (see build()).
284+
private static final ClientConfigProperties[] SSL_MATERIAL_PROPERTIES = {
285+
ClientConfigProperties.SSL_TRUST_STORE,
286+
ClientConfigProperties.SSL_KEYSTORE_TYPE,
287+
ClientConfigProperties.SSL_KEY_STORE_PASSWORD,
288+
ClientConfigProperties.SSL_KEY,
289+
ClientConfigProperties.CA_CERTIFICATE,
290+
ClientConfigProperties.SSL_CERTIFICATE,
291+
};
273292

274293
public Builder() {
275294
this.endpoints = new LinkedHashSet<>();
@@ -344,6 +363,11 @@ private Builder addEndpoint(Endpoint endpoint) {
344363
* @param value - configuration option value
345364
*/
346365
public Builder setOption(String key, String value) {
366+
if (key.equals(ClientConfigProperties.SSL_CONTEXT.getKey())) {
367+
throw new ClientMisconfigurationException("'" + ClientConfigProperties.SSL_CONTEXT.getKey()
368+
+ "' cannot be set as a string; supply a javax.net.ssl.SSLContext object via "
369+
+ "Client.Builder.setSSLContext(...)");
370+
}
347371
this.configuration.put(key, value);
348372
if (key.equals(ClientConfigProperties.PRODUCT_NAME.getKey())) {
349373
setClientName(value);
@@ -785,6 +809,36 @@ public Builder setSSLMode(SSLMode sslMode) {
785809
return this;
786810
}
787811

812+
/**
813+
* Restricts the TLS cipher suites the client may negotiate on secure connections. When set, only
814+
* the listed cipher suites are enabled on the SSL socket (subject to what the JVM and the server
815+
* support); when not set, the transport defaults are used (Apache HttpClient enables the JVM's
816+
* default suites minus those it considers weak). Suite names use the standard JSSE names, for
817+
* example {@code TLS_AES_256_GCM_SHA384}.
818+
*
819+
* @param cipherSuites cipher suite names to enable
820+
* @return same instance of the builder
821+
*/
822+
public Builder setSSLCipherSuites(String... cipherSuites) {
823+
this.configuration.put(ClientConfigProperties.SSL_CIPHER_SUITES.getKey(),
824+
ClientConfigProperties.commaSeparated(Arrays.asList(cipherSuites)));
825+
return this;
826+
}
827+
828+
/**
829+
* Supplies a pre-built {@link SSLContext}. When set, it is used as is instead of a context built
830+
* from the configured trust/key material (which then cannot be set alongside it). {@link SSLMode}
831+
* still applies, but only to server hostname verification: {@link SSLMode#STRICT} (default) enforces
832+
* it while {@link SSLMode#TRUST} and {@link SSLMode#VERIFY_CA} skip it.
833+
*
834+
* @param sslContext a fully configured SSL context; {@code null} clears any previously set context
835+
* @return same instance of the builder
836+
*/
837+
public Builder setSSLContext(SSLContext sslContext) {
838+
this.sslContext = sslContext;
839+
return this;
840+
}
841+
788842
/**
789843
* Configure client to use server timezone for date/datetime columns. Default is true.
790844
* If this options is selected then server timezone should be set as well.
@@ -1165,14 +1219,28 @@ public Client build() {
11651219

11661220
CredentialsManager cManager = new CredentialsManager(this.configuration);
11671221

1168-
if (configuration.containsKey(ClientConfigProperties.SSL_TRUST_STORE.getKey()) &&
1222+
// A textual 'ssl_context' can never be a live context (also rejected in setOption).
1223+
if (configuration.containsKey(ClientConfigProperties.SSL_CONTEXT.getKey())) {
1224+
throw new ClientMisconfigurationException("'" + ClientConfigProperties.SSL_CONTEXT.getKey()
1225+
+ "' cannot be set as a string; supply a javax.net.ssl.SSLContext object via "
1226+
+ "Client.Builder.setSSLContext(...)");
1227+
}
1228+
1229+
if (this.sslContext != null) {
1230+
// A custom SSLContext replaces any context the client would build, so trust/key material
1231+
// cannot be set alongside it. SSL_MODE (hostname verification) is still allowed.
1232+
for (ClientConfigProperties material : SSL_MATERIAL_PROPERTIES) {
1233+
if (configuration.containsKey(material.getKey())) {
1234+
throw new ClientMisconfigurationException("'" + material.getKey() + "' cannot be combined"
1235+
+ " with a custom SSLContext; the supplied context is used as is. Only 'ssl_mode'"
1236+
+ " (hostname verification) may be set alongside it.");
1237+
}
1238+
}
1239+
} else if (configuration.containsKey(ClientConfigProperties.SSL_TRUST_STORE.getKey()) &&
11691240
configuration.containsKey(ClientConfigProperties.SSL_CERTIFICATE.getKey())) {
11701241
throw new ClientMisconfigurationException("Trust store and certificates cannot be used together");
11711242
}
11721243

1173-
// A trust store and a CA certificate are not rejected here: for VERIFY_CA/STRICT the trust
1174-
// store takes precedence and the CA certificate is ignored with a warning (see createSSLContext).
1175-
11761244
// Resolve ssl_mode case-insensitively and normalize it to the canonical enum name so that
11771245
// downstream parsing is consistent and an unknown value is reported as a misconfiguration
11781246
// here instead of failing later with a generic enum-parsing error.
@@ -1229,7 +1297,8 @@ public Client build() {
12291297
}
12301298

12311299
return new Client(this.endpoints, this.configuration, this.sharedOperationExecutor,
1232-
this.columnToMethodMatchingStrategy, this.metricRegistry, this.queryIdGenerator, cManager);
1300+
this.columnToMethodMatchingStrategy, this.metricRegistry, this.queryIdGenerator, cManager,
1301+
this.sslContext);
12331302
}
12341303
}
12351304

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

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
import org.slf4j.Logger;
99
import org.slf4j.LoggerFactory;
1010

11+
import javax.net.ssl.SSLContext;
12+
1113
import java.util.ArrayList;
1214
import java.util.Arrays;
1315
import java.util.Collection;
@@ -118,6 +120,8 @@ public enum ClientConfigProperties {
118120

119121
SSL_MODE("ssl_mode", SSLMode.class, SSLMode.STRICT.name()),
120122

123+
SSL_CONTEXT("ssl_context", SSLContext.class),
124+
121125
RETRY_ON_FAILURE("retry", Integer.class, "3"),
122126

123127
INPUT_OUTPUT_FORMAT("format", ClickHouseFormat.class),
@@ -204,6 +208,35 @@ public Object parseValue(String value) {
204208
* See <a href="https://clickhouse.com/docs/operations/settings/query-level#custom_settings">ClickHouse Docs</a>
205209
*/
206210
CUSTOM_SETTINGS_PREFIX("custom_settings_prefix", String.class, "custom_"),
211+
212+
/**
213+
* Comma-separated list of TLS cipher suites the client is allowed to negotiate on secure connections.
214+
* When set, only these cipher suites are enabled on the SSL socket (subject to what the JVM and server
215+
* support); when unset, the transport defaults are used (Apache HttpClient enables the JVM's default
216+
* suites minus those it considers weak).
217+
* <p>
218+
* The value is parsed into a sanitized list here, in the config-parsing layer (not in transport code):
219+
* blank tokens produced by a leading, trailing or doubled comma are dropped and each surviving name is
220+
* trimmed, so consumers receive a ready-to-use list. A null, empty or whitespace-only entry would
221+
* otherwise be rejected by the SSL socket and break the handshake even alongside valid suites.
222+
* <p>
223+
* Appended at the end of the enum on purpose: adding a constant in the middle would shift the ordinal
224+
* of every following constant (see {@code docs/changes_checklist.md}).
225+
*/
226+
SSL_CIPHER_SUITES("ssl_cipher_suites", List.class) {
227+
@Override
228+
@SuppressWarnings("unchecked")
229+
public Object parseValue(String value) {
230+
List<String> suites = (List<String>) super.parseValue(value);
231+
if (suites == null) {
232+
return null;
233+
}
234+
return suites.stream()
235+
.filter(s -> s != null && !s.trim().isEmpty())
236+
.map(String::trim)
237+
.collect(Collectors.toList());
238+
}
239+
},
207240
;
208241

209242
private static final Logger LOG = LoggerFactory.getLogger(ClientConfigProperties.class);

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,7 @@ protected void setSchema(TableSchema schema) {
323323
case Int256:
324324
case UInt128:
325325
case UInt256:
326+
case BFloat16:
326327
case Float32:
327328
case Float64:
328329
case Decimal:

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

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,8 @@ public <T> T readValue(ClickHouseColumn column, Class<?> typeHint) throws IOExce
164164
return (T) readDecimal(ClickHouseDataType.Decimal128.getMaxPrecision(), scale);
165165
case Decimal256:
166166
return (T) readDecimal(ClickHouseDataType.Decimal256.getMaxPrecision(), scale);
167+
case BFloat16:
168+
return (T) (Float)readBFloat16LE();
167169
case Float32:
168170
return (T) (Float)readFloatLE();
169171
case Float64:
@@ -509,6 +511,18 @@ public float readFloatLE() throws IOException {
509511
return Float.intBitsToFloat(readIntLE());
510512
}
511513

514+
/**
515+
* Reads a little-endian {@code BFloat16} value from the internal input stream and
516+
* widens it to a {@code float}. {@code BFloat16} carries the high 16 bits of the
517+
* IEEE-754 {@code float} representation, so widening (shifting them back into the
518+
* high bits) is lossless.
519+
* @return float value
520+
* @throws IOException when IO error occurs
521+
*/
522+
public float readBFloat16LE() throws IOException {
523+
return Float.intBitsToFloat(readUnsignedShortLE() << 16);
524+
}
525+
512526
private static final byte[] B1 = new byte[8];
513527
/**
514528
* Reads a double value from the internal input stream.
@@ -1268,6 +1282,7 @@ public static boolean isReadToPrimitive(ClickHouseDataType dataType) {
12681282
case Int32:
12691283
case UInt32:
12701284
case Int64:
1285+
case BFloat16:
12711286
case Float32:
12721287
case Float64:
12731288
case Bool:
@@ -1473,8 +1488,6 @@ private ClickHouseColumn readDynamicData() throws IOException {
14731488
}
14741489
case AggregateFunction:
14751490
throw new ClientException("Aggregate functions are not supported yet");
1476-
case BFloat16:
1477-
throw new ClientException("BFloat16 is not supported yet");
14781491
default:
14791492
return ClickHouseColumn.of("v", type, false, 0, 0);
14801493
}

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -554,6 +554,9 @@ private static void serializePrimitiveData(OutputStream stream, Object value, Cl
554554
case UInt256:
555555
BinaryStreamUtils.writeUnsignedInt256(stream, NumberConverter.toBigInteger(value));
556556
break;
557+
case BFloat16:
558+
BinaryStreamUtils.writeBFloat16(stream, (float) value);
559+
break;
557560
case Float32:
558561
BinaryStreamUtils.writeFloat32(stream, NumberConverter.toFloat(value));
559562
break;
@@ -1116,6 +1119,11 @@ private static void binaryReaderMethodForType(MethodVisitor mv, Class<?> targetT
11161119
readerMethodReturnType = Type.getDescriptor(long.class);
11171120
convertOpcode = longToOpcode(targetType);
11181121
break;
1122+
case BFloat16:
1123+
readerMethod = "readBFloat16LE";
1124+
readerMethodReturnType = Type.getDescriptor(float.class);
1125+
convertOpcode = floatToOpcode(targetType);
1126+
break;
11191127
case Float32:
11201128
readerMethod = "readFloatLE";
11211129
readerMethodReturnType = Type.getDescriptor(float.class);

0 commit comments

Comments
 (0)