Skip to content
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -764,6 +764,16 @@ of `NULL` was not set and read. (https://github.com/ClickHouse/clickhouse-java/i
and `getObject(column, Object.class)` and the no-type `getObject(column)` overloads now return a decoded `String`
instead of the internal holder.

## 0.9.9

### Bug Fixes

- **[client-v2]** `ServerException` with code `159 Execution Timeout` is retried unconditionally. After the fix this
error treated as non-retriable. (part of https://github.com/ClickHouse/clickhouse-java/issues/2637)
- **[jdbc-v2]** Fixes `Statement#setQueryTimeout`. By default, client executes query in calling thread and future timeout
has no effect. Fix makes `setQueryTimeout` to set `max_execution_time` server setting in this case to overcome limitation.
(https://github.com/ClickHouse/clickhouse-java/issues/2637)

## 0.9.8

### Improvements
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,9 @@ public String getQueryId() {
private boolean discoverIsRetryable(int code, String message, int transportProtocolCode) {
// Let's check if we have a ServerException to reference the error code
// https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/ErrorCodes.cpp
switch (code) { // UNEXPECTED_END_OF_FILE
switch (code) {
case 3: // UNEXPECTED_END_OF_FILE
case 107: // FILE_DOESNT_EXIST
case 159: // TIMEOUT_EXCEEDED
Comment thread
chernser marked this conversation as resolved.
case 164: // READONLY
case 202: // TOO_MANY_SIMULTANEOUS_QUERIES
case 203: // NO_FREE_CONNECTION
Expand Down
18 changes: 18 additions & 0 deletions client-v2/src/test/java/com/clickhouse/client/ClientTests.java
Original file line number Diff line number Diff line change
Expand Up @@ -709,6 +709,24 @@ public void testQueryIdGenerator() throws Exception {
Assert.assertEquals(actualIds, new ArrayList<>(queryIds));
}

@Test(groups = {"integration"})
public void testExecutionTimeout() throws Exception{
final String query = "SELECT count(), sum(sipHash64(number)) " +
"FROM numbers(1000000000) " +
"SETTINGS max_threads = 1;";

long startTime = System.currentTimeMillis();
int maxExecTime = 4000;
try (Client client = newClient().serverSetting("max_execution_time", String.valueOf(TimeUnit.MILLISECONDS.toSeconds(maxExecTime))).build();
QueryResponse response = client.query(query).get()) {
Assert.fail("Exception expected");
} catch (ServerException e) {
long queryTime = System.currentTimeMillis() - startTime;
Assert.assertTrue(Math.abs(queryTime - maxExecTime) < 1000);
Assert.assertEquals(e.getCode(), ServerException.EXECUTION_TIMEOUT);
}
}
Comment thread
chernser marked this conversation as resolved.

@Test(groups = {"integration"})
public void testHostnameWithUnderscore() throws Exception {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ void tearDown() {
meterRegistry.clear();
Metrics.globalRegistry.clear();
}

@Test(groups = {"integration"}, enabled = true)
public void testRegisterMetrics() throws Exception {
ClickHouseNode node = getServer(ClickHouseProtocol.HTTP);
Expand Down
14 changes: 14 additions & 0 deletions jdbc-v2/src/main/java/com/clickhouse/jdbc/ConnectionImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@

private final JsonParserFactory jsonParserFactory;

public ConnectionImpl(String url, Properties info) throws SQLException {

Check failure on line 79 in jdbc-v2/src/main/java/com/clickhouse/jdbc/ConnectionImpl.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this method to reduce its Cognitive Complexity from 20 to the 15 allowed.

See more on https://sonarcloud.io/project/issues?id=ClickHouse_clickhouse-java&issues=AaDaYKy1c4VPrk66gl5s&open=AaDaYKy1c4VPrk66gl5s&pullRequest=3147
try {
this.url = url;//Raw URL
this.config = new JdbcConfiguration(url, info);
Expand Down Expand Up @@ -122,6 +122,20 @@
this.schema = client.getDefaultDatabase();
this.defaultQuerySettings = new QuerySettings();

String defaultQuerySettingsProp = config.getDriverProperty(DriverProperties.DEFAULT_QUERY_SETTINGS.getKey(), null);
if (defaultQuerySettingsProp != null) {
ClientConfigProperties.toKeyValuePairs(defaultQuerySettingsProp)
.forEach((k, v) -> this.defaultQuerySettings.serverSetting(k, v));
}
Map<String, String> clientProps = config.getClientProperties();
for (Map.Entry<String, String> entry : clientProps.entrySet()) {
if (entry.getKey() != null && entry.getValue() != null) {
if (entry.getKey().startsWith(ClientConfigProperties.SERVER_SETTING_PREFIX)) {

Check warning on line 133 in jdbc-v2/src/main/java/com/clickhouse/jdbc/ConnectionImpl.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Merge this if statement with the enclosing one.

See more on https://sonarcloud.io/project/issues?id=ClickHouse_clickhouse-java&issues=AaDaYKy1c4VPrk66gl5r&open=AaDaYKy1c4VPrk66gl5r&pullRequest=3147
this.defaultQuerySettings.setOption(entry.getKey(), entry.getValue());
}
}
}

this.metadata = new DatabaseMetaDataImpl(this, false, url);
this.defaultCalendar = Calendar.getInstance();

Expand Down
49 changes: 45 additions & 4 deletions jdbc-v2/src/main/java/com/clickhouse/jdbc/StatementImpl.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.clickhouse.jdbc;

import com.clickhouse.client.api.ClientConfigProperties;
import com.clickhouse.client.api.ServerException;
import com.clickhouse.client.api.data_formats.ClickHouseFormatReader;
import com.clickhouse.client.api.data_formats.JSONEachRowFormatReader;
import com.clickhouse.client.api.internal.ServerSettings;
Expand All @@ -15,17 +16,15 @@
import org.slf4j.LoggerFactory;

import java.net.SocketTimeoutException;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.SQLWarning;
import java.sql.Statement;
import java.sql.*;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.function.Supplier;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
Expand Down Expand Up @@ -66,6 +65,8 @@
// settings local to a statement
protected QuerySettings localSettings;

protected Integer connectionLvlExecTimeout; // to properly reset


public StatementImpl(ConnectionImpl connection) throws SQLException {
this.connection = connection;
Expand All @@ -79,6 +80,8 @@
this.escapeProcessingEnabled = true;
this.featureManager = new FeatureManager(connection.getJdbcConfig());
this.queryIdGenerator = connection.getJdbcConfig().getQueryIdGenerator();

this.connectionLvlExecTimeout = connection.getDefaultQuerySettings().getMaxExecutionTime();
}

protected void ensureOpen() throws SQLException {
Expand Down Expand Up @@ -360,6 +363,7 @@
}
handleSocketTimeoutException(e);
onResultSetClosed(null);
throwOnExecutionTimeout(e, mergedSettings.getQueryId());
throw ExceptionUtils.toSqlState(e);
}
}
Expand All @@ -370,6 +374,18 @@
}
}

protected void throwOnExecutionTimeout(Exception e, String queryId) throws SQLTimeoutException {
boolean shouldThrow = e instanceof TimeoutException || e.getCause() instanceof TimeoutException;
ServerException se = e instanceof ServerException ? (ServerException) e : e.getCause() instanceof ServerException ? (ServerException) e.getCause() : null;

Check warning on line 379 in jdbc-v2/src/main/java/com/clickhouse/jdbc/StatementImpl.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Extract this nested ternary operation into an independent statement.

See more on https://sonarcloud.io/project/issues?id=ClickHouse_clickhouse-java&issues=AaDaYKx-c4VPrk66gl5q&open=AaDaYKx-c4VPrk66gl5q&pullRequest=3147
if (se != null && se.getCode() == ServerException.EXECUTION_TIMEOUT) {
shouldThrow = true;
}

if (shouldThrow) {
throw new SQLTimeoutException("Query execution time exceeded limit (queryId=" + queryId + ")", e);
}
}

@Override
public int executeUpdate(String sql) throws SQLException {
ensureOpen();
Expand All @@ -396,6 +412,7 @@
lastQueryId = response.getQueryId();
} catch (Exception e) {
handleSocketTimeoutException(e);
throwOnExecutionTimeout(e, mergedSettings.getQueryId());
throw ExceptionUtils.toSqlState(e);
}

Expand Down Expand Up @@ -469,6 +486,30 @@
@Override
public void setQueryTimeout(int seconds) throws SQLException {
ensureOpen();
if (seconds < 0) {
throw new SQLException("Timeout should be >= 0 but " + seconds + " was passed");
}

if (seconds > 0) {
boolean isAsyncEnabled;
try {
isAsyncEnabled = Boolean.parseBoolean(
getConnection().getClient().getConfiguration().getOrDefault(ClientConfigProperties.ASYNC_OPERATIONS.getKey(),
ClientConfigProperties.ASYNC_OPERATIONS.getDefaultValue()));
} catch (Exception e) {
LOG.error("Failed to read client configuration " + ClientConfigProperties.ASYNC_OPERATIONS.getKey(), e);
isAsyncEnabled = false;
}

if (!isAsyncEnabled) {
// `max_execution_time` is only option when not async operations enabled
getLocalSettings().setMaxExecutionTime(seconds);
}
} else if (connectionLvlExecTimeout != null) {
getLocalSettings().setMaxExecutionTime(connectionLvlExecTimeout);
} else {
getLocalSettings().resetOption(ClientConfigProperties.serverSetting(ServerSettings.MAX_EXECUTION_TIME));
}
queryTimeout = seconds;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -794,26 +794,26 @@ public ResultSet getProcedureColumns(String catalog, String schemaPattern, Strin
static final Map<String, String> ENGINE_TO_TABLE_TYPE;
static {
Map<String, String> map = new java.util.HashMap<>();

// Log tables
map.put("Log", TableType.LOG_TABLE.getTypeName());
map.put("StripeLog", TableType.LOG_TABLE.getTypeName());
map.put("TinyLog", TableType.LOG_TABLE.getTypeName());

// Memory tables
map.put("Buffer", TableType.MEMORY_TABLE.getTypeName());
map.put("Memory", TableType.MEMORY_TABLE.getTypeName());
map.put("Set", TableType.MEMORY_TABLE.getTypeName());

// Views
map.put("View", TableType.VIEW.getTypeName());
map.put("LiveView", TableType.VIEW.getTypeName());
map.put("MaterializedView", TableType.MATERIALIZED_VIEW.getTypeName());
map.put("WindowView", TableType.VIEW.getTypeName());

// Dictionary
map.put("Dictionary", TableType.DICTIONARY.getTypeName());

// Remote/External tables
map.put("AzureBlobStorage", TableType.REMOTE_TABLE.getTypeName());
map.put("AzureQueue", TableType.REMOTE_TABLE.getTypeName());
Expand Down Expand Up @@ -899,6 +899,18 @@ public ResultSet getProcedureColumns(String catalog, String schemaPattern, Strin
// Remote engines (appended 08/31/2026)
map.put("BigQuery", TableType.REMOTE_TABLE.getTypeName());

// Paimon (appended 05/27/2026)
map.put("Paimon", TableType.REMOTE_TABLE.getTypeName());
map.put("PaimonAzure", TableType.REMOTE_TABLE.getTypeName());
map.put("PaimonHDFS", TableType.REMOTE_TABLE.getTypeName());
map.put("PaimonLocal", TableType.REMOTE_TABLE.getTypeName());
map.put("PaimonS3", TableType.REMOTE_TABLE.getTypeName());

// Remote engines (appended 07/21/2026)
map.put("QueryRunner", TableType.REMOTE_TABLE.getTypeName());
map.put("Remote", TableType.REMOTE_TABLE.getTypeName());
map.put("RemoteSecure", TableType.REMOTE_TABLE.getTypeName());

// Special
map.put("TimeSeries", TableType.TABLE.getTypeName());
map.put("Null", TableType.TABLE.getTypeName());
Expand Down
Loading
Loading