diff --git a/CHANGELOG.md b/CHANGELOG.md index 6af66dc6f..41592f8c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -734,6 +734,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 diff --git a/client-v2/src/main/java/com/clickhouse/client/api/ServerException.java b/client-v2/src/main/java/com/clickhouse/client/api/ServerException.java index c324e30ea..0abfabe39 100644 --- a/client-v2/src/main/java/com/clickhouse/client/api/ServerException.java +++ b/client-v2/src/main/java/com/clickhouse/client/api/ServerException.java @@ -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; @@ -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 diff --git a/client-v2/src/test/java/com/clickhouse/client/ClientTests.java b/client-v2/src/test/java/com/clickhouse/client/ClientTests.java index 50a80f589..a0df7ad1d 100644 --- a/client-v2/src/test/java/com/clickhouse/client/ClientTests.java +++ b/client-v2/src/test/java/com/clickhouse/client/ClientTests.java @@ -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()) { + + } catch (ServerException e) { + long queryTime = System.currentTimeMillis() - startTime; + Assert.assertTrue(Math.abs(queryTime - maxExecTime) < 1000); + Assert.assertEquals(e.getCode(), ServerException.EXECUTION_TIMEOUT); + } + } + @Test(groups = {"integration"}) public void testHostnameWithUnderscore() throws Exception { diff --git a/client-v2/src/test/java/com/clickhouse/client/api/internal/HttpAPIClientHelperTest.java b/client-v2/src/test/java/com/clickhouse/client/api/internal/HttpAPIClientHelperTest.java index 0654d8df2..5c3860b59 100644 --- a/client-v2/src/test/java/com/clickhouse/client/api/internal/HttpAPIClientHelperTest.java +++ b/client-v2/src/test/java/com/clickhouse/client/api/internal/HttpAPIClientHelperTest.java @@ -341,8 +341,7 @@ public void testExecuteRequestThrowsConnectExceptionOn503() throws Exception { @DataProvider(name = "serverExceptionRetryCases") public static Object[][] serverExceptionRetryCases() { - // Server code 159 (TIMEOUT_EXCEEDED) is retryable; code 60 (TABLE_NOT_FOUND) is not. - ServerException retryable = new ServerException(159, "TIMEOUT_EXCEEDED", 500, "q1"); + ServerException retryable = new ServerException(202, "TOO_MANY_SIMULTANEOUS_QUERIES", 500, "q1"); ServerException nonRetryable = new ServerException(60, "TABLE_NOT_FOUND", 404, "q2"); return new Object[][]{ // ServerException thrown directly (behaviour that already worked; pinned as contrast). diff --git a/client-v2/src/test/java/com/clickhouse/client/metrics/MetricsTest.java b/client-v2/src/test/java/com/clickhouse/client/metrics/MetricsTest.java index fd36c18eb..bfe671d99 100644 --- a/client-v2/src/test/java/com/clickhouse/client/metrics/MetricsTest.java +++ b/client-v2/src/test/java/com/clickhouse/client/metrics/MetricsTest.java @@ -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); diff --git a/jdbc-v2/src/main/java/com/clickhouse/jdbc/ConnectionImpl.java b/jdbc-v2/src/main/java/com/clickhouse/jdbc/ConnectionImpl.java index 090ceefd2..f947f7b70 100644 --- a/jdbc-v2/src/main/java/com/clickhouse/jdbc/ConnectionImpl.java +++ b/jdbc-v2/src/main/java/com/clickhouse/jdbc/ConnectionImpl.java @@ -122,6 +122,20 @@ public ConnectionImpl(String url, Properties info) throws SQLException { 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 clientProps = config.getClientProperties(); + for (Map.Entry 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(); diff --git a/jdbc-v2/src/main/java/com/clickhouse/jdbc/StatementImpl.java b/jdbc-v2/src/main/java/com/clickhouse/jdbc/StatementImpl.java index 4583b6ef8..c615ec4d1 100644 --- a/jdbc-v2/src/main/java/com/clickhouse/jdbc/StatementImpl.java +++ b/jdbc-v2/src/main/java/com/clickhouse/jdbc/StatementImpl.java @@ -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; @@ -15,10 +16,7 @@ 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; @@ -26,6 +24,7 @@ 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; @@ -66,6 +65,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; @@ -79,6 +80,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 { @@ -360,6 +363,7 @@ protected ResultSetImpl executeQueryImpl(String sql, QuerySettings settings) thr } handleSocketTimeoutException(e); onResultSetClosed(null); + throwOnExecutionTimeout(e, mergedSettings.getQueryId()); throw ExceptionUtils.toSqlState(e); } } @@ -370,6 +374,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); + } + } + @Override public int executeUpdate(String sql) throws SQLException { ensureOpen(); @@ -396,6 +412,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); } @@ -469,6 +486,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; } diff --git a/jdbc-v2/src/main/java/com/clickhouse/jdbc/metadata/DatabaseMetaDataImpl.java b/jdbc-v2/src/main/java/com/clickhouse/jdbc/metadata/DatabaseMetaDataImpl.java index 84bb1886c..482237e03 100644 --- a/jdbc-v2/src/main/java/com/clickhouse/jdbc/metadata/DatabaseMetaDataImpl.java +++ b/jdbc-v2/src/main/java/com/clickhouse/jdbc/metadata/DatabaseMetaDataImpl.java @@ -794,26 +794,26 @@ public ResultSet getProcedureColumns(String catalog, String schemaPattern, Strin static final Map ENGINE_TO_TABLE_TYPE; static { Map 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()); @@ -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()); diff --git a/jdbc-v2/src/test/java/com/clickhouse/jdbc/StatementTest.java b/jdbc-v2/src/test/java/com/clickhouse/jdbc/StatementTest.java index 7d9d7f461..2dba637e9 100644 --- a/jdbc-v2/src/test/java/com/clickhouse/jdbc/StatementTest.java +++ b/jdbc-v2/src/test/java/com/clickhouse/jdbc/StatementTest.java @@ -18,13 +18,7 @@ import org.testng.annotations.DataProvider; import org.testng.annotations.Test; -import java.sql.Array; -import java.sql.Connection; -import java.sql.Date; -import java.sql.ResultSet; -import java.sql.ResultSetMetaData; -import java.sql.SQLException; -import java.sql.Statement; +import java.sql.*; import java.time.LocalDate; import java.util.Arrays; import java.util.Collections; @@ -37,14 +31,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertNull; -import static org.testng.Assert.assertSame; -import static org.testng.Assert.assertThrows; -import static org.testng.Assert.assertTrue; -import static org.testng.Assert.fail; +import static org.testng.Assert.*; @Test(groups = {"integration"}) @@ -289,7 +276,7 @@ public static Object[][] asyncInsertSettingsDP() { @Test(groups = {"integration"}, dataProvider = "asyncInsertSettingsDP") public void testInsertWithAsyncInsert(String asyncInsert, String waitAsyncInsert, int expectedUpdateCount, int expectedSelectCount, boolean fails) throws Exception { String tableName = "test_async_insert_param_" + asyncInsert + "_" + waitAsyncInsert + "_" + UUID.randomUUID().toString().replace("-", "_"); - + Properties props = new Properties(); props.setProperty(ClientConfigProperties.serverSetting(ServerSettings.ASYNC_INSERT), asyncInsert); props.setProperty(ClientConfigProperties.serverSetting(ServerSettings.WAIT_ASYNC_INSERT), waitAsyncInsert); @@ -465,15 +452,90 @@ public void testJdbcEscapeSyntax() throws Exception { } @Test(groups = {"integration"}) - public void testExecuteQueryTimeout() throws Exception { + public void testExecuteQueryTimeoutAsyncOperation() throws Exception { + Properties config = new Properties(); + config.setProperty(ClientConfigProperties.ASYNC_OPERATIONS.getKey(), "true"); + try (Connection conn = getJdbcConnection(config)) { + try (Statement stmt = conn.createStatement()) { + long woTimeoutStart = System.currentTimeMillis(); + final String query = "SELECT count(), sum(sipHash64(number)) " + + "FROM numbers(1000000000) " + + "SETTINGS max_threads = 1;"; + try (ResultSet rs = stmt.executeQuery(query)) { + assertTrue(rs.next()); + } + long woTimeoutTime = System.currentTimeMillis() - woTimeoutStart; + + int queryTimeoutMs = (int) (woTimeoutTime * 0.75); + stmt.setQueryTimeout((int) TimeUnit.MILLISECONDS.toSeconds(queryTimeoutMs)); + + long wTimeoutStart = System.currentTimeMillis(); + expectThrows(SQLTimeoutException.class, () -> { + try (ResultSet rs = stmt.executeQuery(query)) { + assertTrue(rs.next()); + } + }); + long wTimeoutTime = System.currentTimeMillis() - wTimeoutStart; + assertTrue(Math.abs(wTimeoutTime - queryTimeoutMs) < 1000); + } + } + } + + @Test(groups = {"integration"}) + public void testExecuteQueryTimeoutServerTimeout() throws Exception { + + long woTimeoutTime; try (Connection conn = getJdbcConnection()) { try (Statement stmt = conn.createStatement()) { - stmt.setQueryTimeout(1); - assertThrows(SQLException.class, () -> { - try (ResultSet rs = stmt.executeQuery("SELECT sleep(5)")) { - assertFalse(rs.next()); + long woTimeoutStart = System.currentTimeMillis(); + final String query = "SELECT count(), sum(sipHash64(number)) " + + "FROM numbers(1000000000) " + + "SETTINGS max_threads = 1;"; + try (ResultSet rs = stmt.executeQuery(query)) { + assertTrue(rs.next()); + } + woTimeoutTime = System.currentTimeMillis() - woTimeoutStart; + } + } + + int queryTimeoutMs = (int) (woTimeoutTime * 0.75); + Properties config = new Properties(); + config.setProperty(ClientConfigProperties.serverSetting("max_execution_time"), String.valueOf(TimeUnit.MILLISECONDS.toSeconds(queryTimeoutMs))); + try (Connection conn = getJdbcConnection(config)) { + try (Statement stmt = conn.createStatement()) { + final String query = "SELECT count(), sum(sipHash64(number)) " + + "FROM numbers(1000000000) " + + "SETTINGS max_threads = 1;"; + + long wTimeoutStart = System.currentTimeMillis(); + expectThrows(SQLTimeoutException.class, () -> { + try (ResultSet rs = stmt.executeQuery(query)) { + assertTrue(rs.next()); + } + }); + long wTimeoutTime = System.currentTimeMillis() - wTimeoutStart; + assertTrue(Math.abs(wTimeoutTime - queryTimeoutMs) < 1000); + } + } + + // test async because it wraps exceptions + config = new Properties(); + config.setProperty(ClientConfigProperties.serverSetting("max_execution_time"), String.valueOf(TimeUnit.MILLISECONDS.toSeconds(queryTimeoutMs))); + config.setProperty(ClientConfigProperties.ASYNC_OPERATIONS.getKey(), "true"); + try (Connection conn = getJdbcConnection(config)) { + try (Statement stmt = conn.createStatement()) { + final String query = "SELECT count(), sum(sipHash64(number)) " + + "FROM numbers(1000000000) " + + "SETTINGS max_threads = 1;"; + + long wTimeoutStart = System.currentTimeMillis(); + expectThrows(SQLTimeoutException.class, () -> { + try (ResultSet rs = stmt.executeQuery(query)) { + assertTrue(rs.next()); } }); + long wTimeoutTime = System.currentTimeMillis() - wTimeoutStart; + assertTrue(Math.abs(wTimeoutTime - queryTimeoutMs) < 1000); } } } @@ -1763,6 +1825,123 @@ public void testEscapedSQLToNative(String sql, String expected) { assertEquals(StatementImpl.escapedSQLToNative(sql), expected); } + + private void assertQueryTimeout(Statement stmt, int expectedTimeoutSec) { + final String slowQuery = "SELECT count(), sum(sipHash64(number)) FROM numbers(1000000000) SETTINGS max_threads = 1;"; + long start = System.currentTimeMillis(); + expectThrows(SQLTimeoutException.class, () -> { + try (ResultSet rs = stmt.executeQuery(slowQuery)) { + assertTrue(rs.next()); + } + }); + long elapsed = System.currentTimeMillis() - start; + long expectedMs = expectedTimeoutSec * 1000L; + assertTrue(Math.abs(elapsed - expectedMs) < 1000, + "Expected timeout ~" + expectedMs + "ms, but execution took " + elapsed + "ms"); + } + + @Test(groups = {"integration"}) + public void testConnectionLevelExecutionTimeoutOverriddenByStatement() throws Exception { + Properties config = new Properties(); + final int connExecTimeout = 7; + config.setProperty(ClientConfigProperties.serverSetting(ServerSettings.MAX_EXECUTION_TIME), String.valueOf(connExecTimeout)); + try (Connection conn = getJdbcConnection(config); + StatementImpl stmt = (StatementImpl) conn.createStatement()) { + assertEquals(stmt.connectionLvlExecTimeout, Integer.valueOf(connExecTimeout)); + assertEquals(stmt.getLocalSettings().getMaxExecutionTime(), connExecTimeout); + assertEquals(stmt.getQueryTimeout(), 0); + assertQueryTimeout(stmt, connExecTimeout); + + final int stmtExecTimeout = 5; + stmt.setQueryTimeout(stmtExecTimeout); + assertEquals(stmt.getQueryTimeout(), stmtExecTimeout); + assertEquals(stmt.getLocalSettings().getMaxExecutionTime(), stmtExecTimeout); + assertQueryTimeout(stmt, stmtExecTimeout); + + // reset back to connection + stmt.setQueryTimeout(0); + assertEquals(stmt.getQueryTimeout(), 0); + assertEquals(stmt.getLocalSettings().getMaxExecutionTime(), connExecTimeout); + } + + config = new Properties(); + config.setProperty(DriverProperties.DEFAULT_QUERY_SETTINGS.getKey(), "max_execution_time=" + connExecTimeout); + try (Connection conn = getJdbcConnection(config); + StatementImpl stmt = (StatementImpl) conn.createStatement()) { + assertEquals(stmt.connectionLvlExecTimeout, connExecTimeout); + assertEquals(stmt.getLocalSettings().getMaxExecutionTime(), connExecTimeout); + } + } + + @Test(groups = {"integration"}) + public void testAsyncOperationsEnabledTimeout() throws Exception { + Properties config = new Properties(); + config.setProperty(ClientConfigProperties.ASYNC_OPERATIONS.getKey(), "true"); + try (Connection conn = getJdbcConnection(config); + StatementImpl stmt = (StatementImpl) conn.createStatement()) { + assertNull(stmt.connectionLvlExecTimeout); + assertNull(stmt.getLocalSettings().getMaxExecutionTime()); + assertEquals(stmt.getQueryTimeout(), 0); + + final int stmtExecTimeout = 5; + stmt.setQueryTimeout(stmtExecTimeout); + assertEquals(stmt.getQueryTimeout(), stmtExecTimeout); + assertNull(stmt.getLocalSettings().getMaxExecutionTime()); + assertQueryTimeout(stmt, stmtExecTimeout); + + stmt.setQueryTimeout(0); + assertEquals(stmt.getQueryTimeout(), 0); + assertNull(stmt.getLocalSettings().getMaxExecutionTime()); + } + } + + @Test(groups = {"integration"}) + public void testAsyncOperationsEnabledWithConnectionLevelTimeout() throws Exception { + Properties config = new Properties(); + final int connExecTimeout = 7; + config.setProperty(ClientConfigProperties.ASYNC_OPERATIONS.getKey(), "true"); + config.setProperty(ClientConfigProperties.serverSetting(ServerSettings.MAX_EXECUTION_TIME), String.valueOf(connExecTimeout)); + try (Connection conn = getJdbcConnection(config); + StatementImpl stmt = (StatementImpl) conn.createStatement()) { + assertEquals(stmt.connectionLvlExecTimeout, connExecTimeout); + assertEquals(stmt.getLocalSettings().getMaxExecutionTime(), connExecTimeout); + assertEquals(stmt.getQueryTimeout(), 0); + + assertQueryTimeout(stmt, connExecTimeout); + + int stmtExecTimeout = 5; + stmt.setQueryTimeout(stmtExecTimeout); + assertEquals(stmt.getQueryTimeout(), stmtExecTimeout); + assertEquals(stmt.getLocalSettings().getMaxExecutionTime(), connExecTimeout); + assertQueryTimeout(stmt, stmtExecTimeout); + + stmt.setQueryTimeout(0); + assertEquals(stmt.getQueryTimeout(), 0); + assertEquals(stmt.getLocalSettings().getMaxExecutionTime(), connExecTimeout); + } + } + + @Test(groups = {"integration"}) + public void testNoConnectionLevelTimeoutOverriddenAndReset() throws Exception { + try (Connection conn = getJdbcConnection(); + StatementImpl stmt = (StatementImpl) conn.createStatement()) { + assertNull(stmt.connectionLvlExecTimeout); + assertNull(stmt.getLocalSettings().getMaxExecutionTime()); + assertEquals(stmt.getQueryTimeout(), 0); + + stmt.setQueryTimeout(1); + assertEquals(stmt.getQueryTimeout(), 1); + assertEquals(stmt.getLocalSettings().getMaxExecutionTime(), Integer.valueOf(1)); + assertQueryTimeout(stmt, 1); + + stmt.setQueryTimeout(0); + assertEquals(stmt.getQueryTimeout(), 0); + assertNull(stmt.getLocalSettings().getMaxExecutionTime()); + + assertThrows(SQLException.class, () -> stmt.setQueryTimeout(-1)); + } + } + private static String getDBName(Statement stmt) throws SQLException { try (ResultSet rs = stmt.executeQuery("SELECT database()")) { rs.next(); diff --git a/jdbc-v2/src/test/resources/StatementSQLTests.yaml b/jdbc-v2/src/test/resources/StatementSQLTests.yaml index 304d495a9..c9552b06e 100644 --- a/jdbc-v2/src/test/resources/StatementSQLTests.yaml +++ b/jdbc-v2/src/test/resources/StatementSQLTests.yaml @@ -77,7 +77,7 @@ - name: column_types expected: ["UInt64", "UInt64"] - name: explain_stmt_01 - query: EXPLAIN SELECT 1 + query: EXPLAIN AST SELECT 1 tables: events: datasets/empty_table checks: