From 1cc71a22dd3a7035f312aba3f9ce51a24be08d32 Mon Sep 17 00:00:00 2001 From: Sergey Chernov Date: Tue, 16 Sep 2025 14:37:25 -0700 Subject: [PATCH 1/5] fixed max rows. added check for execute method if they supported with specific parameters. --- .../com/clickhouse/jdbc/DriverProperties.java | 12 ++ .../com/clickhouse/jdbc/ResultSetImpl.java | 22 ++- .../com/clickhouse/jdbc/StatementImpl.java | 48 ++++--- .../jdbc/internal/JdbcConfiguration.java | 8 ++ .../com/clickhouse/jdbc/StatementTest.java | 125 ++++++++++++------ 5 files changed, 157 insertions(+), 58 deletions(-) diff --git a/jdbc-v2/src/main/java/com/clickhouse/jdbc/DriverProperties.java b/jdbc-v2/src/main/java/com/clickhouse/jdbc/DriverProperties.java index 1f138cb47..8339aad38 100644 --- a/jdbc-v2/src/main/java/com/clickhouse/jdbc/DriverProperties.java +++ b/jdbc-v2/src/main/java/com/clickhouse/jdbc/DriverProperties.java @@ -1,5 +1,7 @@ package com.clickhouse.jdbc; +import com.clickhouse.client.api.internal.ServerSettings; + import java.util.Collections; import java.util.List; @@ -35,6 +37,16 @@ public enum DriverProperties { * Enables closing result set before */ RESULTSET_AUTO_CLOSE("jdbc_resultset_auto_close", "true"), + + /** + * Enables using server property `max_result_rows` ({@link ServerSettings#MAX_RESULT_ROWS} to limit number of rows returned by query. + * Enabling this property will override user set overflow mode. It may cause error if server doesn't allow changing properties. + * When this property is not enabled then result set will stop reading data once limit is reached. As server may have + * more in a result set then it will require time to read all data to make HTTP connection usable again. In most cases + * this is fine. It is recommended to set limit in SQL query. + * + */ + USE_MAX_RESULT_ROWS("jdbc_use_max_result_rows", "false"), ; diff --git a/jdbc-v2/src/main/java/com/clickhouse/jdbc/ResultSetImpl.java b/jdbc-v2/src/main/java/com/clickhouse/jdbc/ResultSetImpl.java index d7bf05525..462b0645b 100644 --- a/jdbc-v2/src/main/java/com/clickhouse/jdbc/ResultSetImpl.java +++ b/jdbc-v2/src/main/java/com/clickhouse/jdbc/ResultSetImpl.java @@ -57,6 +57,9 @@ public class ResultSetImpl implements ResultSet, JdbcV2Wrapper { private int rowPos; private int fetchSize; + private int fetchDirection; + final private int maxFieldSize; + final private int maxRows; private Consumer onDataTransferException; @@ -77,6 +80,9 @@ public ResultSetImpl(StatementImpl parentStatement, QueryResponse response, Clic this.defaultCalendar = parentStatement.getConnection().defaultCalendar; this.rowPos = BEFORE_FIRST; this.fetchSize = parentStatement.getFetchSize(); + this.fetchDirection = parentStatement.getFetchDirection(); + this.maxFieldSize = parentStatement.getMaxFieldSize(); + this.maxRows = parentStatement.getMaxRows(); this.onDataTransferException = onDataTransferException; } @@ -103,6 +109,15 @@ private String columnIndexToName(int index) throws SQLException { public boolean next() throws SQLException { checkClosed(); + if (rowPos == AFTER_LAST) { + return false; + } + + if (maxRows > 0 && rowPos == maxRows) { + rowPos = AFTER_LAST; + return false; + } + try { Object readerRow = reader.next(); if (readerRow != null) { @@ -543,7 +558,7 @@ public boolean isFirst() throws SQLException { @Override public boolean isLast() throws SQLException { checkClosed(); - return !reader.hasNext() && rowPos != AFTER_LAST && rowPos != BEFORE_FIRST; + return (!reader.hasNext() || rowPos == maxRows) && rowPos != AFTER_LAST && rowPos != BEFORE_FIRST; } @Override @@ -607,15 +622,16 @@ public boolean previous() throws SQLException { @Override public int getFetchDirection() throws SQLException { checkClosed(); - return FETCH_FORWARD; + return fetchDirection; } @Override public void setFetchDirection(int direction) throws SQLException { checkClosed(); - if (direction != ResultSet.FETCH_FORWARD) { + if (getType() == TYPE_FORWARD_ONLY && direction != ResultSet.FETCH_FORWARD) { throw new SQLException("This result set object is of FORWARD ONLY type. Only ResultSet.FETCH_FORWARD is allowed as fetchDirection."); } + fetchDirection = direction; } @Override 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 4f167f997..722b5cb37 100644 --- a/jdbc-v2/src/main/java/com/clickhouse/jdbc/StatementImpl.java +++ b/jdbc-v2/src/main/java/com/clickhouse/jdbc/StatementImpl.java @@ -7,6 +7,7 @@ import com.clickhouse.client.api.query.QuerySettings; import com.clickhouse.client.api.sql.SQLUtils; import com.clickhouse.jdbc.internal.ExceptionUtils; +import com.clickhouse.jdbc.internal.FeatureManager; import com.clickhouse.jdbc.internal.ParsedStatement; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -29,6 +30,7 @@ public class StatementImpl implements Statement, JdbcV2Wrapper { ConnectionImpl connection; protected int queryTimeout; protected boolean isPoolable = false; // Statement is not poolable by default + private final FeatureManager featureManager; // State private volatile boolean closed; @@ -60,6 +62,7 @@ public StatementImpl(ConnectionImpl connection) throws SQLException { this.resultSets= new ConcurrentLinkedQueue<>(); this.resultSetAutoClose = connection.getJdbcConfig().isSet(DriverProperties.RESULTSET_AUTO_CLOSE); this.escapeProcessingEnabled = true; + this.featureManager = new FeatureManager(connection.getJdbcConfig()); } protected void ensureOpen() throws SQLException { @@ -458,22 +461,22 @@ public boolean getMoreResults(int current) throws SQLException { return false; // false indicates that no more results (or it is an update count) } -// @Override +// @Override -- because doesn't exist in Java 8 public String enquoteLiteral(String val) throws SQLException { return SQLUtils.enquoteLiteral(val); } -// @Override +// @Override -- because doesn't exist in Java 8 public String enquoteIdentifier(String identifier, boolean alwaysQuote) throws SQLException { return SQLUtils.enquoteIdentifier(identifier, alwaysQuote); } -// @Override +// @Override -- because doesn't exist in Java 8 public boolean isSimpleIdentifier(String identifier) throws SQLException { return SQLUtils.isSimpleIdentifier(identifier); } -// @Override +// @Override -- because doesn't exist in Java 8 public String enquoteNCharLiteral(String val) throws SQLException { if (val == null) { throw new NullPointerException(); @@ -489,31 +492,37 @@ public ResultSet getGeneratedKeys() throws SQLException { @Override public int executeUpdate(String sql, int autoGeneratedKeys) throws SQLException { + featureManager.unsupportedFeatureThrow("executeUpdate(String, int)", autoGeneratedKeys != Statement.NO_GENERATED_KEYS); return executeUpdate(sql); } @Override public int executeUpdate(String sql, int[] columnIndexes) throws SQLException { + featureManager.unsupportedFeatureThrow("executeUpdate(String, int[])"); return executeUpdate(sql); } @Override public int executeUpdate(String sql, String[] columnNames) throws SQLException { + featureManager.unsupportedFeatureThrow("executeUpdate(String, String[])"); return executeUpdate(sql); } @Override public boolean execute(String sql, int autoGeneratedKeys) throws SQLException { + featureManager.unsupportedFeatureThrow("execute(String, int)", autoGeneratedKeys != Statement.NO_GENERATED_KEYS); return execute(sql); } @Override public boolean execute(String sql, int[] columnIndexes) throws SQLException { + featureManager.unsupportedFeatureThrow("execute(String, int[])"); return execute(sql); } @Override public boolean execute(String sql, String[] columnNames) throws SQLException { + featureManager.unsupportedFeatureThrow("execute(String, String[])"); return execute(sql); } @@ -573,20 +582,23 @@ public long getLargeUpdateCount() throws SQLException { public void setLargeMaxRows(long max) throws SQLException { ensureOpen(); maxRows = max; - // This method override user set overflow mode on purpose: - // 1. Spec clearly states that after calling this method with a limit > 0 all rows over limit are dropped. - // 2. Calling this method should not cause throwing exception for future queries what only `break` can guarantee - // 3. If user wants different behavior then they are can use connection properties. - if (max > 0) { - localSettings.setOption(ClientConfigProperties.serverSetting(ServerSettings.MAX_RESULT_ROWS), maxRows); - localSettings.setOption(ClientConfigProperties.serverSetting(ServerSettings.RESULT_OVERFLOW_MODE), - ServerSettings.RESULT_OVERFLOW_MODE_BREAK); - } else { - // overriding potential client settings (set thru connection setup) - // there is no no limit value so we use very large limit. - localSettings.setOption(ClientConfigProperties.serverSetting(ServerSettings.MAX_RESULT_ROWS), Long.MAX_VALUE); - localSettings.setOption(ClientConfigProperties.serverSetting(ServerSettings.RESULT_OVERFLOW_MODE), - ServerSettings.RESULT_OVERFLOW_MODE_BREAK); + + if (connection.getJdbcConfig().isFlagSet(DriverProperties.USE_MAX_RESULT_ROWS)) { + // This method override user set overflow mode on purpose: + // 1. Spec clearly states that after calling this method with a limit > 0 all rows over limit are dropped. + // 2. Calling this method should not cause throwing exception for future queries what only `break` can guarantee + // 3. If user wants different behavior then they are can use connection properties. + if (max > 0) { + localSettings.setOption(ClientConfigProperties.serverSetting(ServerSettings.MAX_RESULT_ROWS), maxRows); + localSettings.setOption(ClientConfigProperties.serverSetting(ServerSettings.RESULT_OVERFLOW_MODE), + ServerSettings.RESULT_OVERFLOW_MODE_BREAK); + } else { + // overriding potential client settings (set thru connection setup) + // there is no no limit value so we use very large limit. + localSettings.setOption(ClientConfigProperties.serverSetting(ServerSettings.MAX_RESULT_ROWS), Long.MAX_VALUE); + localSettings.setOption(ClientConfigProperties.serverSetting(ServerSettings.RESULT_OVERFLOW_MODE), + ServerSettings.RESULT_OVERFLOW_MODE_BREAK); + } } } diff --git a/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/JdbcConfiguration.java b/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/JdbcConfiguration.java index 73de6bbfc..bb574e6ef 100644 --- a/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/JdbcConfiguration.java +++ b/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/JdbcConfiguration.java @@ -309,6 +309,10 @@ public static String getDefaultClientName() { } public boolean isBetaFeatureEnabled(DriverProperties prop) { + return isFlagSet(prop); + } + + public boolean isFlagSet(DriverProperties prop) { String value = driverProperties.getOrDefault(prop.getKey(), prop.getDefaultValue()); return Boolean.parseBoolean(value); } @@ -318,4 +322,8 @@ private Map> defaultTypeHintMapping() { mapping.put(ClickHouseDataType.Array, List.class); return mapping; } + + public boolean useMaxResultRows() { + return isFlagSet(DriverProperties.USE_MAX_RESULT_ROWS); + } } 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 2a562bae5..34fb3a0c8 100644 --- a/jdbc-v2/src/test/java/com/clickhouse/jdbc/StatementTest.java +++ b/jdbc-v2/src/test/java/com/clickhouse/jdbc/StatementTest.java @@ -4,6 +4,7 @@ import com.clickhouse.client.api.internal.ServerSettings; import com.clickhouse.client.api.query.GenericRecord; import com.clickhouse.data.ClickHouseVersion; +import com.clickhouse.jdbc.internal.JdbcConfiguration; import org.apache.commons.lang3.RandomStringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -21,7 +22,10 @@ import java.sql.Statement; import java.time.LocalDate; import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Properties; import java.util.concurrent.CountDownLatch; @@ -744,61 +748,108 @@ public void testWasNullFlagArray() throws Exception { } } - @Test(groups = {"integration"}) - public void testExecuteWithMaxRows() throws Exception { - try (Connection conn = getJdbcConnection()) { + @Test(groups = {"integration"}, dataProvider = "testMaxRowsDP") + public void testMaxRows(Map props, Long maxRows, boolean exactMatch) throws Exception { + Properties p = new Properties(); + p.putAll(props); + try (Connection conn = getJdbcConnection(p)) { try (Statement stmt = conn.createStatement()) { - stmt.setMaxRows(10); - assertEquals(stmt.getMaxRows(), 10); - assertEquals(stmt.getLargeMaxRows(), 10); + if (maxRows != null) { + stmt.setMaxRows(maxRows.intValue()); + assertEquals(stmt.getMaxRows(), maxRows.intValue()); + assertEquals(stmt.getLargeMaxRows(), maxRows); + } - stmt.setMaxRows(1); - int count = 0; - try (ResultSet rs = stmt.executeQuery("SELECT * FROM generate_series(0, 100000)")) { - while (rs.next()) { - count++; + for (int i = 0; i < 3; i++) { // to check that subsequent queries are not affected + int count = 0; + try (ResultSet rs = stmt.executeQuery("SELECT * FROM generate_series(0, 100000)")) { + int lastRow = 0; + while (rs.next()) { + count++; + if (rs.isLast()) { + lastRow = rs.getRow(); + } + } + + int expectedRows = maxRows == null || maxRows == 0L ? 100001 : maxRows.intValue(); + + if (exactMatch) { + assertEquals(lastRow, expectedRows); + assertEquals(count, expectedRows); + } else { + // MaxRows limits the number of row with rounding to the size of the block + // https://clickhouse.com/docs/en/operations/settings/query-complexity#setting-max_result_rows + // https://clickhouse.com/docs/en/operations/settings/query-complexity#result-overflow-mode + assertTrue(count > 0 && count < 100001); + } + } + } + + // check that settings can be reset + { + int expectedRows = 100001; + stmt.setMaxRows(0); + try (ResultSet rs = stmt.executeQuery("SELECT * FROM generate_series(0, 100000)")) { + int lastRow = 0; + int count = 0; + while (rs.next()) { + count++; + if (rs.isLast()) { + lastRow = rs.getRow(); + } + } + + if (props.containsKey(ClientConfigProperties.serverSetting(ServerSettings.MAX_RESULT_ROWS))) { + assertTrue(count > 0 && count < expectedRows); + } else { + assertEquals(lastRow, expectedRows); + assertEquals(count, expectedRows); + } } } - // MaxRows limits the number of row with rounding to the size of the block - // https://clickhouse.com/docs/en/operations/settings/query-complexity#setting-max_result_rows - // https://clickhouse.com/docs/en/operations/settings/query-complexity#result-overflow-mode - assertTrue(count > 0 && count < 100000); } } + } - Properties props = new Properties(); - props.setProperty(ClientConfigProperties.serverSetting(ServerSettings.RESULT_OVERFLOW_MODE), - ServerSettings.RESULT_OVERFLOW_MODE_THROW); - props.setProperty(ClientConfigProperties.serverSetting(ServerSettings.MAX_RESULT_ROWS), "100"); - try (Connection conn = getJdbcConnection(props); - Statement stmt = conn.createStatement()) { + @DataProvider(name = "testMaxRowsDP") + static Object[][] testMaxRowsDP() { + Map userDefinedMaxResultRows = new HashMap<>(); + userDefinedMaxResultRows.put(ClientConfigProperties.serverSetting(ServerSettings.MAX_RESULT_ROWS), "1000"); + userDefinedMaxResultRows.put(ClientConfigProperties.serverSetting(ServerSettings.RESULT_OVERFLOW_MODE), ServerSettings.RESULT_OVERFLOW_MODE_BREAK); - Assert.assertThrows(SQLException.class, () -> stmt.execute("SELECT * FROM generate_series(0, 100000)")); - { - stmt.setMaxRows(10); + return new Object[][] { + {Collections.emptyMap(), null, true}, + {Collections.emptyMap(), 0L, true}, + {Collections.emptyMap(), 100L, true}, + {userDefinedMaxResultRows, 2000L, false }, + {userDefinedMaxResultRows, 500L, true }, + }; + } - int count = 0; + @Test(groups = {"integration"}) + public void testMaxRowsWithOverflowMode() throws Exception { + Properties props = new Properties(); + props.put(ClientConfigProperties.serverSetting(ServerSettings.MAX_RESULT_ROWS), "1000"); + props.put(ClientConfigProperties.serverSetting(ServerSettings.RESULT_OVERFLOW_MODE), ServerSettings.RESULT_OVERFLOW_MODE_THROW); + props.put(DriverProperties.USE_MAX_RESULT_ROWS.getKey(), "true"); + try (Connection conn = getJdbcConnection(props)) { + assertThrows(() -> conn.createStatement().executeQuery("SELECT * FROM generate_series(0, 100000)")); + + try (Statement stmt = conn.createStatement()) { + stmt.setMaxRows(1000); try (ResultSet rs = stmt.executeQuery("SELECT * FROM generate_series(0, 100000)")) { + int count = 0; while (rs.next()) { count++; } + assertEquals(count, 1000); } - assertTrue(count > 0 && count < 100000); } - { - stmt.setMaxRows(0); - - int count = 0; - try (ResultSet rs = stmt.executeQuery("SELECT * FROM generate_series(0, 99999)")) { - while (rs.next()) { - count++; - } - } - assertEquals(count, 100000); - } } + + } @Test(groups = {"integration"}) From e0bdc8be34431bd151ee7f15a376efd157105df2 Mon Sep 17 00:00:00 2001 From: Sergey Chernov Date: Fri, 19 Sep 2025 11:15:34 -0700 Subject: [PATCH 2/5] added more tests --- .../com/clickhouse/jdbc/DriverProperties.java | 14 +++- .../com/clickhouse/jdbc/StatementImpl.java | 4 ++ .../com/clickhouse/jdbc/StatementTest.java | 66 ++++++++++++++++++- 3 files changed, 81 insertions(+), 3 deletions(-) diff --git a/jdbc-v2/src/main/java/com/clickhouse/jdbc/DriverProperties.java b/jdbc-v2/src/main/java/com/clickhouse/jdbc/DriverProperties.java index 8339aad38..16614430c 100644 --- a/jdbc-v2/src/main/java/com/clickhouse/jdbc/DriverProperties.java +++ b/jdbc-v2/src/main/java/com/clickhouse/jdbc/DriverProperties.java @@ -13,8 +13,18 @@ */ public enum DriverProperties { - IGNORE_UNSUPPORTED_VALUES("jdbc_ignore_unsupported_values", ""), - SCHEMA_TERM("jdbc_schema_term", ""), + /** + * Indicates if driver should ignore unsupported values and methods. + * JDBC allows throwing SQLException for unsupported values and methods. + * But driver can ignore them and continue execution. Driver will do no operation in such case. + */ + IGNORE_UNSUPPORTED_VALUES("jdbc_ignore_unsupported_values", "false"), + + /** + * Schema term to be used in the connection URL. Only `schema` is supported right now. + */ + SCHEMA_TERM("jdbc_schema_term", "schema"), + /** * Indicates if driver should create a secure connection over SSL/TLS */ 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 722b5cb37..6c9667f51 100644 --- a/jdbc-v2/src/main/java/com/clickhouse/jdbc/StatementImpl.java +++ b/jdbc-v2/src/main/java/com/clickhouse/jdbc/StatementImpl.java @@ -326,6 +326,7 @@ public void clearWarnings() throws SQLException { @Override public void setCursorName(String name) throws SQLException { + featureManager.unsupportedFeatureThrow("setCursorName(String)", true); ensureOpen(); } @@ -623,16 +624,19 @@ public long executeLargeUpdate(String sql) throws SQLException { @Override public long executeLargeUpdate(String sql, int autoGeneratedKeys) throws SQLException { + featureManager.unsupportedFeatureThrow("executeLargeUpdate(String, int)", autoGeneratedKeys != Statement.NO_GENERATED_KEYS); return executeLargeUpdate(sql); } @Override public long executeLargeUpdate(String sql, int[] columnIndexes) throws SQLException { + featureManager.unsupportedFeatureThrow("executeLargeUpdate(String, int[])"); return executeLargeUpdate(sql); } @Override public long executeLargeUpdate(String sql, String[] columnNames) throws SQLException { + featureManager.unsupportedFeatureThrow("executeLargeUpdate(String, String[])"); return executeLargeUpdate(sql); } 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 34fb3a0c8..1f0f965b1 100644 --- a/jdbc-v2/src/test/java/com/clickhouse/jdbc/StatementTest.java +++ b/jdbc-v2/src/test/java/com/clickhouse/jdbc/StatementTest.java @@ -1109,7 +1109,7 @@ public void testExecute() throws Exception { } @Test(groups = {"integration"}) - public void setConnectionSchema() throws Exception { + public void testSetConnectionSchema() throws Exception { String db1 = getDatabase() + "_schema1"; String db2 = getDatabase() + "_schema2"; try (Connection conn = getJdbcConnection(); Statement stmt = conn.createStatement()) { @@ -1129,6 +1129,70 @@ public void setConnectionSchema() throws Exception { } } + @Test(groups = {"integration"}, dataProvider = "testUnsupportedOperationsDP") + public void testUnsupportedOperations(Properties props, boolean shouldThrow) throws Exception { + try (Connection conn = getJdbcConnection(props); Statement stmt = conn.createStatement()) { + List unsupportedOperations = Arrays.asList( + () -> stmt.execute("SELECT 1", Statement.RETURN_GENERATED_KEYS), + () -> stmt.execute("SELECT 1", new int[] {1}), + () -> stmt.execute("SELECT 1", new String[] {"1"}), + () -> stmt.executeUpdate("CREATE TABLE IF NOT EXISTS test_unsupported_01 (id Int32) Engine MergeTree ORDER BY ()", Statement.RETURN_GENERATED_KEYS), + () -> stmt.executeUpdate("CREATE TABLE IF NOT EXISTS test_unsupported_02 (id Int32) Engine MergeTree ORDER BY ()", new int[] {1}), + () -> stmt.executeUpdate("CREATE TABLE IF NOT EXISTS test_unsupported_03 (id Int32) Engine MergeTree ORDER BY ()", new String[] {"1"}), + () -> stmt.executeLargeUpdate("CREATE TABLE IF NOT EXISTS test_unsupported_01 (id Int32) Engine MergeTree ORDER BY ()", Statement.RETURN_GENERATED_KEYS), + () -> stmt.executeLargeUpdate("CREATE TABLE IF NOT EXISTS test_unsupported_02 (id Int32) Engine MergeTree ORDER BY ()", new int[] {1}), + () -> stmt.executeLargeUpdate("CREATE TABLE IF NOT EXISTS test_unsupported_03 (id Int32) Engine MergeTree ORDER BY ()", new String[] {"1"}), + () -> stmt.setCursorName("CURSOR_NAME_IGNORED") + ); + + stmt.execute("SELECT 1", Statement.NO_GENERATED_KEYS); // supported + stmt.executeUpdate("CREATE TABLE IF NOT EXISTS test_unsupported_04 (id Int32) Engine MergeTree ORDER BY ()", Statement.NO_GENERATED_KEYS); // supported + stmt.executeLargeUpdate("CREATE TABLE IF NOT EXISTS test_unsupported_04 (id Int32) Engine MergeTree ORDER BY ()", Statement.NO_GENERATED_KEYS); // supported + + assertNull(stmt.getGeneratedKeys()); + + + for (Assert.ThrowingRunnable op : unsupportedOperations) { + if (shouldThrow) { + assertThrows(SQLException.class, op); + } else { + try { + op.run(); + } catch (Throwable e) { + fail(e.getMessage(), e); + } + } + } + } + } + + @DataProvider(name = "testUnsupportedOperationsDP") + public static Object[][] testUnsupportedOperationsDP() { + Properties props1 = new Properties(); + Properties props2 = new Properties(); + props2.put(DriverProperties.IGNORE_UNSUPPORTED_VALUES.getKey(), "true"); + Properties props3 = new Properties(); + props3.put(DriverProperties.IGNORE_UNSUPPORTED_VALUES.getKey(), "false"); + return new Object[][] { + {props1, true}, + {props2, false}, + {props3, true} + }; + } + + @Test(groups = {"integration"}) + public void testSetFetchSize() throws Exception { + try (Connection conn = getJdbcConnection()) { + try (Statement stmt = conn.createStatement()) { + stmt.setFetchSize(10000); + Assert.assertEquals(stmt.getFetchSize(), 10000); + stmt.setFetchSize(0); + Assert.assertEquals(stmt.getFetchSize(), 0); + Assert.assertThrows(SQLException.class, () -> stmt.setFetchSize(-1)); + } + } + } + private static String getDBName(Statement stmt) throws SQLException { try (ResultSet rs = stmt.executeQuery("SELECT database()")) { rs.next(); From 931de7a47c6b232aa34bd1c5d9637b36bf38d565 Mon Sep 17 00:00:00 2001 From: Sergey Chernov Date: Fri, 19 Sep 2025 11:20:55 -0700 Subject: [PATCH 3/5] added test for setMaxRows() --- .../test/java/com/clickhouse/jdbc/StatementTest.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) 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 1f0f965b1..61b21505f 100644 --- a/jdbc-v2/src/test/java/com/clickhouse/jdbc/StatementTest.java +++ b/jdbc-v2/src/test/java/com/clickhouse/jdbc/StatementTest.java @@ -845,11 +845,16 @@ public void testMaxRowsWithOverflowMode() throws Exception { } assertEquals(count, 1000); } + stmt.setMaxRows(0); + try (ResultSet rs = stmt.executeQuery("SELECT * FROM generate_series(0, 1000)")) { + int count = 0; + while (rs.next()) { + count++; + } + assertEquals(count, 1001); + } } - } - - } @Test(groups = {"integration"}) From 65d956a2c8369b9c9659067a2e74d9356fb0b0ee Mon Sep 17 00:00:00 2001 From: Sergey Chernov Date: Fri, 19 Sep 2025 11:48:18 -0700 Subject: [PATCH 4/5] fixed minor issues --- .../java/com/clickhouse/jdbc/DriverProperties.java | 10 +++++----- .../main/java/com/clickhouse/jdbc/ResultSetImpl.java | 7 ++++--- .../test/java/com/clickhouse/jdbc/StatementTest.java | 1 - 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/jdbc-v2/src/main/java/com/clickhouse/jdbc/DriverProperties.java b/jdbc-v2/src/main/java/com/clickhouse/jdbc/DriverProperties.java index 16614430c..46e4c0b6b 100644 --- a/jdbc-v2/src/main/java/com/clickhouse/jdbc/DriverProperties.java +++ b/jdbc-v2/src/main/java/com/clickhouse/jdbc/DriverProperties.java @@ -18,7 +18,7 @@ public enum DriverProperties { * JDBC allows throwing SQLException for unsupported values and methods. * But driver can ignore them and continue execution. Driver will do no operation in such case. */ - IGNORE_UNSUPPORTED_VALUES("jdbc_ignore_unsupported_values", "false"), + IGNORE_UNSUPPORTED_VALUES("jdbc_ignore_unsupported_values", String.valueOf(Boolean.FALSE)), /** * Schema term to be used in the connection URL. Only `schema` is supported right now. @@ -28,7 +28,7 @@ public enum DriverProperties { /** * Indicates if driver should create a secure connection over SSL/TLS */ - SECURE_CONNECTION("ssl", "false"), + SECURE_CONNECTION("ssl", String.valueOf(Boolean.FALSE)), /** * Query settings to be passed along with query operation. @@ -41,12 +41,12 @@ public enum DriverProperties { * PreparedStatement is used. Has limitation and can be used with a simple form of insert like; * {@code INSERT INTO t VALUES (?, ?, ?...)} */ - BETA_ROW_BINARY_WRITER("beta.row_binary_for_simple_insert", "false"), + BETA_ROW_BINARY_WRITER("beta.row_binary_for_simple_insert", String.valueOf(Boolean.FALSE)), /** * Enables closing result set before */ - RESULTSET_AUTO_CLOSE("jdbc_resultset_auto_close", "true"), + RESULTSET_AUTO_CLOSE("jdbc_resultset_auto_close", String.valueOf(Boolean.FALSE)), /** * Enables using server property `max_result_rows` ({@link ServerSettings#MAX_RESULT_ROWS} to limit number of rows returned by query. @@ -56,7 +56,7 @@ public enum DriverProperties { * this is fine. It is recommended to set limit in SQL query. * */ - USE_MAX_RESULT_ROWS("jdbc_use_max_result_rows", "false"), + USE_MAX_RESULT_ROWS("jdbc_use_max_result_rows", String.valueOf(Boolean.FALSE)), ; diff --git a/jdbc-v2/src/main/java/com/clickhouse/jdbc/ResultSetImpl.java b/jdbc-v2/src/main/java/com/clickhouse/jdbc/ResultSetImpl.java index 462b0645b..2f8444897 100644 --- a/jdbc-v2/src/main/java/com/clickhouse/jdbc/ResultSetImpl.java +++ b/jdbc-v2/src/main/java/com/clickhouse/jdbc/ResultSetImpl.java @@ -16,7 +16,6 @@ import java.io.Reader; import java.io.StringReader; import java.math.BigDecimal; -import java.net.SocketTimeoutException; import java.net.URL; import java.nio.charset.StandardCharsets; import java.sql.Blob; @@ -58,8 +57,9 @@ public class ResultSetImpl implements ResultSet, JdbcV2Wrapper { private int fetchSize; private int fetchDirection; - final private int maxFieldSize; - final private int maxRows; + @SuppressWarnings("unused") + private final int maxFieldSize; + private final int maxRows; private Consumer onDataTransferException; @@ -114,6 +114,7 @@ public boolean next() throws SQLException { } if (maxRows > 0 && rowPos == maxRows) { + // rowPos is at current position. if we reached here it means we stepped over maxRows rowPos = AFTER_LAST; return false; } 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 61b21505f..137ad32c1 100644 --- a/jdbc-v2/src/test/java/com/clickhouse/jdbc/StatementTest.java +++ b/jdbc-v2/src/test/java/com/clickhouse/jdbc/StatementTest.java @@ -4,7 +4,6 @@ import com.clickhouse.client.api.internal.ServerSettings; import com.clickhouse.client.api.query.GenericRecord; import com.clickhouse.data.ClickHouseVersion; -import com.clickhouse.jdbc.internal.JdbcConfiguration; import org.apache.commons.lang3.RandomStringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; From 8a369b51e27339512b36816115a06d9ef01b1264 Mon Sep 17 00:00:00 2001 From: Sergey Chernov Date: Fri, 19 Sep 2025 12:22:05 -0700 Subject: [PATCH 5/5] added test for DriverProperties.RESULTSET_AUTO_CLOSE --- .../com/clickhouse/jdbc/DriverProperties.java | 2 +- .../java/com/clickhouse/jdbc/StatementImpl.java | 2 +- .../java/com/clickhouse/jdbc/StatementTest.java | 17 +++++++++++++++-- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/jdbc-v2/src/main/java/com/clickhouse/jdbc/DriverProperties.java b/jdbc-v2/src/main/java/com/clickhouse/jdbc/DriverProperties.java index 46e4c0b6b..6bae0071b 100644 --- a/jdbc-v2/src/main/java/com/clickhouse/jdbc/DriverProperties.java +++ b/jdbc-v2/src/main/java/com/clickhouse/jdbc/DriverProperties.java @@ -46,7 +46,7 @@ public enum DriverProperties { /** * Enables closing result set before */ - RESULTSET_AUTO_CLOSE("jdbc_resultset_auto_close", String.valueOf(Boolean.FALSE)), + RESULTSET_AUTO_CLOSE("jdbc_resultset_auto_close", String.valueOf(Boolean.TRUE)), /** * Enables using server property `max_result_rows` ({@link ServerSettings#MAX_RESULT_ROWS} to limit number of rows returned by query. 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 6c9667f51..1909e88ec 100644 --- a/jdbc-v2/src/main/java/com/clickhouse/jdbc/StatementImpl.java +++ b/jdbc-v2/src/main/java/com/clickhouse/jdbc/StatementImpl.java @@ -43,7 +43,7 @@ public class StatementImpl implements Statement, JdbcV2Wrapper { protected volatile String lastQueryId; private long maxRows; private boolean closeOnCompletion; - private boolean resultSetAutoClose; + private final boolean resultSetAutoClose; private int maxFieldSize; private boolean escapeProcessingEnabled; 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 137ad32c1..073345b0b 100644 --- a/jdbc-v2/src/test/java/com/clickhouse/jdbc/StatementTest.java +++ b/jdbc-v2/src/test/java/com/clickhouse/jdbc/StatementTest.java @@ -511,9 +511,8 @@ public void testWithIPs() throws Exception { } } - @Test + @Test(groups = {"integration"}) public void testConnectionExhaustion() throws Exception { - int maxNumConnections = 3; Properties properties = new Properties(); properties.put(ClientConfigProperties.HTTP_MAX_OPEN_CONNECTIONS.getKey(), "" + maxNumConnections); @@ -526,6 +525,20 @@ public void testConnectionExhaustion() throws Exception { } } } + + properties.put(DriverProperties.RESULTSET_AUTO_CLOSE.getKey(), "false"); + try (Connection conn = getJdbcConnection(properties)) { + try (Statement stmt = conn.createStatement()) { + try { + for (int i = 0; i < maxNumConnections * 2; i++) { + stmt.executeQuery("SELECT number FROM system.numbers LIMIT 100"); + } + fail("Exception expected"); + } catch (SQLException e) { + // ignore + } + } + } } @Test(groups = {"integration"})