Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
## 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 @@ -27,7 +27,7 @@ public void connectionTest() throws SQLException {
}
}

@Test
@Test(enabled = false) // skipped to be removed after reviewing tests.
public void connectionWithPropertiesTest() throws SQLException {
Comment thread
chernser marked this conversation as resolved.
Properties properties = new Properties();
properties.setProperty("user", "default");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ public class ServerException extends ClickHouseException {

public static final int UNKNOWN_SETTING = 115;

public static final int EXECUTION_TIMEOUT = 159;

private final int code;

private final int transportProtocolCode;
Expand Down Expand Up @@ -56,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
case 164: // READONLY
case 202: // TOO_MANY_SIMULTANEOUS_QUERIES
case 203: // NO_FREE_CONNECTION
Expand Down
38 changes: 20 additions & 18 deletions client-v2/src/test/java/com/clickhouse/client/ClientTests.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,6 @@
package com.clickhouse.client;

import com.clickhouse.client.api.Client;
import com.clickhouse.client.api.ClientConfigProperties;
import com.clickhouse.client.api.ClientException;
import com.clickhouse.client.api.ClientFaultCause;
import com.clickhouse.client.api.ClientMisconfigurationException;
import com.clickhouse.client.api.ConnectionReuseStrategy;
import com.clickhouse.client.api.ServerException;
import com.clickhouse.client.api.*;
import com.clickhouse.client.api.enums.Protocol;
import com.clickhouse.client.api.insert.InsertSettings;
import com.clickhouse.client.api.internal.ClickHouseLZ4OutputStream;
Expand All @@ -30,17 +24,7 @@

import java.io.ByteArrayInputStream;
import java.net.ConnectException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Queue;
import java.util.Set;
import java.util.UUID;
import java.util.*;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
Expand Down Expand Up @@ -569,6 +553,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()) {

} 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.
}
Comment thread
chernser marked this conversation as resolved.

public boolean isVersionMatch(String versionExpression, Client client) {
List<GenericRecord> serverVersion = client.queryAll("SELECT version()");
return ClickHouseVersion.of(serverVersion.get(0).getString(1)).check(versionExpression);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,7 @@
import org.testng.annotations.Test;

import java.time.temporal.ChronoUnit;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.*;

import static org.testng.Assert.assertEquals;
import static org.testng.Assert.fail;
Expand All @@ -40,7 +38,7 @@ void tearDown() {
meterRegistry.clear();
Metrics.globalRegistry.clear();
}

@Test(groups = {"integration"}, enabled = true)
public void testRegisterMetrics() throws Exception {
ClickHouseNode node = getServer(ClickHouseProtocol.HTTP);
Expand All @@ -54,6 +52,7 @@ public void testRegisterMetrics() throws Exception {
.serverSetting(ServerSettings.ASYNC_INSERT, "0")
.serverSetting(ServerSettings.WAIT_END_OF_QUERY, "1")
.registerClientMetrics(meterRegistry, "pool-test")
.setKeepAliveTimeout(10, ChronoUnit.SECONDS) // enforce connection cleanup
.build()) {

client.ping();
Expand All @@ -66,21 +65,38 @@ public void testRegisterMetrics() throws Exception {
Assert.assertEquals((int) available.value(), 1);
Assert.assertEquals((int) leased.value(), 0);

CountDownLatch responsesReady = new CountDownLatch(2);
CountDownLatch releaseResponses = new CountDownLatch(1);
Runnable task = () -> {
try (QueryResponse response = client.query("SELECT 1").get()) {
Assert.assertEquals((int) available.value(), 0);
Assert.assertEquals((int) leased.value(), 1);
responsesReady.countDown();
Assert.assertTrue(releaseResponses.await(10, TimeUnit.SECONDS),
"Timed out waiting to release query responses");
} catch (Exception e) {
e.printStackTrace();
fail("Failed to to request", e);
throw new RuntimeException("Failed to execute request", e);
}
};

ExecutorService executor = Executors.newFixedThreadPool(3);
executor.submit(task);
executor.submit(task);
executor.shutdown();
executor.awaitTermination(10, TimeUnit.SECONDS);
ExecutorService executor = Executors.newFixedThreadPool(2);
Future<?> firstQuery = executor.submit(task);
Future<?> secondQuery = executor.submit(task);
try {
try {
Assert.assertTrue(responsesReady.await(10, TimeUnit.SECONDS),
"Timed out waiting for concurrent query responses");
Assert.assertEquals((int) available.value(), 0);
Assert.assertEquals((int) leased.value(), 2);
} finally {
releaseResponses.countDown();
}
firstQuery.get(10, TimeUnit.SECONDS);
secondQuery.get(10, TimeUnit.SECONDS);
} finally {
releaseResponses.countDown();
executor.shutdownNow();
}
Assert.assertTrue(executor.awaitTermination(10, TimeUnit.SECONDS),
"Timed out waiting for query executor to terminate");

Assert.assertEquals((int) available.value(), 2);
Assert.assertEquals((int) leased.value(), 0);
Expand All @@ -90,7 +106,10 @@ public void testRegisterMetrics() throws Exception {
Assert.assertEquals((int) available.value(), 2);
Assert.assertEquals((int) leased.value(), 0);

task.run();
try (QueryResponse response = client.query("SELECT 1").get()) {
Assert.assertEquals((int) available.value(), 0);
Assert.assertEquals((int) leased.value(), 1);
}

Assert.assertEquals((int) available.value(), 1);
Assert.assertEquals((int) leased.value(), 0);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -988,6 +988,10 @@ public void testIntegerDataTypes() {

@Test(groups = {"integration"})
public void testFloatDataTypes() {
final boolean usesPreciseFloatParsing = isVersionMatch("[26.7,)");
final float expectedMaxFloat32 = usesPreciseFloatParsing ? Float.MAX_VALUE : 3.4028233E38F;
final double expectedMinFloat64 = usesPreciseFloatParsing ? Double.MIN_VALUE : 0.0D;

final List<String> columns = Arrays.asList(
"min_float32 Float32",
"max_float32 Float32",
Expand Down Expand Up @@ -1019,12 +1023,12 @@ public void testFloatDataTypes() {

});
verifiers.add(r -> {
Assert.assertEquals(r.getFloat("max_float32"), 3.4028233E38F); // TODO: investigate why it's not Float.MAX_VALUE returned from server
Assert.assertEquals(r.getFloat(2), 3.4028233E38F);
Assert.assertEquals(r.getFloat("max_float32"), expectedMaxFloat32);
Assert.assertEquals(r.getFloat(2), expectedMaxFloat32);
});
verifiers.add(r -> {
Assert.assertEquals(r.getDouble("min_float64"), 0.0D); // TODO: investigate why it's not Double.MIN_VALUE returned from server
Assert.assertEquals(r.getDouble(3), 0.0D);
Assert.assertEquals(r.getDouble("min_float64"), expectedMinFloat64);
Assert.assertEquals(r.getDouble(3), expectedMinFloat64);
});
verifiers.add(r -> {
Assert.assertEquals(r.getDouble("max_float64"), Double.MAX_VALUE);
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 @@ -114,6 +114,20 @@ public ConnectionImpl(String url, Properties info) throws SQLException {
.serverSetting(ServerSettings.ASYNC_INSERT, "0")
.serverSetting(ServerSettings.WAIT_END_OF_QUERY, "0");

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)) {
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.ClickHouseBinaryFormatReader;
import com.clickhouse.client.api.internal.ServerSettings;
import com.clickhouse.client.api.query.QueryResponse;
Expand All @@ -13,15 +14,13 @@
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.ArrayList;
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;

public class StatementImpl implements Statement, JdbcV2Wrapper {
Expand Down Expand Up @@ -54,6 +53,8 @@ public class StatementImpl implements Statement, JdbcV2Wrapper {
// 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 @@ -67,6 +68,8 @@ public StatementImpl(ConnectionImpl connection) throws SQLException {
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 @@ -215,6 +218,7 @@ protected ResultSetImpl executeQueryImpl(String sql, QuerySettings settings) thr
}
handleSocketTimeoutException(e);
onResultSetClosed(null);
throwOnExecutionTimeout(e, mergedSettings.getQueryId());
Comment thread
chernser marked this conversation as resolved.
throw ExceptionUtils.toSqlState(e);
}
}
Expand All @@ -225,6 +229,18 @@ protected void handleSocketTimeoutException(Exception e) {
}
}

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;
if (se != null && se.getCode() == ServerException.EXECUTION_TIMEOUT) {
shouldThrow = true;
}

if (shouldThrow) {
throw new SQLTimeoutException("Query execution time exceeded limit (queryId=" + queryId + ")", e);
}
Comment thread
cursor[bot] marked this conversation as resolved.
}

@Override
public int executeUpdate(String sql) throws SQLException {
ensureOpen();
Expand All @@ -251,6 +267,7 @@ protected long executeUpdateImpl(String sql, QuerySettings settings) throws SQLE
lastQueryId = response.getQueryId();
} catch (Exception e) {
handleSocketTimeoutException(e);
throwOnExecutionTimeout(e, mergedSettings.getQueryId());
throw ExceptionUtils.toSqlState(e);
}

Expand Down Expand Up @@ -324,6 +341,30 @@ public int getQueryTimeout() throws SQLException {
@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 @@ -60,7 +60,21 @@ private static Set<String> initAllowedKeywordAliases() {
"UNDROP", "UNFREEZE", "UNIQUE", "UNLOCK", "UNSET", "UNSIGNED", "UNTIL", "UPDATE", "URL", "USE",
"USER", "VALID", "VALUES", "VARYING", "VIEW", "VISIBLE", "VOLUME", "WATCH", "WATERMARK", "WEEK",
"WEEKS", "WHEN", "WITH_ITEMINDEX", "WK", "WORKER", "WORKLOAD", "WRITABLE", "WRITE", "WW",
"YEAR", "YEARS", "YY", "YYYY", "ZKPATH");
"YEAR", "YEARS", "YY", "YYYY", "ZKPATH",
// Appended 04/01/2026.
"CENTURY", "DECADE", "DOW", "DOY", "EPOCH", "ISODOW", "ISOYEAR", "MILLENNIUM", "NATURAL", "SOME",
"ZONE",
// Appended 04/10/2026
"PATH", "PLACING",
// Appended 05/27/2026
"CURSOR", "DETERMINISTIC", "ESCAPE", "SAMPLES", "STREAM", "UNKNOWN",
// Appended 06/10/2026
"IPV4_PREFIX_BITS", "IPV6_PREFIX_BITS", "TIMEZONE_HOUR", "TIMEZONE_MINUTE",
// Appended 06/26/2026
"ENUM", "HYPOTHETICAL", "WHATIF",
// Appended 07/21/2026
"ANALYZE", "AT", "MANIFEST", "RESERVATION"
);
}

private static Set<String> buildKeywordSet(String... values) {
Expand Down
Loading
Loading