Skip to content

Commit 4571c1c

Browse files
committed
Merge branch 'main' into 04/10/26/json_format_support
2 parents 4d4fc7b + 00e534d commit 4571c1c

17 files changed

Lines changed: 1089 additions & 51 deletions

File tree

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
---
2+
name: update-keyword-engine-lists
3+
description: Update ALLOWED_KEYWORD_ALIASES in ClickHouseSqlUtils.java and ENGINE_TO_TABLE_TYPE in DatabaseMetaDataImpl.java from failing test output. Use when StatementSQLTest.testAllowedKeywordAliasesMatchSystemKeywords or DatabaseMetaDataTest.testAllTableEnginesFromSystemTableEnginesAreMapped fails.
4+
---
5+
6+
# Update keyword and engine lists from test failures
7+
8+
## Files
9+
10+
**Keywords:**
11+
`jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/parser/javacc/ClickHouseSqlUtils.java`
12+
`initAllowedKeywordAliases()`, append after the last `// Appended` comment block.
13+
14+
**Engines:**
15+
`jdbc-v2/src/main/java/com/clickhouse/jdbc/metadata/DatabaseMetaDataImpl.java`
16+
`ENGINE_TO_TABLE_TYPE` static block, insert a new group before `// Special`.
17+
18+
## Steps
19+
20+
1. Parse the failing test message to extract the list of missing items.
21+
22+
2. **For keywords** (`StatementSQLTest` failure — e.g. `["CURSOR", "DETERMINISTIC", ...]`):
23+
Append a new dated comment + entries at the end of `buildKeywordSet(...)`, after the last existing `// Appended` block:
24+
```java
25+
// Appended MM/DD/YYYY
26+
"KEYWORD1", "KEYWORD2", ...
27+
```
28+
The previous entry must have a trailing comma. Keep alphabetical order within the new block.
29+
30+
3. **For engines** (`DatabaseMetaDataTest` failure — e.g. `[Paimon, PaimonAzure, ...]`):
31+
Add a new named group before `// Special` in the static block:
32+
```java
33+
// <GroupName> (appended MM/DD/YYYY)
34+
map.put("Engine1", TableType.REMOTE_TABLE.getTypeName());
35+
```
36+
Choose `TableType` by analogy:
37+
- External storage (S3/Azure/HDFS variants, lake formats) → `REMOTE_TABLE`
38+
- MergeTree family or local engines → `TABLE`
39+
40+
## Output
41+
42+
After editing, output this git commit command:
43+
44+
```
45+
git commit -m "jdbc-v2: update <keywords|engines|keywords and engines> in JDBC"
46+
```

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,20 @@
1717

1818
(https://github.com/ClickHouse/clickhouse-java/issues/2652, https://github.com/ClickHouse/clickhouse-java/issues/2825)
1919

20+
### Breaking Changes
21+
22+
- **[client-v2]** `Client.Builder#build()` now throws `ClientMisconfigurationException` instead of `IllegalArgumentException` for authentication and SSL misconfiguration (missing credentials, conflicting authentication methods, missing client certificate when SSL authentication is enabled, and trust store used together with a client certificate). Callers that relied on catching `IllegalArgumentException` from `build()` for these cases must catch `ClientMisconfigurationException` (which extends `RuntimeException` via `ClientException`).
23+
24+
- **[client-v2]** Combining `setUsername(...)` + `setPassword(...)` with a custom `Authorization` HTTP header (`httpHeader(HttpHeaders.AUTHORIZATION, ...)`) now fails at `Client.Builder#build()` with `ClientMisconfigurationException` unless HTTP Basic authentication is explicitly disabled via `useHTTPBasicAuth(false)`. Previously this combination was accepted and the custom `Authorization` header overrode the ClickHouse user/password headers at request time.
25+
26+
- **[client-v2]** The `access_token` configuration property (set via `Client.Builder#setAccessToken(String)` or directly through `setOption`) is now actually applied to outgoing requests as the `Authorization` HTTP header value verbatim. Previously the value was stored under `access_token` but never sent on the wire, so providing it alone had no effect on authentication. Callers must include the scheme prefix themselves (e.g. `setAccessToken("Bearer <token>")`), or use `useBearerTokenAuth(String)` which prepends `Bearer ` automatically.
27+
28+
- **[client-v2]** `Client.Builder#useBearerTokenAuth(String)` now stores the bearer token under the `access_token` configuration key (with the `Bearer ` prefix) instead of writing it directly into `http_header_authorization`. The HTTP wire format is unchanged, but the token is no longer observable through `Client#getReadOnlyConfig()` under the `http_header_authorization` key.
29+
2030
### New Features
2131

32+
- **[client-v2]** Added runtime credential update APIs on `Client`: `updateUserAndPassword(String, String)`, `updateAccessToken(String)`, and `updateBearerToken(String)`. Subsequent requests on the same `Client` instance use the new credentials without rebuilding the client. The authentication method is fixed at construction time; calling a runtime updater that does not match the configured method throws `ClientMisconfigurationException`. See `docs/authentication.md` for details and migration guidance.
33+
2234
- **[jdbc-v2]** Added `cluster_name` configuration property to specify a target cluster for statements like `KILL QUERY` that require an `ON CLUSTER` clause to execute across all nodes. (https://github.com/ClickHouse/clickhouse-java/issues/2837)
2335

2436
- **[client-v2, jdbc-v2]** Added support for ClickHouse `Geometry` type for ClickHouse `25.11+`, where `Geometry` changed from a `String` alias to `Variant(Point, Ring, LineString, MultiLineString, Polygon, MultiPolygon)` (client still compatible with older versions). Includes client read/write handling and JDBC type mapping for retrieving and inserting geometry values. Current writes infer the target geometry variant from array nesting depth, so `Ring` vs `LineString` and `Polygon` vs `MultiLineString` are not yet distinguishable through the generic `Geometry` write path. (https://github.com/ClickHouse/clickhouse-java/pull/2815)

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

Lines changed: 55 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import com.clickhouse.client.api.insert.InsertResponse;
1717
import com.clickhouse.client.api.insert.InsertSettings;
1818
import com.clickhouse.client.api.internal.ClientStatisticsHolder;
19+
import com.clickhouse.client.api.internal.CredentialsManager;
1920
import com.clickhouse.client.api.internal.HttpAPIClientHelper;
2021
import com.clickhouse.client.api.internal.MapUtils;
2122
import com.clickhouse.client.api.internal.TableSchemaParser;
@@ -44,7 +45,6 @@
4445
import org.apache.hc.core5.concurrent.DefaultThreadFactory;
4546
import org.apache.hc.core5.http.ClassicHttpResponse;
4647
import org.apache.hc.core5.http.Header;
47-
import org.apache.hc.core5.http.HttpHeaders;
4848
import org.apache.hc.core5.http.HttpStatus;
4949
import org.slf4j.Logger;
5050
import org.slf4j.LoggerFactory;
@@ -105,7 +105,8 @@
105105
*
106106
*
107107
*
108-
* <p>Client is thread-safe. It uses exclusive set of object to perform an operation.</p>
108+
* <p>Client is thread-safe. It uses exclusive set of object to perform an operation.
109+
* Exception is client global authentication configuration. Application should handle it in the way it is designed.</p>
109110
*
110111
*/
111112
public class Client implements AutoCloseable {
@@ -140,11 +141,13 @@ public class Client implements AutoCloseable {
140141
private final int retries;
141142
private LZ4Factory lz4Factory = null;
142143
private final Supplier<String> queryIdGenerator;
144+
private final CredentialsManager credentialsManager;
143145

144146
private Client(Collection<Endpoint> endpoints, Map<String,String> configuration,
145147
ExecutorService sharedOperationExecutor, ColumnToMethodMatchingStrategy columnToMethodMatchingStrategy,
146-
Object metricsRegistry, Supplier<String> queryIdGenerator) {
147-
Map<String, Object> parsedConfiguration = ClientConfigProperties.parseConfigMap(configuration);
148+
Object metricsRegistry, Supplier<String> queryIdGenerator, CredentialsManager cManager) {
149+
Map<String, Object> parsedConfiguration = new ConcurrentHashMap<>(ClientConfigProperties.parseConfigMap(configuration));
150+
this.credentialsManager = cManager;
148151
this.session = Session.extractFrom(parsedConfiguration);
149152
this.configuration = new ConcurrentHashMap<>(parsedConfiguration);
150153
this.readOnlyConfig = Collections.unmodifiableMap(configuration);
@@ -1039,12 +1042,13 @@ public Builder setOptions(Map<String, String> options) {
10391042
* Specifies whether to use Bearer Authentication and what token to use.
10401043
* The token will be sent as is, so it should be encoded before passing to this method.
10411044
*
1042-
* @param bearerToken - token to use
1045+
* @param bearerToken - token to use (without {@code Bearer} prefix)
10431046
* @return same instance of the builder
10441047
*/
10451048
public Builder useBearerTokenAuth(String bearerToken) {
10461049
// Most JWT libraries (https://jwt.io/libraries?language=Java) compact tokens in proper way
1047-
this.httpHeader(HttpHeaders.AUTHORIZATION, "Bearer " + bearerToken);
1050+
// Bearer token in subset of access token
1051+
setAccessToken(CredentialsManager.AUTH_HEADER_BEARER_PREFIX + bearerToken);
10481052
return this;
10491053
}
10501054

@@ -1128,28 +1132,12 @@ public Client build() {
11281132
if (this.endpoints.isEmpty()) {
11291133
throw new IllegalArgumentException("At least one endpoint is required");
11301134
}
1131-
// check if username and password are empty. so can not initiate client?
1132-
boolean useSslAuth = MapUtils.getFlag(this.configuration, ClientConfigProperties.SSL_AUTH.getKey());
1133-
boolean hasAccessToken = this.configuration.containsKey(ClientConfigProperties.ACCESS_TOKEN.getKey());
1134-
boolean hasUser = this.configuration.containsKey(ClientConfigProperties.USER.getKey());
1135-
boolean hasPassword = this.configuration.containsKey(ClientConfigProperties.PASSWORD.getKey());
1136-
boolean customHttpHeaders = this.configuration.containsKey(ClientConfigProperties.httpHeader(HttpHeaders.AUTHORIZATION));
1137-
1138-
if (!(useSslAuth || hasAccessToken || hasUser || hasPassword || customHttpHeaders)) {
1139-
throw new IllegalArgumentException("Username and password (or access token or SSL authentication or pre-define Authorization header) are required");
1140-
}
1141-
1142-
if (useSslAuth && (hasAccessToken || hasPassword)) {
1143-
throw new IllegalArgumentException("Only one of password, access token or SSL authentication can be used per client.");
1144-
}
11451135

1146-
if (useSslAuth && !this.configuration.containsKey(ClientConfigProperties.SSL_CERTIFICATE.getKey())) {
1147-
throw new IllegalArgumentException("SSL authentication requires a client certificate");
1148-
}
1136+
CredentialsManager cManager = new CredentialsManager(this.configuration);
11491137

1150-
if (this.configuration.containsKey(ClientConfigProperties.SSL_TRUST_STORE.getKey()) &&
1151-
this.configuration.containsKey(ClientConfigProperties.SSL_CERTIFICATE.getKey())) {
1152-
throw new IllegalArgumentException("Trust store and certificates cannot be used together");
1138+
if (configuration.containsKey(ClientConfigProperties.SSL_TRUST_STORE.getKey()) &&
1139+
configuration.containsKey(ClientConfigProperties.SSL_CERTIFICATE.getKey())) {
1140+
throw new ClientMisconfigurationException("Trust store and certificates cannot be used together");
11531141
}
11541142

11551143
// Check timezone settings
@@ -1181,7 +1169,7 @@ public Client build() {
11811169
}
11821170

11831171
return new Client(this.endpoints, this.configuration, this.sharedOperationExecutor,
1184-
this.columnToMethodMatchingStrategy, this.metricRegistry, this.queryIdGenerator);
1172+
this.columnToMethodMatchingStrategy, this.metricRegistry, this.queryIdGenerator, cManager);
11851173
}
11861174
}
11871175

@@ -2114,7 +2102,8 @@ public String toString() {
21142102
}
21152103

21162104
/**
2117-
* Returns unmodifiable map of configuration options.
2105+
* Returns unmodifiable map of initial configuration options.
2106+
* As authentication configuration values can change this map doesn't reflect them.
21182107
* @return - configuration options
21192108
*/
21202109
public Map<String, String> getConfiguration() {
@@ -2194,8 +2183,44 @@ public Collection<String> getDBRoles() {
21942183
return unmodifiableDbRolesView;
21952184
}
21962185

2186+
2187+
/**
2188+
* Updates Bearer token for other requests.
2189+
* This method is not thread-safe with respect to other credential updates
2190+
* or concurrent request execution. Applications must coordinate access if
2191+
* they require stronger consistency.
2192+
* Method doesn't allow to switch authentication type
2193+
* @param bearer - token to use without {@code "Bearer"} prefix.
2194+
*/
21972195
public void updateBearerToken(String bearer) {
2198-
this.configuration.put(ClientConfigProperties.httpHeader(HttpHeaders.AUTHORIZATION), "Bearer " + bearer);
2196+
ValidationUtils.checkNonBlank(bearer, "Bearer token");
2197+
updateAccessToken(CredentialsManager.AUTH_HEADER_BEARER_PREFIX + bearer);
2198+
}
2199+
2200+
/**
2201+
* Updates the user and password for all subsequential requests.
2202+
* This method is not thread-safe with respect to other credential updates
2203+
* or concurrent request execution. Applications must coordinate access if
2204+
* they require stronger consistency.
2205+
* Method doesn't allow to switch authentication type
2206+
* @param username user name
2207+
* @param password user password
2208+
* @throws ClientMisconfigurationException if another authentication type in use.
2209+
*/
2210+
public void updateUserAndPassword(String username, String password) {
2211+
this.credentialsManager.setCredentials(username, password);
2212+
}
2213+
2214+
/**
2215+
* Updates access token for the client. Change will be applied to all following requests.
2216+
* This method is not thread-safe with respect to other credential updates
2217+
* or concurrent request execution. Applications must coordinate access if
2218+
* they require stronger consistency.
2219+
* Method doesn't allow to switch authentication type
2220+
* @param accessToken - plain text access token
2221+
*/
2222+
public void updateAccessToken(String accessToken) {
2223+
this.credentialsManager.setAccessToken(accessToken);
21992224
}
22002225

22012226
private Endpoint getNextAliveNode() {
@@ -2213,6 +2238,7 @@ private Endpoint getNextAliveNode() {
22132238
private Map<String, Object> buildRequestSettings(Map<String, Object> opSettings) {
22142239
Map<String, Object> requestSettings = new HashMap<>(configuration);
22152240
session.applyTo(requestSettings);
2241+
credentialsManager.applyCredentials(requestSettings);
22162242
requestSettings.putAll(opSettings);
22172243
return requestSettings;
22182244
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package com.clickhouse.client.api.internal;
2+
3+
/**
4+
* Class containing utility methods used across the client.
5+
*/
6+
public final class ClientUtils {
7+
8+
private ClientUtils() {}
9+
10+
public static boolean isNotBlank(String str) {
11+
return str != null && !str.trim().isEmpty();
12+
}
13+
}

0 commit comments

Comments
 (0)