Skip to content

Commit 6c7e8f7

Browse files
committed
Merge branch 'main' into new_time_datatypes
2 parents a526116 + d4792b7 commit 6c7e8f7

File tree

27 files changed

+1080
-269
lines changed

27 files changed

+1080
-269
lines changed

clickhouse-client/src/main/java/com/clickhouse/client/config/ClickHouseClientOption.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -451,7 +451,14 @@ public enum ClickHouseClientOption implements ClickHouseOption {
451451
*/
452452
CONNECTION_TTL("connection_ttl", 0L,
453453
"Connection time to live in milliseconds. 0 or negative number means no limit."),
454-
MEASURE_REQUEST_TIME("debug_measure_request_time", false, "Whether to measure request time. If true, the time will be logged in debug mode.");
454+
MEASURE_REQUEST_TIME("debug_measure_request_time", false, "Whether to measure request time. If true, the time will be logged in debug mode."),
455+
456+
/**
457+
* SNI SSL parameter that will be set for each outbound SSL socket.
458+
*/
459+
SSL_SOCKET_SNI("ssl_socket_sni", "", " SNI SSL parameter that will be set for each outbound SSL socket.")
460+
461+
;
455462

456463
private final String key;
457464
private final Serializable defaultValue;

clickhouse-http-client/src/main/java/com/clickhouse/client/http/ApacheHttpConnectionImpl.java

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import com.clickhouse.client.config.ClickHouseProxyType;
1212
import com.clickhouse.client.config.ClickHouseSslMode;
1313
import com.clickhouse.client.http.config.ClickHouseHttpOption;
14+
import com.clickhouse.config.ClickHouseOption;
1415
import com.clickhouse.data.ClickHouseChecker;
1516
import com.clickhouse.data.ClickHouseExternalTable;
1617
import com.clickhouse.data.ClickHouseFormat;
@@ -54,8 +55,11 @@
5455
import org.apache.hc.core5.util.Timeout;
5556
import org.apache.hc.core5.util.VersionInfo;
5657

58+
import javax.net.ssl.SNIHostName;
5759
import javax.net.ssl.SSLContext;
5860
import javax.net.ssl.SSLException;
61+
import javax.net.ssl.SSLParameters;
62+
import javax.net.ssl.SSLSocket;
5963
import java.io.BufferedReader;
6064
import java.io.ByteArrayInputStream;
6165
import java.io.ByteArrayOutputStream;
@@ -66,10 +70,9 @@
6670
import java.io.UncheckedIOException;
6771
import java.net.ConnectException;
6872
import java.net.HttpURLConnection;
73+
import java.net.InetAddress;
6974
import java.net.InetSocketAddress;
7075
import java.net.Socket;
71-
import java.net.SocketOption;
72-
import java.net.SocketOptions;
7376
import java.nio.charset.StandardCharsets;
7477
import java.util.Collections;
7578
import java.util.List;
@@ -394,6 +397,7 @@ public static SocketFactory create(ClickHouseConfig config) {
394397

395398
static class SSLSocketFactory extends SSLConnectionSocketFactory {
396399
private final ClickHouseConfig config;
400+
private final SNIHostName defaultSNI;
397401

398402
private SSLSocketFactory(ClickHouseConfig config) throws SSLException {
399403
super(ClickHouseSslContextProvider.getProvider().getSslContext(SSLContext.class, config)
@@ -402,13 +406,25 @@ private SSLSocketFactory(ClickHouseConfig config) throws SSLException {
402406
? new DefaultHostnameVerifier()
403407
: (hostname, session) -> true); // NOSONAR
404408
this.config = config;
409+
String sni = config.getStrOption(ClickHouseClientOption.SSL_SOCKET_SNI);
410+
defaultSNI = sni == null || sni.trim().isEmpty() ? null : new SNIHostName(sni);
405411
}
406412

407413
@Override
408414
public Socket createSocket(HttpContext context) throws IOException {
409415
return AbstractSocketClient.setSocketOptions(config, new Socket());
410416
}
411417

418+
@Override
419+
protected void prepareSocket(SSLSocket socket, HttpContext context) throws IOException {
420+
super.prepareSocket(socket, context);
421+
if (defaultSNI != null) {
422+
SSLParameters sslParams = socket.getSSLParameters();
423+
sslParams.setServerNames(Collections.singletonList(defaultSNI));
424+
socket.setSSLParameters(sslParams);
425+
}
426+
}
427+
412428
public static SSLSocketFactory create(ClickHouseConfig config) throws SSLException {
413429
return new SSLSocketFactory(config);
414430
}

client-v2/pom.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,12 @@
129129
<version>1.18.36</version>
130130
<scope>test</scope>
131131
</dependency>
132+
<dependency>
133+
<groupId>org.slf4j</groupId>
134+
<artifactId>slf4j-simple</artifactId>
135+
<version>2.0.16</version>
136+
<scope>test</scope>
137+
</dependency>
132138
</dependencies>
133139

134140
<build>

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

Lines changed: 45 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import com.clickhouse.client.api.data_formats.RowBinaryFormatReader;
88
import com.clickhouse.client.api.data_formats.RowBinaryWithNamesAndTypesFormatReader;
99
import com.clickhouse.client.api.data_formats.RowBinaryWithNamesFormatReader;
10+
import com.clickhouse.client.api.data_formats.internal.AbstractBinaryFormatReader;
1011
import com.clickhouse.client.api.data_formats.internal.BinaryStreamReader;
1112
import com.clickhouse.client.api.data_formats.internal.MapBackedRecord;
1213
import com.clickhouse.client.api.data_formats.internal.ProcessParser;
@@ -36,7 +37,9 @@
3637
import com.clickhouse.client.api.transport.Endpoint;
3738
import com.clickhouse.client.api.transport.HttpEndpoint;
3839
import com.clickhouse.client.config.ClickHouseClientOption;
40+
import com.clickhouse.config.ClickHouseOption;
3941
import com.clickhouse.data.ClickHouseColumn;
42+
import com.clickhouse.data.ClickHouseDataType;
4043
import com.clickhouse.data.ClickHouseFormat;
4144
import com.google.common.collect.ImmutableList;
4245
import net.jpountz.lz4.LZ4Factory;
@@ -131,6 +134,8 @@ public class Client implements AutoCloseable {
131134

132135
private final Map<String, Boolean> tableSchemaHasDefaults = new ConcurrentHashMap<>();
133136

137+
private final Map<ClickHouseDataType, Class<?>> typeHintMapping;
138+
134139
// Server context
135140
private String serverVersion;
136141
private Object metricsRegistry;
@@ -192,6 +197,8 @@ private Client(Set<String> endpoints, Map<String,String> configuration,
192197
}
193198

194199
this.serverVersion = configuration.getOrDefault(ClientConfigProperties.SERVER_VERSION.getKey(), "unknown");
200+
201+
this.typeHintMapping = (Map<ClickHouseDataType, Class<?>>) this.configuration.get(ClientConfigProperties.TYPE_HINT_MAPPING.getKey());
195202
}
196203

197204
/**
@@ -595,6 +602,11 @@ public Builder useHttpCompression(boolean enabled) {
595602
return this;
596603
}
597604

605+
/**
606+
* Tell client that compression will be handled by application.
607+
* @param enabled - indicates that feature is enabled.
608+
* @return
609+
*/
598610
public Builder appCompressedData(boolean enabled) {
599611
this.configuration.put(ClientConfigProperties.APP_COMPRESSED_DATA.getKey(), String.valueOf(enabled));
600612
return this;
@@ -1004,6 +1016,33 @@ public Builder setServerVersion(String serverVersion) {
10041016
return this;
10051017
}
10061018

1019+
/**
1020+
* Defines mapping between ClickHouse data type and target Java type
1021+
* Used by binary readers to convert values into desired Java type.
1022+
* @param typeHintMapping - map between ClickHouse data type and Java class
1023+
* @return this builder instance
1024+
*/
1025+
public Builder typeHintMapping(Map<ClickHouseDataType, Class<?>> typeHintMapping) {
1026+
this.configuration.put(ClientConfigProperties.TYPE_HINT_MAPPING.getKey(),
1027+
ClientConfigProperties.mapToString(typeHintMapping, (v) -> {
1028+
return ((Class<?>) v).getName();
1029+
}));
1030+
return this;
1031+
}
1032+
1033+
1034+
/**
1035+
* SNI SSL parameter that will be set for each outbound SSL socket.
1036+
* SNI stands for Server Name Indication - an extension to the TLS protocol that allows multiple domains to share the same IP address.
1037+
*
1038+
* @param sni - SNI parameter
1039+
* @return this builder instance
1040+
*/
1041+
public Builder sslSocketSNI(String sni) {
1042+
this.configuration.put(ClientConfigProperties.SSL_SOCKET_SNI.getKey(), sni);
1043+
return this;
1044+
}
1045+
10071046
public Client build() {
10081047
// check if endpoint are empty. so can not initiate client
10091048
if (this.endpoints.isEmpty()) {
@@ -1197,7 +1236,7 @@ public CompletableFuture<InsertResponse> insert(String tableName, List<?> data,
11971236
Integer retry = (Integer) configuration.get(ClientConfigProperties.RETRY_ON_FAILURE.getKey());
11981237
final int maxRetries = retry == null ? 0 : retry;
11991238

1200-
settings.setOption(ClientConfigProperties.INPUT_OUTPUT_FORMAT.getKey(), format.name());
1239+
settings.setOption(ClientConfigProperties.INPUT_OUTPUT_FORMAT.getKey(), format);
12011240
final InsertSettings finalSettings = new InsertSettings(buildRequestSettings(settings.getAllSettings()));
12021241
Supplier<InsertResponse> supplier = () -> {
12031242
long startTime = System.nanoTime();
@@ -1400,7 +1439,7 @@ public CompletableFuture<InsertResponse> insert(String tableName,
14001439
throw new IllegalArgumentException("Buffer size must be greater than 0");
14011440
}
14021441

1403-
settings.setOption(ClientConfigProperties.INPUT_OUTPUT_FORMAT.getKey(), format.name());
1442+
settings.setOption(ClientConfigProperties.INPUT_OUTPUT_FORMAT.getKey(), format);
14041443
final InsertSettings finalSettings = new InsertSettings(buildRequestSettings(settings.getAllSettings()));
14051444

14061445
StringBuilder sqlStmt = new StringBuilder("INSERT INTO ").append(tableName);
@@ -1919,23 +1958,20 @@ public ClickHouseBinaryFormatReader newBinaryFormatReader(QueryResponse response
19191958
BinaryStreamReader.ByteBufferAllocator byteBufferPool = useCachingBufferAllocator ?
19201959
new BinaryStreamReader.CachingByteBufferAllocator() :
19211960
new BinaryStreamReader.DefaultByteBufferAllocator();
1922-
19231961
switch (response.getFormat()) {
19241962
case Native:
19251963
reader = new NativeFormatReader(response.getInputStream(), response.getSettings(),
1926-
byteBufferPool);
1964+
byteBufferPool, typeHintMapping);
19271965
break;
19281966
case RowBinaryWithNamesAndTypes:
1929-
reader = new RowBinaryWithNamesAndTypesFormatReader(response.getInputStream(), response.getSettings(),
1930-
byteBufferPool);
1967+
reader = new RowBinaryWithNamesAndTypesFormatReader(response.getInputStream(), response.getSettings(), byteBufferPool, typeHintMapping);
19311968
break;
19321969
case RowBinaryWithNames:
1933-
reader = new RowBinaryWithNamesFormatReader(response.getInputStream(), response.getSettings(), schema,
1934-
byteBufferPool);
1970+
reader = new RowBinaryWithNamesFormatReader(response.getInputStream(), response.getSettings(), schema, byteBufferPool, typeHintMapping);
19351971
break;
19361972
case RowBinary:
19371973
reader = new RowBinaryFormatReader(response.getInputStream(), response.getSettings(), schema,
1938-
byteBufferPool);
1974+
byteBufferPool, typeHintMapping);
19391975
break;
19401976
default:
19411977
throw new IllegalArgumentException("Binary readers doesn't support format: " + response.getFormat());

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

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

3+
import com.clickhouse.client.api.data_formats.internal.AbstractBinaryFormatReader;
34
import com.clickhouse.client.api.internal.ClickHouseLZ4OutputStream;
5+
import com.clickhouse.data.ClickHouseDataType;
46
import com.clickhouse.data.ClickHouseFormat;
57
import org.slf4j.Logger;
68
import org.slf4j.LoggerFactory;
@@ -11,11 +13,13 @@
1113
import java.util.Collections;
1214
import java.util.HashMap;
1315
import java.util.HashSet;
16+
import java.util.LinkedHashMap;
1417
import java.util.List;
1518
import java.util.Locale;
1619
import java.util.Map;
1720
import java.util.TimeZone;
1821
import java.util.function.Consumer;
22+
import java.util.function.Function;
1923
import java.util.stream.Collectors;
2024

2125
/**
@@ -168,6 +172,16 @@ public Object parseValue(String value) {
168172

169173
BINARY_READER_USE_PREALLOCATED_BUFFERS("client_allow_binary_reader_to_reuse_buffers", Boolean.class, "false"),
170174

175+
/**
176+
* Defines mapping between ClickHouse data type and target Java type
177+
* Used by binary readers to convert values into desired Java type.
178+
*/
179+
TYPE_HINT_MAPPING("type_hint_mapping", Map.class),
180+
181+
/**
182+
* SNI SSL parameter that will be set for each outbound SSL socket.
183+
*/
184+
SSL_SOCKET_SNI("ssl_socket_sni", String.class,""),
171185
;
172186

173187
private static final Logger LOG = LoggerFactory.getLogger(ClientConfigProperties.class);
@@ -208,6 +222,9 @@ public <T> T getDefObjVal() {
208222

209223
public static final String SERVER_SETTING_PREFIX = "clickhouse_setting_";
210224

225+
// Key used to identify default value in configuration map
226+
public static final String DEFAULT_KEY = "_default_";
227+
211228
public static String serverSetting(String key) {
212229
return SERVER_SETTING_PREFIX + key;
213230
}
@@ -278,6 +295,10 @@ public Object parseValue(String value) {
278295
return TimeZone.getTimeZone(value);
279296
}
280297

298+
if (valueType.equals(Map.class)) {
299+
return toKeyValuePairs(value);
300+
}
301+
281302
return null;
282303
}
283304

@@ -301,7 +322,15 @@ public static Map<String, Object> parseConfigMap(Map<String, String> configMap)
301322
for (ClientConfigProperties config : ClientConfigProperties.values()) {
302323
String value = tmpMap.remove(config.getKey());
303324
if (value != null) {
304-
parsedConfig.put(config.getKey(), config.parseValue(value));
325+
Object parsedValue;
326+
switch (config) {
327+
case TYPE_HINT_MAPPING:
328+
parsedValue = translateTypeHintMapping(value);
329+
break;
330+
default:
331+
parsedValue = config.parseValue(value);
332+
}
333+
parsedConfig.put(config.getKey(), parsedValue);
305334
}
306335
}
307336

@@ -317,4 +346,90 @@ public static Map<String, Object> parseConfigMap(Map<String, String> configMap)
317346

318347
return parsedConfig;
319348
}
349+
350+
351+
/**
352+
* Converts given string to key value pairs.
353+
* This is very simple implementation that do not handle edge cases like
354+
* {@code k1=v1, ,k2=v2}
355+
*
356+
* @param str string
357+
* @return non-null key value pairs
358+
*/
359+
public static Map<String, String> toKeyValuePairs(String str) {
360+
if (str == null || str.isEmpty()) {
361+
return Collections.emptyMap();
362+
}
363+
364+
Map<String, String> map = new LinkedHashMap<>();
365+
String key = null;
366+
StringBuilder builder = new StringBuilder();
367+
for (int i = 0, len = str.length(); i < len; i++) {
368+
char ch = str.charAt(i);
369+
if (ch == '\\' && i + 1 < len) {
370+
ch = str.charAt(++i);
371+
builder.append(ch);
372+
continue;
373+
}
374+
375+
if (Character.isWhitespace(ch)) {
376+
if (builder.length() > 0) {
377+
builder.append(ch);
378+
}
379+
} else if (ch == '=' && key == null) {
380+
key = builder.toString().trim();
381+
builder.setLength(0);
382+
} else if (ch == ',' && key != null) {
383+
String value = builder.toString().trim();
384+
builder.setLength(0);
385+
if (!key.isEmpty() && !value.isEmpty()) {
386+
map.put(key, value);
387+
}
388+
key = null;
389+
} else {
390+
builder.append(ch);
391+
}
392+
}
393+
394+
if (key != null && builder.length() > 0) {
395+
String value = builder.toString().trim();
396+
if (!key.isEmpty() && !value.isEmpty()) {
397+
map.put(key, value);
398+
}
399+
}
400+
401+
return Collections.unmodifiableMap(map);
402+
}
403+
404+
405+
406+
public static String mapToString(Map<?,?> map, Function<Object, String> valueConverter) {
407+
StringBuilder sb = new StringBuilder();
408+
for (Map.Entry<?, ?> entry : map.entrySet()) {
409+
sb.append(entry.getKey()).append("=").append(valueConverter.apply(entry.getValue())).append(",");
410+
}
411+
412+
if (sb.length() > 0) {
413+
sb.setLength(sb.length() - 1);
414+
}
415+
return sb.toString();
416+
}
417+
418+
public static Map<ClickHouseDataType, Class<?>> translateTypeHintMapping(String mappingStr) {
419+
if (mappingStr == null || mappingStr.isEmpty()) {
420+
return AbstractBinaryFormatReader.NO_TYPE_HINT_MAPPING;
421+
}
422+
423+
Map<String, String> mapping= ClientConfigProperties.toKeyValuePairs(mappingStr);
424+
Map<ClickHouseDataType, Class<?>> hintMapping = new HashMap<>();
425+
try {
426+
for (Map.Entry<String, String> entry : mapping.entrySet()) {
427+
hintMapping.put(ClickHouseDataType.of(entry.getKey()),
428+
Class.forName(entry.getValue()));
429+
}
430+
} catch (ClassNotFoundException e) {
431+
throw new ClientMisconfigurationException("Failed to translate type-hint mapping", e);
432+
}
433+
return hintMapping;
434+
}
320435
}

0 commit comments

Comments
 (0)