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
32 changes: 27 additions & 5 deletions jdbc-v2/src/main/java/com/clickhouse/jdbc/DriverProperties.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.clickhouse.jdbc;

import com.clickhouse.client.api.internal.ServerSettings;

import java.util.Collections;
import java.util.List;

Expand All @@ -11,12 +13,22 @@
*/
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", String.valueOf(Boolean.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
*/
SECURE_CONNECTION("ssl", "false"),
SECURE_CONNECTION("ssl", String.valueOf(Boolean.FALSE)),

/**
* Query settings to be passed along with query operation.
Expand All @@ -29,12 +41,22 @@ 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.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", String.valueOf(Boolean.FALSE)),
;


Expand Down
25 changes: 21 additions & 4 deletions jdbc-v2/src/main/java/com/clickhouse/jdbc/ResultSetImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -57,6 +56,10 @@ public class ResultSetImpl implements ResultSet, JdbcV2Wrapper {
private int rowPos;

private int fetchSize;
private int fetchDirection;
@SuppressWarnings("unused")
private final int maxFieldSize;
private final int maxRows;

private Consumer<Exception> onDataTransferException;

Expand All @@ -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;
}

Expand All @@ -103,6 +109,16 @@ private String columnIndexToName(int index) throws SQLException {
public boolean next() throws SQLException {
checkClosed();

if (rowPos == AFTER_LAST) {
return false;
}

if (maxRows > 0 && rowPos == maxRows) {
Copy link

Copilot AI Sep 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The condition rowPos == maxRows will prevent fetching the last allowed row. If maxRows is 5, this will stop at row 4 instead of row 5. The condition should be rowPos >= maxRows to correctly limit to the specified number of rows.

Suggested change
if (maxRows > 0 && rowPos == maxRows) {
if (maxRows > 0 && rowPos >= maxRows) {

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rows start with 1. 0 is before we do any next() call.
Lets say maxRows = 2
rowPos = 0
next() - ( rowPos < maxRows)
rowPos = 1 - we got first row
next() (rowPos < maxRows)
rowPos = 2 - we got second row
at this point should return isLast true

so it will not prevent reading row, but will make state not accurate

// rowPos is at current position. if we reached here it means we stepped over maxRows
rowPos = AFTER_LAST;
return false;
}

try {
Object readerRow = reader.next();
if (readerRow != null) {
Expand Down Expand Up @@ -543,7 +559,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
Expand Down Expand Up @@ -607,15 +623,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
Expand Down
54 changes: 35 additions & 19 deletions jdbc-v2/src/main/java/com/clickhouse/jdbc/StatementImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -41,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;

Expand All @@ -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 {
Expand Down Expand Up @@ -323,6 +326,7 @@ public void clearWarnings() throws SQLException {

@Override
public void setCursorName(String name) throws SQLException {
featureManager.unsupportedFeatureThrow("setCursorName(String)", true);
ensureOpen();
}

Expand Down Expand Up @@ -458,22 +462,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();
Expand All @@ -489,31 +493,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);
}

Expand Down Expand Up @@ -573,20 +583,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);
}
}
}

Expand All @@ -611,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);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand All @@ -318,4 +322,8 @@ private Map<ClickHouseDataType, Class<?>> defaultTypeHintMapping() {
mapping.put(ClickHouseDataType.Array, List.class);
return mapping;
}

public boolean useMaxResultRows() {
return isFlagSet(DriverProperties.USE_MAX_RESULT_ROWS);
}
}
Loading
Loading