From 1e47a0bf20d8571aaefd413a8ec4e94feb0817ae Mon Sep 17 00:00:00 2001 From: Itamar Syn-Hershko Date: Fri, 13 Feb 2026 09:20:19 +0200 Subject: [PATCH 1/2] Expose X-ClickHouse-Server-Display-Name header via client-v2 API Extract the server display name from HTTP response headers and surface it through OperationMetrics, QueryResponse, InsertResponse, and CommandResponse, following the existing queryId pattern. Signed-off-by: Itamar Syn-Hershko --- .../main/java/com/clickhouse/client/api/Client.java | 6 ++++++ .../clickhouse/client/api/command/CommandResponse.java | 8 ++++++++ .../clickhouse/client/api/insert/InsertResponse.java | 8 ++++++++ .../client/api/metrics/OperationMetrics.java | 10 ++++++++++ .../com/clickhouse/client/api/query/QueryResponse.java | 8 ++++++++ 5 files changed, 40 insertions(+) diff --git a/client-v2/src/main/java/com/clickhouse/client/api/Client.java b/client-v2/src/main/java/com/clickhouse/client/api/Client.java index 9d983decb..c410873b9 100644 --- a/client-v2/src/main/java/com/clickhouse/client/api/Client.java +++ b/client-v2/src/main/java/com/clickhouse/client/api/Client.java @@ -1338,6 +1338,8 @@ public CompletableFuture insert(String tableName, List data, String queryId = HttpAPIClientHelper.getHeaderVal(httpResponse.getFirstHeader(ClickHouseHttpProto.HEADER_QUERY_ID), requestSettings.getQueryId(), String::valueOf); metrics.operationComplete(); metrics.setQueryId(queryId); + metrics.setServerDisplayName(HttpAPIClientHelper.getHeaderVal( + httpResponse.getFirstHeader(ClickHouseHttpProto.HEADER_SRV_DISPLAY_NAME), null)); return new InsertResponse(metrics); } catch (Exception e) { String msg = requestExMsg("Insert", (i + 1), durationSince(startTime).toMillis(), requestSettings.getQueryId()); @@ -1545,6 +1547,8 @@ public CompletableFuture insert(String tableName, String queryId = HttpAPIClientHelper.getHeaderVal(httpResponse.getFirstHeader(ClickHouseHttpProto.HEADER_QUERY_ID), requestSettings.getQueryId(), String::valueOf); metrics.operationComplete(); metrics.setQueryId(queryId); + metrics.setServerDisplayName(HttpAPIClientHelper.getHeaderVal( + httpResponse.getFirstHeader(ClickHouseHttpProto.HEADER_SRV_DISPLAY_NAME), null)); return new InsertResponse(metrics); } catch (Exception e) { String msg = requestExMsg("Insert", (i + 1), durationSince(startTime).toMillis(), requestSettings.getQueryId()); @@ -1677,6 +1681,8 @@ public CompletableFuture query(String sqlQuery, Map metrics = new HashMap<>(); private String queryId; + private String serverDisplayName; private final ClientStatisticsHolder clientStatistics; @@ -55,10 +56,19 @@ public void setQueryId(String queryId) { this.queryId = queryId; } + public String getServerDisplayName() { + return serverDisplayName; + } + + public void setServerDisplayName(String serverDisplayName) { + this.serverDisplayName = serverDisplayName; + } + @Override public String toString() { return "OperationStatistics{" + "\"queryId\"=\"" + queryId + "\", " + + "\"serverDisplayName\"=\"" + serverDisplayName + "\", " + "\"metrics\"=" + metrics + '}'; } diff --git a/client-v2/src/main/java/com/clickhouse/client/api/query/QueryResponse.java b/client-v2/src/main/java/com/clickhouse/client/api/query/QueryResponse.java index 735fe6f58..5edc30a12 100644 --- a/client-v2/src/main/java/com/clickhouse/client/api/query/QueryResponse.java +++ b/client-v2/src/main/java/com/clickhouse/client/api/query/QueryResponse.java @@ -150,6 +150,14 @@ public String getQueryId() { return operationMetrics.getQueryId(); } + /** + * Alias for {@link OperationMetrics#getServerDisplayName()} + * @return server display name from the response header + */ + public String getServerDisplayName() { + return operationMetrics.getServerDisplayName(); + } + public TimeZone getTimeZone() { return settings.getOption(ClientConfigProperties.SERVER_TIMEZONE.getKey()) == null ? null From 89328758f6069cf09a94766876b9abb3aadea871 Mon Sep 17 00:00:00 2001 From: Itamar Syn-Hershko Date: Sat, 14 Feb 2026 21:41:25 +0200 Subject: [PATCH 2/2] Updating as per CR comments --- .../com/clickhouse/client/api/Client.java | 13 +++------ .../client/api/command/CommandResponse.java | 17 ++++++++++-- .../client/api/insert/InsertResponse.java | 27 ++++++++++++++++--- .../api/internal/HttpAPIClientHelper.java | 27 +++++++++++++++++++ .../client/api/metrics/OperationMetrics.java | 10 ------- .../client/api/query/QueryResponse.java | 27 ++++++++++++++++--- 6 files changed, 94 insertions(+), 27 deletions(-) diff --git a/client-v2/src/main/java/com/clickhouse/client/api/Client.java b/client-v2/src/main/java/com/clickhouse/client/api/Client.java index c410873b9..73ec21155 100644 --- a/client-v2/src/main/java/com/clickhouse/client/api/Client.java +++ b/client-v2/src/main/java/com/clickhouse/client/api/Client.java @@ -1338,9 +1338,7 @@ public CompletableFuture insert(String tableName, List data, String queryId = HttpAPIClientHelper.getHeaderVal(httpResponse.getFirstHeader(ClickHouseHttpProto.HEADER_QUERY_ID), requestSettings.getQueryId(), String::valueOf); metrics.operationComplete(); metrics.setQueryId(queryId); - metrics.setServerDisplayName(HttpAPIClientHelper.getHeaderVal( - httpResponse.getFirstHeader(ClickHouseHttpProto.HEADER_SRV_DISPLAY_NAME), null)); - return new InsertResponse(metrics); + return new InsertResponse(metrics, HttpAPIClientHelper.collectResponseHeaders(httpResponse)); } catch (Exception e) { String msg = requestExMsg("Insert", (i + 1), durationSince(startTime).toMillis(), requestSettings.getQueryId()); lastException = httpClientHelper.wrapException(msg, e, requestSettings.getQueryId()); @@ -1547,9 +1545,7 @@ public CompletableFuture insert(String tableName, String queryId = HttpAPIClientHelper.getHeaderVal(httpResponse.getFirstHeader(ClickHouseHttpProto.HEADER_QUERY_ID), requestSettings.getQueryId(), String::valueOf); metrics.operationComplete(); metrics.setQueryId(queryId); - metrics.setServerDisplayName(HttpAPIClientHelper.getHeaderVal( - httpResponse.getFirstHeader(ClickHouseHttpProto.HEADER_SRV_DISPLAY_NAME), null)); - return new InsertResponse(metrics); + return new InsertResponse(metrics, HttpAPIClientHelper.collectResponseHeaders(httpResponse)); } catch (Exception e) { String msg = requestExMsg("Insert", (i + 1), durationSince(startTime).toMillis(), requestSettings.getQueryId()); lastException = httpClientHelper.wrapException(msg, e, requestSettings.getQueryId()); @@ -1681,8 +1677,6 @@ public CompletableFuture query(String sqlQuery, Map query(String sqlQuery, Map getResponseHeaders() { + return response.getResponseHeaders(); + } + @Override public void close() throws Exception { response.close(); diff --git a/client-v2/src/main/java/com/clickhouse/client/api/insert/InsertResponse.java b/client-v2/src/main/java/com/clickhouse/client/api/insert/InsertResponse.java index ca051e11e..2fc09f312 100644 --- a/client-v2/src/main/java/com/clickhouse/client/api/insert/InsertResponse.java +++ b/client-v2/src/main/java/com/clickhouse/client/api/insert/InsertResponse.java @@ -1,13 +1,23 @@ package com.clickhouse.client.api.insert; +import com.clickhouse.client.api.http.ClickHouseHttpProto; import com.clickhouse.client.api.metrics.OperationMetrics; import com.clickhouse.client.api.metrics.ServerMetrics; +import java.util.Collections; +import java.util.Map; + public class InsertResponse implements AutoCloseable { private OperationMetrics operationMetrics; + private final Map responseHeaders; public InsertResponse(OperationMetrics metrics) { + this(metrics, Collections.emptyMap()); + } + + public InsertResponse(OperationMetrics metrics, Map responseHeaders) { this.operationMetrics = metrics; + this.responseHeaders = responseHeaders; } @Override @@ -80,10 +90,21 @@ public String getQueryId() { } /** - * Alias for {@link OperationMetrics#getServerDisplayName()} - * @return server display name from the response header + * Returns the value of {@code X-ClickHouse-Server-Display-Name} response header. + * + * @return server display name or {@code null} if not present */ public String getServerDisplayName() { - return operationMetrics.getServerDisplayName(); + return responseHeaders.get(ClickHouseHttpProto.HEADER_SRV_DISPLAY_NAME); + } + + /** + * Returns all collected response headers as an unmodifiable map. + * Only whitelisted ClickHouse headers are included. + * + * @return map of header name to header value + */ + public Map getResponseHeaders() { + return responseHeaders; } } diff --git a/client-v2/src/main/java/com/clickhouse/client/api/internal/HttpAPIClientHelper.java b/client-v2/src/main/java/com/clickhouse/client/api/internal/HttpAPIClientHelper.java index 31cdeff3f..76e2dec93 100644 --- a/client-v2/src/main/java/com/clickhouse/client/api/internal/HttpAPIClientHelper.java +++ b/client-v2/src/main/java/com/clickhouse/client/api/internal/HttpAPIClientHelper.java @@ -95,6 +95,8 @@ import java.util.Objects; import java.util.Optional; import java.util.Properties; +import java.util.Arrays; +import java.util.HashMap; import java.util.Set; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.TimeUnit; @@ -752,6 +754,31 @@ public static int getHeaderInt(Header header, int defaultValue) { return getHeaderVal(header, defaultValue, Integer::parseInt); } + private static final Set RESPONSE_HEADER_WHITELIST = new HashSet<>(Arrays.asList( + ClickHouseHttpProto.HEADER_QUERY_ID, + ClickHouseHttpProto.HEADER_SRV_SUMMARY, + ClickHouseHttpProto.HEADER_SRV_DISPLAY_NAME, + ClickHouseHttpProto.HEADER_DATABASE, + ClickHouseHttpProto.HEADER_DB_USER + )); + + /** + * Collects whitelisted response headers from an HTTP response into a map. + * + * @param response the HTTP response + * @return unmodifiable map of header name to header value for whitelisted headers present in the response + */ + public static Map collectResponseHeaders(ClassicHttpResponse response) { + Map headers = new HashMap<>(); + for (String name : RESPONSE_HEADER_WHITELIST) { + Header header = response.getFirstHeader(name); + if (header != null) { + headers.put(name, header.getValue()); + } + } + return Collections.unmodifiableMap(headers); + } + public static String getHeaderVal(Header header, String defaultValue) { return getHeaderVal(header, defaultValue, Function.identity()); } diff --git a/client-v2/src/main/java/com/clickhouse/client/api/metrics/OperationMetrics.java b/client-v2/src/main/java/com/clickhouse/client/api/metrics/OperationMetrics.java index 1c010a5c7..dfb283259 100644 --- a/client-v2/src/main/java/com/clickhouse/client/api/metrics/OperationMetrics.java +++ b/client-v2/src/main/java/com/clickhouse/client/api/metrics/OperationMetrics.java @@ -16,7 +16,6 @@ public class OperationMetrics { public Map metrics = new HashMap<>(); private String queryId; - private String serverDisplayName; private final ClientStatisticsHolder clientStatistics; @@ -56,19 +55,10 @@ public void setQueryId(String queryId) { this.queryId = queryId; } - public String getServerDisplayName() { - return serverDisplayName; - } - - public void setServerDisplayName(String serverDisplayName) { - this.serverDisplayName = serverDisplayName; - } - @Override public String toString() { return "OperationStatistics{" + "\"queryId\"=\"" + queryId + "\", " + - "\"serverDisplayName\"=\"" + serverDisplayName + "\", " + "\"metrics\"=" + metrics + '}'; } diff --git a/client-v2/src/main/java/com/clickhouse/client/api/query/QueryResponse.java b/client-v2/src/main/java/com/clickhouse/client/api/query/QueryResponse.java index 5edc30a12..6e237a18d 100644 --- a/client-v2/src/main/java/com/clickhouse/client/api/query/QueryResponse.java +++ b/client-v2/src/main/java/com/clickhouse/client/api/query/QueryResponse.java @@ -10,6 +10,8 @@ import org.apache.hc.core5.http.Header; import java.io.InputStream; +import java.util.Collections; +import java.util.Map; import java.util.TimeZone; /** @@ -35,12 +37,20 @@ public class QueryResponse implements AutoCloseable { private ClassicHttpResponse httpResponse; + private final Map responseHeaders; + public QueryResponse(ClassicHttpResponse response, ClickHouseFormat format, QuerySettings settings, OperationMetrics operationMetrics) { + this(response, format, settings, operationMetrics, Collections.emptyMap()); + } + + public QueryResponse(ClassicHttpResponse response, ClickHouseFormat format, QuerySettings settings, + OperationMetrics operationMetrics, Map responseHeaders) { this.httpResponse = response; this.format = format; this.operationMetrics = operationMetrics; this.settings = settings; + this.responseHeaders = responseHeaders; Header tzHeader = response.getFirstHeader(ClickHouseHttpProto.HEADER_TIMEZONE); if (tzHeader != null) { @@ -151,11 +161,22 @@ public String getQueryId() { } /** - * Alias for {@link OperationMetrics#getServerDisplayName()} - * @return server display name from the response header + * Returns the value of {@code X-ClickHouse-Server-Display-Name} response header. + * + * @return server display name or {@code null} if not present */ public String getServerDisplayName() { - return operationMetrics.getServerDisplayName(); + return responseHeaders.get(ClickHouseHttpProto.HEADER_SRV_DISPLAY_NAME); + } + + /** + * Returns all collected response headers as an unmodifiable map. + * Only whitelisted ClickHouse headers are included. + * + * @return map of header name to header value + */ + public Map getResponseHeaders() { + return responseHeaders; } public TimeZone getTimeZone() {