This is an automated email from the ASF dual-hosted git repository.
jooger pushed a commit to branch jdbc_over_thin_sql
in repository https://gitbox.apache.org/repos/asf/ignite-3.git
The following commit(s) were added to refs/heads/jdbc_over_thin_sql by this
push:
new 757b44c08ef IGNITE-26427 Jdbc. Statement for thin client backed
connection (#6659)
757b44c08ef is described below
commit 757b44c08ef755066b63518cd6d8d9cd5458642c
Author: Max Zhuravkov <[email protected]>
AuthorDate: Mon Oct 6 14:50:23 2025 +0300
IGNITE-26427 Jdbc. Statement for thin client backed connection (#6659)
---
.../ignite/internal/jdbc2/JdbcConnection2.java | 65 +-
.../ignite/internal/jdbc2/JdbcResultSet.java | 50 +-
.../ignite/internal/jdbc2/JdbcStatement2.java | 687 +++++++++++++++++++++
.../internal/jdbc2/JdbcResultSet2SelfTest.java | 77 ++-
.../internal/jdbc2/JdbcStatement2SelfTest.java | 457 ++++++++++++++
5 files changed, 1313 insertions(+), 23 deletions(-)
diff --git
a/modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc2/JdbcConnection2.java
b/modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc2/JdbcConnection2.java
index 9fa8ff80b78..1033f4fb0a7 100644
---
a/modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc2/JdbcConnection2.java
+++
b/modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc2/JdbcConnection2.java
@@ -39,10 +39,13 @@ import java.sql.Savepoint;
import java.sql.ShardingKey;
import java.sql.Statement;
import java.sql.Struct;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Properties;
import java.util.concurrent.Executor;
+import java.util.concurrent.locks.ReentrantLock;
import org.apache.ignite.internal.jdbc.ConnectionProperties;
import org.apache.ignite.internal.jdbc.JdbcDatabaseMetadata;
import org.apache.ignite.internal.jdbc.proto.JdbcQueryEventHandler;
@@ -58,7 +61,7 @@ public class JdbcConnection2 implements Connection {
private static final String CONNECTION_IS_CLOSED = "Connection is closed.";
- private static final String RETURNING_AUTO_GENERATED_KEYS_IS_NOT_SUPPORTED
=
+ static final String RETURNING_AUTO_GENERATED_KEYS_IS_NOT_SUPPORTED =
"Returning auto-generated keys is not supported.";
private static final String CALLABLE_FUNCTIONS_ARE_NOT_SUPPORTED =
@@ -97,12 +100,20 @@ public class JdbcConnection2 implements Connection {
private static final String TYPES_MAPPING_IS_NOT_SUPPORTED =
"Types mapping is not supported.";
- private final JdbcDatabaseMetadata metadata;
+ private final IgniteSql igniteSql;
- private String schemaName;
+ private final ReentrantLock lock = new ReentrantLock();
+
+ private final List<Statement> statements = new ArrayList<>();
+
+ private final ConnectionProperties properties;
+
+ private final JdbcDatabaseMetadata metadata;
private volatile boolean closed;
+ private String schemaName;
+
private int txIsolation;
private boolean autoCommit;
@@ -123,10 +134,12 @@ public class JdbcConnection2 implements Connection {
JdbcQueryEventHandler eventHandler,
ConnectionProperties props
) {
+ igniteSql = client;
autoCommit = true;
networkTimeoutMillis = props.getConnectionTimeout();
txIsolation = TRANSACTION_SERIALIZABLE;
schemaName = readSchemaName(props.getSchema());
+ properties = props;
//noinspection ThisEscapedInObjectConstruction
metadata = new JdbcDatabaseMetadata(this, eventHandler,
props.getUrl(), props.getUsername());
@@ -152,7 +165,16 @@ public class JdbcConnection2 implements Connection {
checkCursorOptions(resSetType, resSetConcurrency, resSetHoldability);
- throw new UnsupportedOperationException();
+ JdbcStatement2 statement = new JdbcStatement2(this, igniteSql,
schemaName, resSetHoldability);
+
+ lock.lock();
+ try {
+ statements.add(statement);
+ } finally {
+ lock.unlock();
+ }
+
+ return statement;
}
/** {@inheritDoc} */
@@ -284,6 +306,37 @@ public class JdbcConnection2 implements Connection {
}
closed = true;
+
+ List<Exception> suppressedExceptions = null;
+
+ lock.lock();
+ try {
+ if (closed) {
+ return;
+ }
+
+ for (Statement statement : statements) {
+ try {
+ statement.close();
+ } catch (Exception e) {
+ if (suppressedExceptions == null) {
+ suppressedExceptions = new ArrayList<>();
+ }
+ suppressedExceptions.add(e);
+ }
+ }
+
+ } finally {
+ lock.unlock();
+ }
+
+ if (suppressedExceptions != null) {
+ SQLException err = new SQLException("Exception occurred while
closing a connection.");
+ for (Exception suppressed : suppressedExceptions) {
+ err.addSuppressed(suppressed);
+ }
+ throw err;
+ }
}
/**
@@ -686,6 +739,10 @@ public class JdbcConnection2 implements Connection {
return iface != null && iface.isAssignableFrom(JdbcConnection2.class);
}
+ ConnectionProperties properties() {
+ return properties;
+ }
+
private static void checkCursorOptions(
int resSetType,
int resSetConcurrency,
diff --git
a/modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc2/JdbcResultSet.java
b/modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc2/JdbcResultSet.java
index 85517457fa5..2e82c529869 100644
---
a/modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc2/JdbcResultSet.java
+++
b/modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc2/JdbcResultSet.java
@@ -91,6 +91,12 @@ public class JdbcResultSet implements ResultSet {
private final Statement statement;
+ private final JdbcResultSetMetadata jdbcMeta;
+
+ private final int maxRows;
+
+ private boolean closeOnCompletion;
+
private int fetchSize;
private @Nullable SqlRow currentRow;
@@ -101,15 +107,15 @@ public class JdbcResultSet implements ResultSet {
private boolean wasNull;
- private JdbcResultSetMetadata jdbcMeta;
-
/**
* Constructor.
*/
public JdbcResultSet(
org.apache.ignite.sql.ResultSet<SqlRow> rs,
Statement statement,
- Supplier<ZoneId> zoneIdSupplier
+ Supplier<ZoneId> zoneIdSupplier,
+ boolean closeOnCompletion,
+ int maxRows
) {
this.rs = rs;
@@ -122,6 +128,30 @@ public class JdbcResultSet implements ResultSet {
this.closed = false;
this.wasNull = false;
this.jdbcMeta = new JdbcResultSetMetadata(rsMetadata);
+ this.closeOnCompletion = closeOnCompletion;
+ this.maxRows = maxRows;
+ }
+
+ int updateCount() {
+ assert !isQuery() : "Should not be called on a query";
+ if (rs.wasApplied() || rs.affectedRows() == -1) {
+ return 0;
+ } else {
+ //noinspection NumericCastThatLosesPrecision
+ return (int) rs.affectedRows();
+ }
+ }
+
+ boolean isQuery() {
+ return rs.hasRowSet();
+ }
+
+ void closeStatement(boolean close) {
+ closeOnCompletion = close;
+ }
+
+ private boolean hasNext() {
+ return rs.hasNext() && (maxRows == 0 || currentPosition < maxRows);
}
@Override
@@ -129,7 +159,7 @@ public class JdbcResultSet implements ResultSet {
ensureNotClosed();
try {
- if (!rs.hasNext()) {
+ if (!hasNext()) {
currentRow = null;
return false;
}
@@ -154,6 +184,11 @@ public class JdbcResultSet implements ResultSet {
} catch (Exception e) {
Throwable cause =
IgniteExceptionMapperUtil.mapToPublicException(e);
throw new SQLException(cause.getMessage(), cause);
+ } finally {
+ if (closeOnCompletion) {
+ JdbcStatement2 statement2 =
statement.unwrap(JdbcStatement2.class);
+ statement2.closeIfAllResultsClosed();
+ }
}
}
@@ -913,7 +948,7 @@ public class JdbcResultSet implements ResultSet {
public boolean isBeforeFirst() throws SQLException {
ensureNotClosed();
- return currentRow == null && rs.hasNext();
+ return currentRow == null && hasNext();
}
/** {@inheritDoc} */
@@ -921,7 +956,7 @@ public class JdbcResultSet implements ResultSet {
public boolean isAfterLast() throws SQLException {
ensureNotClosed();
- boolean hasNext = rs.hasNext();
+ boolean hasNext = hasNext();
// Result set is empty
if (currentPosition == 0 && !hasNext) {
return false;
@@ -943,7 +978,7 @@ public class JdbcResultSet implements ResultSet {
public boolean isLast() throws SQLException {
ensureNotClosed();
- return currentRow != null && !rs.hasNext();
+ return currentRow != null && !hasNext();
}
/** {@inheritDoc} */
@@ -2277,6 +2312,7 @@ public class JdbcResultSet implements ResultSet {
// Append nano seconds according to the specified precision.
long nanos = value.getLong(ChronoField.NANO_OF_SECOND);
+ //noinspection NumericCastThatLosesPrecision
long scaled = nanos / (long) Math.pow(10, 9 - precision);
sb.append('.');
diff --git
a/modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc2/JdbcStatement2.java
b/modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc2/JdbcStatement2.java
new file mode 100644
index 00000000000..53fefc6bd4b
--- /dev/null
+++
b/modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc2/JdbcStatement2.java
@@ -0,0 +1,687 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.jdbc2;
+
+import static java.sql.ResultSet.CONCUR_READ_ONLY;
+import static java.sql.ResultSet.FETCH_FORWARD;
+import static java.sql.ResultSet.TYPE_FORWARD_ONLY;
+
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.SQLFeatureNotSupportedException;
+import java.sql.SQLWarning;
+import java.sql.Statement;
+import java.time.ZoneId;
+import java.util.EnumSet;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+import org.apache.ignite.internal.client.sql.ClientSql;
+import org.apache.ignite.internal.client.sql.QueryModifier;
+import org.apache.ignite.internal.jdbc.proto.SqlStateCode;
+import org.apache.ignite.internal.lang.IgniteExceptionMapperUtil;
+import org.apache.ignite.internal.sql.SyncResultSetAdapter;
+import org.apache.ignite.internal.util.ArrayUtils;
+import org.apache.ignite.sql.IgniteSql;
+import org.apache.ignite.sql.SqlRow;
+import org.apache.ignite.sql.Statement.StatementBuilder;
+import org.apache.ignite.sql.async.AsyncResultSet;
+import org.apache.ignite.table.mapper.Mapper;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * {@link Statement} implementation backed by the thin client.
+ */
+public class JdbcStatement2 implements Statement {
+
+ private static final EnumSet<QueryModifier> QUERY =
EnumSet.of(QueryModifier.ALLOW_ROW_SET_RESULT);
+
+ private static final EnumSet<QueryModifier> DML_OR_DDL = EnumSet.of(
+ QueryModifier.ALLOW_AFFECTED_ROWS_RESULT,
QueryModifier.ALLOW_APPLIED_RESULT);
+
+ private static final String RETURNING_AUTO_GENERATED_KEYS_IS_NOT_SUPPORTED
=
+ JdbcConnection2.RETURNING_AUTO_GENERATED_KEYS_IS_NOT_SUPPORTED;
+
+ private static final String LARGE_UPDATE_NOT_SUPPORTED =
+ "executeLargeUpdate not implemented";
+
+ private static final String FIELD_SIZE_LIMIT_IS_NOT_SUPPORTED =
+ "Field size limit is not supported.";
+
+ private static final String CURSOR_NAME_IS_NOT_SUPPORTED =
+ "Setting cursor name is not supported.";
+
+ private static final String MULTIPLE_OPEN_RESULTS_ARE_NOT_SUPPORTED =
+ "Multiple open results are not supported.";
+
+ private static final String POOLING_IS_NOT_SUPPORTED =
+ "Pooling is not supported.";
+
+ private static final String STATEMENT_IS_CLOSED =
+ "Statement is closed.";
+ public static final String ONLY_FORWARD_DIRECTION_IS_SUPPORTED = "Only
forward direction is supported.";
+
+ private final IgniteSql igniteSql;
+
+ private final Connection connection;
+
+ private final String schemaName;
+
+ private final int rsHoldability;
+
+ private volatile @Nullable JdbcResultSet resultSet;
+
+ private int queryTimeoutSeconds;
+
+ private int pageSize;
+
+ private int maxRows = 0;
+
+ private volatile boolean closed;
+
+ private boolean closeOnCompletion;
+
+ JdbcStatement2(
+ Connection connection,
+ IgniteSql igniteSql,
+ String schemaName,
+ int rsHoldability
+ ) {
+ this.connection = connection;
+ this.schemaName = schemaName;
+ this.igniteSql = igniteSql;
+ this.rsHoldability = rsHoldability;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public ResultSet executeQuery(String sql) throws SQLException {
+ execute0(QUERY, Objects.requireNonNull(sql),
ArrayUtils.OBJECT_EMPTY_ARRAY);
+
+ ResultSet rs = getResultSet();
+
+ if (rs == null) {
+ throw new SQLException("The query isn't SELECT query: " + sql,
SqlStateCode.PARSING_EXCEPTION);
+ }
+
+ return rs;
+ }
+
+ JdbcResultSet createResultSet(org.apache.ignite.sql.ResultSet<SqlRow>
resultSet) throws SQLException {
+ JdbcConnection2 connection2 = connection.unwrap(JdbcConnection2.class);
+ ZoneId zoneId = connection2.properties().getConnectionTimeZone();
+ return new JdbcResultSet(resultSet, this, () -> zoneId,
closeOnCompletion, maxRows);
+ }
+
+ /**
+ * Execute the query with given parameters.
+ *
+ * @param sql Sql query.
+ * @param args Query parameters.
+ * @throws SQLException Onj error.
+ */
+ void execute0(Set<QueryModifier> queryModifiers, String sql, Object[]
args) throws SQLException {
+ ensureNotClosed();
+
+ closeResults();
+
+ if (sql == null || sql.isEmpty()) {
+ throw new SQLException("SQL query is empty.");
+ }
+
+ // TODO https://issues.apache.org/jira/browse/IGNITE-26139 Implement
autocommit = false
+ if (!connection.getAutoCommit()) {
+ throw new UnsupportedOperationException("Explicit transactions are
not supported yet.");
+ }
+
+ if (sql.indexOf(';') == -1 || sql.indexOf(';') == sql.length() - 1) {
+ queryModifiers.remove(QueryModifier.ALLOW_MULTISTATEMENT);
+ }
+
+ // TODO https://issues.apache.org/jira/browse/IGNITE-26142
multistatement.
+ if (queryModifiers.contains(QueryModifier.ALLOW_MULTISTATEMENT)) {
+ throw new UnsupportedOperationException("Multi-statements are not
supported yet.");
+ }
+
+ StatementBuilder stmtBuilder = igniteSql.statementBuilder()
+ .query(sql)
+ .defaultSchema(schemaName);
+
+ if (queryTimeoutSeconds > 0) {
+ stmtBuilder.queryTimeout(queryTimeoutSeconds, TimeUnit.SECONDS);
+ }
+
+ if (getFetchSize() > 0) {
+ stmtBuilder.pageSize(getFetchSize());
+ }
+
+ JdbcConnection2 conn = connection.unwrap(JdbcConnection2.class);
+ ZoneId zoneId = conn.properties().getConnectionTimeZone();
+
+ org.apache.ignite.sql.Statement igniteStmt = stmtBuilder
+ .timeZoneId(zoneId)
+ .build();
+
+ ClientSql clientSql = (ClientSql) igniteSql;
+
+ AsyncResultSet<SqlRow> clientRs;
+ try {
+ clientRs = clientSql.executeAsyncInternal(null,
+ (Mapper<SqlRow>) null,
+ null,
+ queryModifiers,
+ igniteStmt,
+ args
+ ).join();
+
+ SyncResultSetAdapter<SqlRow> syncRs = new
SyncResultSetAdapter<>(clientRs);
+
+ resultSet = createResultSet(syncRs);
+ } catch (Exception e) {
+ Throwable cause =
IgniteExceptionMapperUtil.mapToPublicException(e);
+ throw new SQLException(cause.getMessage(), cause);
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int executeUpdate(String sql) throws SQLException {
+ Objects.requireNonNull(sql, "sql");
+
+ execute0(DML_OR_DDL, sql, ArrayUtils.OBJECT_EMPTY_ARRAY);
+
+ int rowCount = getUpdateCount();
+
+ if (isQuery()) {
+ closeResults();
+ throw new SQLException("The query is not DML statement: " + sql);
+ }
+
+ return Math.max(rowCount, 0);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int executeUpdate(String sql, int autoGeneratedKeys) throws
SQLException {
+ ensureNotClosed();
+
+ switch (autoGeneratedKeys) {
+ case RETURN_GENERATED_KEYS:
+ throw new
SQLFeatureNotSupportedException((RETURNING_AUTO_GENERATED_KEYS_IS_NOT_SUPPORTED));
+
+ case NO_GENERATED_KEYS:
+ return executeUpdate(sql);
+
+ default:
+ throw new SQLException("Invalid autoGeneratedKeys value");
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int executeUpdate(String sql, int[] colIndexes) throws SQLException
{
+ ensureNotClosed();
+
+ throw new
SQLFeatureNotSupportedException(RETURNING_AUTO_GENERATED_KEYS_IS_NOT_SUPPORTED);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int executeUpdate(String sql, String[] colNames) throws
SQLException {
+ ensureNotClosed();
+
+ throw new
SQLFeatureNotSupportedException(RETURNING_AUTO_GENERATED_KEYS_IS_NOT_SUPPORTED);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void close() throws SQLException {
+ if (isClosed()) {
+ return;
+ }
+
+ closed = true;
+
+ closeResults();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int getMaxFieldSize() throws SQLException {
+ ensureNotClosed();
+
+ return 0;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setMaxFieldSize(int max) throws SQLException {
+ ensureNotClosed();
+
+ if (max < 0) {
+ throw new SQLException("Invalid field limit.");
+ }
+
+ throw new
SQLFeatureNotSupportedException(FIELD_SIZE_LIMIT_IS_NOT_SUPPORTED);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int getMaxRows() throws SQLException {
+ ensureNotClosed();
+
+ return maxRows;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setMaxRows(int maxRows) throws SQLException {
+ ensureNotClosed();
+
+ if (maxRows < 0) {
+ throw new SQLException("Invalid max rows value.");
+ }
+
+ this.maxRows = maxRows;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setEscapeProcessing(boolean enable) throws SQLException {
+ ensureNotClosed();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int getQueryTimeout() throws SQLException {
+ ensureNotClosed();
+
+ return queryTimeoutSeconds;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setQueryTimeout(int timeout) throws SQLException {
+ ensureNotClosed();
+
+ if (timeout < 0) {
+ throw new SQLException("Invalid timeout value.");
+ }
+
+ this.queryTimeoutSeconds = timeout;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void cancel() throws SQLException {
+ ensureNotClosed();
+
+ throw new UnsupportedOperationException();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ @Nullable
+ public SQLWarning getWarnings() throws SQLException {
+ ensureNotClosed();
+
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void clearWarnings() throws SQLException {
+ ensureNotClosed();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setCursorName(String name) throws SQLException {
+ ensureNotClosed();
+
+ throw new
SQLFeatureNotSupportedException(CURSOR_NAME_IS_NOT_SUPPORTED);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean execute(String sql) throws SQLException {
+ ensureNotClosed();
+
+ execute0(EnumSet.allOf(QueryModifier.class),
Objects.requireNonNull(sql), ArrayUtils.OBJECT_EMPTY_ARRAY);
+
+ return isQuery();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean execute(String sql, int[] colIndexes) throws SQLException {
+ ensureNotClosed();
+
+ if (colIndexes != null && colIndexes.length > 0) {
+ throw new
SQLFeatureNotSupportedException(RETURNING_AUTO_GENERATED_KEYS_IS_NOT_SUPPORTED);
+ }
+
+ return execute(sql);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean execute(String sql, int autoGeneratedKeys) throws
SQLException {
+ ensureNotClosed();
+
+ switch (autoGeneratedKeys) {
+ case RETURN_GENERATED_KEYS:
+ throw new
SQLFeatureNotSupportedException(RETURNING_AUTO_GENERATED_KEYS_IS_NOT_SUPPORTED);
+
+ case NO_GENERATED_KEYS:
+ return execute(sql);
+
+ default:
+ throw new SQLException("Invalid autoGeneratedKeys value.");
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean execute(String sql, String[] colNames) throws SQLException {
+ ensureNotClosed();
+
+ if (colNames != null && colNames.length > 0) {
+ throw new
SQLFeatureNotSupportedException(RETURNING_AUTO_GENERATED_KEYS_IS_NOT_SUPPORTED);
+ }
+
+ return execute(sql);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public @Nullable ResultSet getResultSet() throws SQLException {
+ ensureNotClosed();
+
+ return isQuery() ? resultSet : null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int getUpdateCount() throws SQLException {
+ ensureNotClosed();
+
+ JdbcResultSet rs = resultSet;
+ if (rs == null || rs.isQuery()) {
+ return -1;
+ } else {
+ return rs.updateCount();
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean getMoreResults() throws SQLException {
+ return getMoreResults(CLOSE_CURRENT_RESULT);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean getMoreResults(int current) throws SQLException {
+ ensureNotClosed();
+
+ switch (current) {
+ case CLOSE_CURRENT_RESULT:
+ return false;
+
+ case CLOSE_ALL_RESULTS:
+ case KEEP_CURRENT_RESULT:
+ throw new
SQLFeatureNotSupportedException(MULTIPLE_OPEN_RESULTS_ARE_NOT_SUPPORTED);
+
+ default:
+ throw new SQLException("Invalid 'current' parameter.");
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public long executeLargeUpdate(String sql) throws SQLException {
+ ensureNotClosed();
+
+ throw new SQLFeatureNotSupportedException(LARGE_UPDATE_NOT_SUPPORTED);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public long executeLargeUpdate(String sql, int[] columnIndexes) throws
SQLException {
+ ensureNotClosed();
+
+ throw new SQLFeatureNotSupportedException(LARGE_UPDATE_NOT_SUPPORTED);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public long executeLargeUpdate(String sql, String[] columnNames) throws
SQLException {
+ ensureNotClosed();
+
+ throw new SQLFeatureNotSupportedException(LARGE_UPDATE_NOT_SUPPORTED);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setFetchDirection(int direction) throws SQLException {
+ ensureNotClosed();
+
+ if (direction != FETCH_FORWARD) {
+ throw new
SQLFeatureNotSupportedException(ONLY_FORWARD_DIRECTION_IS_SUPPORTED);
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int getFetchDirection() throws SQLException {
+ ensureNotClosed();
+
+ return FETCH_FORWARD;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setFetchSize(int fetchSize) throws SQLException {
+ ensureNotClosed();
+
+ if (fetchSize <= 0) {
+ throw new SQLException("Invalid fetch size.");
+ }
+
+ pageSize = fetchSize;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int getFetchSize() throws SQLException {
+ ensureNotClosed();
+
+ return pageSize;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int getResultSetConcurrency() throws SQLException {
+ ensureNotClosed();
+
+ return CONCUR_READ_ONLY;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int getResultSetType() throws SQLException {
+ ensureNotClosed();
+
+ return TYPE_FORWARD_ONLY;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void addBatch(String sql) throws SQLException {
+ ensureNotClosed();
+
+ Objects.requireNonNull(sql);
+
+ // TODO https://issues.apache.org/jira/browse/IGNITE-26143 batch
operations
+ throw new UnsupportedOperationException("Batch operation");
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void clearBatch() throws SQLException {
+ ensureNotClosed();
+
+ // TODO https://issues.apache.org/jira/browse/IGNITE-26143 batch
operations
+ throw new UnsupportedOperationException("Batch operation");
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int[] executeBatch() throws SQLException {
+ ensureNotClosed();
+
+ closeResults();
+
+ // TODO https://issues.apache.org/jira/browse/IGNITE-26143 batch
operations
+ throw new UnsupportedOperationException("Batch operation");
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Connection getConnection() throws SQLException {
+ ensureNotClosed();
+
+ return connection;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public ResultSet getGeneratedKeys() throws SQLException {
+ ensureNotClosed();
+
+ throw new
SQLFeatureNotSupportedException((RETURNING_AUTO_GENERATED_KEYS_IS_NOT_SUPPORTED));
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int getResultSetHoldability() throws SQLException {
+ ensureNotClosed();
+
+ return rsHoldability;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean isClosed() throws SQLException {
+ return closed;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setPoolable(boolean poolable) throws SQLException {
+ ensureNotClosed();
+
+ if (poolable) {
+ throw new
SQLFeatureNotSupportedException(POOLING_IS_NOT_SUPPORTED);
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean isPoolable() throws SQLException {
+ ensureNotClosed();
+
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void closeOnCompletion() throws SQLException {
+ ensureNotClosed();
+
+ closeOnCompletion = true;
+
+ JdbcResultSet rs = resultSet;
+ if (rs != null) {
+ rs.closeStatement(true);
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean isCloseOnCompletion() throws SQLException {
+ ensureNotClosed();
+
+ return closeOnCompletion;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public <T> T unwrap(Class<T> iface) throws SQLException {
+ if (!isWrapperFor(Objects.requireNonNull(iface))) {
+ throw new SQLException("Statement is not a wrapper for " +
iface.getName());
+ }
+
+ return (T) this;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean isWrapperFor(Class<?> iface) throws SQLException {
+ return iface != null && iface.isAssignableFrom(JdbcStatement2.class);
+ }
+
+ /**
+ * Gets the isQuery flag from the first result.
+ *
+ * @return isQuery flag.
+ */
+ protected boolean isQuery() {
+ if (resultSet == null) {
+ return false;
+ }
+ return resultSet.isQuery();
+ }
+
+ private void ensureNotClosed() throws SQLException {
+ if (isClosed()) {
+ throw new SQLException(STATEMENT_IS_CLOSED);
+ }
+ }
+
+ private void closeResults() throws SQLException {
+ JdbcResultSet rs = resultSet;
+ if (rs != null) {
+ rs.close();
+ }
+ resultSet = null;
+ }
+
+ /**
+ * Used by statement on closeOnCompletion mode.
+ */
+ void closeIfAllResultsClosed() throws SQLException {
+ if (!closeOnCompletion) {
+ return;
+ }
+
+ close();
+ }
+}
diff --git
a/modules/jdbc/src/test/java/org/apache/ignite/internal/jdbc2/JdbcResultSet2SelfTest.java
b/modules/jdbc/src/test/java/org/apache/ignite/internal/jdbc2/JdbcResultSet2SelfTest.java
index 1e9035f60e4..01bfc0cda3e 100644
---
a/modules/jdbc/src/test/java/org/apache/ignite/internal/jdbc2/JdbcResultSet2SelfTest.java
+++
b/modules/jdbc/src/test/java/org/apache/ignite/internal/jdbc2/JdbcResultSet2SelfTest.java
@@ -66,6 +66,7 @@ import org.jetbrains.annotations.Nullable;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;
+import org.junit.jupiter.params.provider.ValueSource;
import org.mockito.Mockito;
import org.mockito.internal.stubbing.answers.ThrowsException;
@@ -144,7 +145,7 @@ public class JdbcResultSet2SelfTest extends
JdbcResultSetBaseSelfTest {
RuntimeException cause = new RuntimeException("Some error");
when(igniteRs.hasNext()).thenThrow(cause);
- ResultSet rs = new JdbcResultSet(igniteRs, statement,
ZoneId::systemDefault);
+ ResultSet rs = new JdbcResultSet(igniteRs, statement,
ZoneId::systemDefault, false, 0);
SQLException err = assertThrows(SQLException.class, rs::next);
assertEquals("Some error", err.getMessage());
@@ -162,7 +163,7 @@ public class JdbcResultSet2SelfTest extends
JdbcResultSetBaseSelfTest {
when(igniteRs.hasNext()).thenReturn(true);
when(igniteRs.next()).thenThrow(cause);
- ResultSet rs = new JdbcResultSet(igniteRs, statement,
ZoneId::systemDefault);
+ ResultSet rs = new JdbcResultSet(igniteRs, statement,
ZoneId::systemDefault, false, 0);
SQLException err = assertThrows(SQLException.class, rs::next);
assertEquals("Some error", err.getMessage());
@@ -179,27 +180,33 @@ public class JdbcResultSet2SelfTest extends
JdbcResultSetBaseSelfTest {
org.apache.ignite.sql.ResultSet<SqlRow> igniteRs =
Mockito.mock(org.apache.ignite.sql.ResultSet.class);
when(igniteRs.metadata()).thenReturn(new
ResultSetMetadataImpl(List.of()));
- ResultSet rs = new JdbcResultSet(igniteRs, statement,
ZoneId::systemDefault);
+ JdbcStatement2 statement2 = Mockito.mock(JdbcStatement2.class);
+ when(statement.unwrap(JdbcStatement2.class)).thenReturn(statement2);
+
+ ResultSet rs = new JdbcResultSet(igniteRs, statement,
ZoneId::systemDefault, true, 0);
rs.close();
rs.close();
verify(igniteRs, times(1)).close();
verify(igniteRs, times(1)).metadata();
- verifyNoMoreInteractions(igniteRs);
+ verify(statement2, times(1)).closeIfAllResultsClosed();
+ verifyNoMoreInteractions(igniteRs, statement2);
}
- @Test
+ @ParameterizedTest
+ @ValueSource(booleans = {true, false})
@SuppressWarnings("unchecked")
- public void closeExceptionIsWrapped() {
- Statement statement = Mockito.mock(Statement.class);
+ public void closeExceptionIsWrapped(boolean closeOnCompletion) throws
SQLException {
+ JdbcStatement2 statement = Mockito.mock(JdbcStatement2.class);
+ when(statement.unwrap(JdbcStatement2.class)).thenReturn(statement);
org.apache.ignite.sql.ResultSet<SqlRow> igniteRs =
Mockito.mock(org.apache.ignite.sql.ResultSet.class);
RuntimeException cause = new RuntimeException("Some error");
doAnswer(new ThrowsException(cause)).when(igniteRs).close();
- ResultSet rs = new JdbcResultSet(igniteRs, statement,
ZoneId::systemDefault);
+ ResultSet rs = new JdbcResultSet(igniteRs, statement,
ZoneId::systemDefault, closeOnCompletion, 0);
SQLException err = assertThrows(SQLException.class, rs::close);
assertEquals("Some error", err.getMessage());
@@ -223,7 +230,7 @@ public class JdbcResultSet2SelfTest extends
JdbcResultSetBaseSelfTest {
RuntimeException cause = new RuntimeException("Corrupted value");
when(row.value(0)).thenThrow(cause);
- JdbcResultSet rs = new JdbcResultSet(igniteRs, statement,
ZoneId::systemDefault);
+ JdbcResultSet rs = new JdbcResultSet(igniteRs, statement,
ZoneId::systemDefault, false, 0);
assertTrue(rs.next());
SQLException err = assertThrows(SQLException.class, () ->
rs.getValue(1));
@@ -232,6 +239,41 @@ public class JdbcResultSet2SelfTest extends
JdbcResultSetBaseSelfTest {
assertSame(cause, err.getCause().getCause());
}
+ @Test
+ public void maxRows() throws SQLException {
+ ColumnMetadataImpl column = new ColumnMetadataImpl("C1",
ColumnType.INT32, 0, 0, false, null);
+ ResultSetMetadata apiMeta = new ResultSetMetadataImpl(List.of(column));
+ Statement statement = Mockito.mock(Statement.class);
+ Supplier<ZoneId> zoneIdSupplier = ZoneId::systemDefault;
+
+ List<List<Object>> rows = List.of(
+ List.of(1),
+ List.of(2),
+ List.of(3),
+ List.of(4)
+ );
+
+ try (ResultSet rs = new JdbcResultSet(new ResultSetStub(apiMeta,
rows), statement, zoneIdSupplier, false, 3)) {
+ assertTrue(rs.next());
+ assertTrue(rs.next());
+ assertTrue(rs.next());
+ // MaxRows exceeded
+ assertFalse(rs.next());
+ assertFalse(rs.next());
+ }
+
+ // no limit
+
+ try (ResultSet rs = new JdbcResultSet(new ResultSetStub(apiMeta,
rows), statement, zoneIdSupplier, false, 0)) {
+ assertTrue(rs.next());
+ assertTrue(rs.next());
+ assertTrue(rs.next());
+ assertTrue(rs.next());
+ // No more rows
+ assertFalse(rs.next());
+ }
+ }
+
@Override
protected ResultSet createResultSet(@Nullable ZoneId zoneId,
List<ColumnDefinition> cols, List<List<Object>> rows) {
Statement statement = Mockito.mock(Statement.class);
@@ -239,7 +281,6 @@ public class JdbcResultSet2SelfTest extends
JdbcResultSetBaseSelfTest {
return createResultSet(statement, zoneId, cols, rows);
}
- @SuppressWarnings("unchecked")
private static ResultSet createResultSet(
Statement statement,
@SuppressWarnings("unused")
@@ -247,6 +288,18 @@ public class JdbcResultSet2SelfTest extends
JdbcResultSetBaseSelfTest {
List<ColumnDefinition> cols,
List<List<Object>> rows
) {
+ return createResultSet(statement, zoneId, cols, rows, 0);
+ }
+
+ @SuppressWarnings("unchecked")
+ private static ResultSet createResultSet(
+ Statement statement,
+ @SuppressWarnings("unused")
+ @Nullable ZoneId zoneId,
+ List<ColumnDefinition> cols,
+ List<List<Object>> rows,
+ int maxRows
+ ) {
Supplier<ZoneId> zoneIdSupplier = () -> {
if (zoneId != null) {
@@ -261,7 +314,7 @@ public class JdbcResultSet2SelfTest extends
JdbcResultSetBaseSelfTest {
org.apache.ignite.sql.ResultSet<SqlRow> rs =
Mockito.mock(org.apache.ignite.sql.ResultSet.class);
when(rs.metadata()).thenReturn(null);
- return new JdbcResultSet(rs, statement, zoneIdSupplier);
+ return new JdbcResultSet(rs, statement, zoneIdSupplier, false, 0);
}
List<ColumnMetadata> apiCols = new ArrayList<>();
@@ -276,7 +329,7 @@ public class JdbcResultSet2SelfTest extends
JdbcResultSetBaseSelfTest {
ResultSetMetadata apiMeta = new ResultSetMetadataImpl(apiCols);
- return new JdbcResultSet(new ResultSetStub(apiMeta, rows), statement,
zoneIdSupplier);
+ return new JdbcResultSet(new ResultSetStub(apiMeta, rows), statement,
zoneIdSupplier, false, maxRows);
}
private static class ResultSetStub implements
org.apache.ignite.sql.ResultSet<SqlRow> {
diff --git
a/modules/jdbc/src/test/java/org/apache/ignite/internal/jdbc2/JdbcStatement2SelfTest.java
b/modules/jdbc/src/test/java/org/apache/ignite/internal/jdbc2/JdbcStatement2SelfTest.java
new file mode 100644
index 00000000000..086dd9843ac
--- /dev/null
+++
b/modules/jdbc/src/test/java/org/apache/ignite/internal/jdbc2/JdbcStatement2SelfTest.java
@@ -0,0 +1,457 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.jdbc2;
+
+import static
org.apache.ignite.jdbc.util.JdbcTestUtils.assertThrowsSqlException;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.when;
+
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.SQLFeatureNotSupportedException;
+import java.sql.Statement;
+import java.util.List;
+import org.apache.ignite.internal.jdbc.ConnectionProperties;
+import org.apache.ignite.internal.jdbc.ConnectionPropertiesImpl;
+import org.apache.ignite.internal.sql.ResultSetMetadataImpl;
+import org.apache.ignite.internal.testframework.BaseIgniteAbstractTest;
+import org.apache.ignite.sql.IgniteSql;
+import org.apache.ignite.sql.SqlRow;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.function.Executable;
+import org.mockito.Mockito;
+
+/**
+ * Unit tests for {@link JdbcStatement2}.
+ */
+public class JdbcStatement2SelfTest extends BaseIgniteAbstractTest {
+
+ @Test
+ public void close() throws SQLException {
+ try (Statement stmt = createStatement()) {
+ assertFalse(stmt.isClosed());
+
+ stmt.close();
+ assertTrue(stmt.isClosed());
+
+ expectClosed(() -> stmt.executeQuery("SELECT 1"));
+ expectClosed(() -> stmt.executeUpdate("UPDATE t SET val=1"));
+
+ expectClosed(() -> stmt.setMaxFieldSize(100_000));
+ expectClosed(stmt::getMaxFieldSize);
+
+ expectClosed(() -> stmt.setEscapeProcessing(true));
+ expectClosed(() -> stmt.setEscapeProcessing(false));
+
+ expectClosed(() -> stmt.setQueryTimeout(-1));
+ expectClosed(() -> stmt.setQueryTimeout(1));
+ expectClosed(stmt::getQueryTimeout);
+
+ expectClosed(stmt::cancel);
+
+ expectClosed(stmt::getWarnings);
+ expectClosed(stmt::clearWarnings);
+
+ expectClosed(() -> stmt.setCursorName("C"));
+ expectClosed(() -> stmt.setCursorName(null));
+
+ expectClosed(() -> stmt.execute("SELECT 1"));
+
+ expectClosed(stmt::getResultSet);
+
+ expectClosed(stmt::getUpdateCount);
+
+ expectClosed(stmt::getMoreResults);
+
+ expectClosed(() ->
stmt.setFetchDirection(ResultSet.FETCH_FORWARD));
+ expectClosed(() ->
stmt.setFetchDirection(ResultSet.FETCH_REVERSE));
+ expectClosed(() ->
stmt.setFetchDirection(ResultSet.FETCH_UNKNOWN));
+ expectClosed(stmt::getFetchDirection);
+
+ expectClosed(() -> stmt.setFetchSize(-1));
+ expectClosed(() -> stmt.setFetchSize(0));
+ expectClosed(() -> stmt.setFetchSize(1));
+ expectClosed(stmt::getFetchSize);
+
+ expectClosed(stmt::getResultSetConcurrency);
+ expectClosed(stmt::getResultSetType);
+
+ expectClosed(() -> stmt.addBatch("UPDATE t SET val=2"));
+
+ expectClosed(stmt::clearBatch);
+
+ expectClosed(stmt::executeBatch);
+
+ expectClosed(stmt::getConnection);
+
+ expectClosed(stmt::getMoreResults);
+
+ expectClosed(() ->
stmt.getMoreResults(Statement.CLOSE_CURRENT_RESULT));
+ expectClosed(() ->
stmt.getMoreResults(Statement.KEEP_CURRENT_RESULT));
+ expectClosed(() ->
stmt.getMoreResults(Statement.CLOSE_ALL_RESULTS));
+
+ expectClosed(stmt::getGeneratedKeys);
+
+ expectClosed(() -> stmt.executeUpdate("UPDATE t SET val=2",
Statement.RETURN_GENERATED_KEYS));
+ expectClosed(() -> stmt.executeUpdate("UPDATE t SET val=2",
Statement.NO_GENERATED_KEYS));
+
+ expectClosed(() -> stmt.executeUpdate("UPDATE t SET val=2", new
int[]{0}));
+ expectClosed(() -> stmt.executeUpdate("UPDATE t SET val=2", new
String[]{"C1"}));
+
+ expectClosed(stmt::getResultSetHoldability);
+
+ expectClosed(() -> stmt.setPoolable(true));
+ expectClosed(() -> stmt.setPoolable(false));
+ expectClosed(stmt::isPoolable);
+
+ expectClosed(stmt::closeOnCompletion);
+ expectClosed(stmt::isCloseOnCompletion);
+ }
+ }
+
+ @Test
+ public void setMaxFieldSize() throws SQLException {
+ try (Statement stmt = createStatement()) {
+ assertThrowsSqlException(
+ SQLFeatureNotSupportedException.class,
+ "Field size limit is not supported.",
+ () -> stmt.setMaxFieldSize(1)
+ );
+
+ assertEquals(0, stmt.getMaxFieldSize());
+
+ assertThrowsSqlException(
+ SQLException.class,
+ "Invalid field limit.",
+ () -> stmt.setMaxFieldSize(-1)
+ );
+ }
+ }
+
+ @Test
+ public void queryTimeout() throws SQLException {
+ try (Statement stmt = createStatement()) {
+ stmt.setQueryTimeout(2);
+ assertEquals(2, stmt.getQueryTimeout());
+
+ stmt.setQueryTimeout(0);
+ assertEquals(0, stmt.getQueryTimeout());
+
+ stmt.setQueryTimeout(4);
+ assertEquals(4, stmt.getQueryTimeout());
+
+ assertThrowsSqlException(
+ SQLException.class,
+ "Invalid timeout value.",
+ () -> stmt.setQueryTimeout(-1)
+ );
+
+ // No changes
+ assertEquals(4, stmt.getQueryTimeout());
+ }
+ }
+
+ @Test
+ public void setMaxRows() throws SQLException {
+ try (Statement stmt = createStatement()) {
+ // Unlimited
+ assertEquals(0, stmt.getMaxRows());
+
+ stmt.setMaxRows(1000);
+ assertEquals(1000, stmt.getMaxRows());
+
+ // Change to unlimited
+ stmt.setMaxRows(0);
+ assertEquals(0, stmt.getMaxRows());
+
+ assertThrowsSqlException(SQLException.class,
+ "Invalid max rows value.",
+ () -> stmt.setMaxRows(-1)
+ );
+ }
+ }
+
+ @Test
+ public void setEscapeProcessing() throws SQLException {
+ try (Statement stmt = createStatement()) {
+ // Does nothing
+ stmt.setEscapeProcessing(true);
+ stmt.setEscapeProcessing(false);
+ }
+ }
+
+ @Test
+ public void warnings() throws SQLException {
+ try (Statement stmt = createStatement()) {
+ // Does nothing
+ assertNull(stmt.getWarnings());
+ stmt.clearWarnings();
+ }
+ }
+
+ @Test
+ public void setCursorName() throws SQLException {
+ try (Statement stmt = createStatement()) {
+ String error = "Setting cursor name is not supported.";
+
+ assertThrowsSqlException(SQLException.class,
+ error,
+ () -> stmt.setCursorName("C")
+ );
+
+ assertThrowsSqlException(SQLException.class,
+ error,
+ () -> stmt.setCursorName(null)
+ );
+ }
+ }
+
+ @Test
+ public void setFetchDirection() throws SQLException {
+ try (Statement stmt = createStatement()) {
+ // Initial state
+ int value = ResultSet.FETCH_FORWARD;
+ assertEquals(value, stmt.getFetchDirection());
+
+ stmt.setFetchDirection(value);
+ assertEquals(value, stmt.getFetchDirection());
+
+ // Does not change anything
+ assertThrowsSqlException(
+ SQLFeatureNotSupportedException.class,
+ "Only forward direction is supported.",
+ () -> stmt.setFetchDirection(ResultSet.FETCH_REVERSE)
+ );
+ assertEquals(value, stmt.getFetchDirection());
+
+ // Does not change anything
+ assertThrowsSqlException(
+ SQLFeatureNotSupportedException.class,
+ "Only forward direction is supported.",
+ () -> stmt.setFetchDirection(ResultSet.FETCH_UNKNOWN)
+ );
+ assertEquals(value, stmt.getFetchDirection());
+ }
+ }
+
+ @Test
+ public void setFetchSize() throws SQLException {
+ try (Statement stmt = createStatement()) {
+ // Fetch size is a hint
+ assertEquals(0, stmt.getFetchSize());
+
+ stmt.setFetchSize(1000);
+ assertEquals(1000, stmt.getFetchSize());
+
+ assertThrowsSqlException(SQLException.class,
+ "Invalid fetch size.",
+ () -> stmt.setFetchSize(-1)
+ );
+ }
+ }
+
+ @Test
+ public void getResultSetConcurrency() throws SQLException {
+ try (Statement stmt = createStatement()) {
+ assertEquals(ResultSet.CONCUR_READ_ONLY,
stmt.getResultSetConcurrency());
+ }
+ }
+
+ @Test
+ public void getResultSetType() throws SQLException {
+ try (Statement stmt = createStatement()) {
+ assertEquals(ResultSet.TYPE_FORWARD_ONLY, stmt.getResultSetType());
+ }
+ }
+
+ @Test
+ public void getConnection() throws SQLException {
+ Connection connection = Mockito.mock(Connection.class);
+
+ try (Statement stmt = createStatement(connection)) {
+ assertSame(connection, stmt.getConnection());
+ }
+ }
+
+ @Test
+ public void getMoreResults() throws SQLException {
+ try (Statement stmt = createStatement()) {
+ // Nothing
+ assertFalse(stmt.getMoreResults(Statement.CLOSE_CURRENT_RESULT));
+
+ String error = "Multiple open results are not supported.";
+
+ assertThrowsSqlException(SQLException.class,
+ error,
+ () -> stmt.getMoreResults(Statement.CLOSE_ALL_RESULTS));
+
+ assertThrowsSqlException(SQLException.class,
+ error,
+ () -> stmt.getMoreResults(Statement.KEEP_CURRENT_RESULT));
+ }
+ }
+
+ @Test
+ public void getGeneratedKeys() throws SQLException {
+ try (Statement stmt = createStatement()) {
+ assertThrowsSqlException(
+ SQLFeatureNotSupportedException.class,
+ "Returning auto-generated keys is not supported.",
+ stmt::getGeneratedKeys
+ );
+ }
+ }
+
+ @Test
+ public void updateWithColumns() throws SQLException {
+ try (Statement stmt = createStatement()) {
+ String sql = "UPDATE t SET c = 1";
+ String error = "Returning auto-generated keys is not supported.";
+
+ assertThrowsSqlException(
+ SQLFeatureNotSupportedException.class,
+ error,
+ () -> stmt.executeUpdate(sql,
Statement.RETURN_GENERATED_KEYS)
+ );
+
+ assertThrowsSqlException(
+ SQLFeatureNotSupportedException.class,
+ error,
+ () -> stmt.executeUpdate(sql, new int[]{1})
+ );
+
+ assertThrowsSqlException(
+ SQLFeatureNotSupportedException.class,
+ error,
+ () -> stmt.executeUpdate(sql, new String[]{"id"})
+ );
+ }
+ }
+
+ @Test
+ public void getResultSetHoldability() throws SQLException {
+ try (Statement stmt = createStatement()) {
+ // any value
+ assertEquals(ResultSet.HOLD_CURSORS_OVER_COMMIT,
stmt.getResultSetHoldability());
+ }
+ }
+
+ @Test
+ public void setPoolable() throws SQLException {
+ try (Statement stmt = createStatement()) {
+ assertFalse(stmt.isPoolable());
+
+ String error = "Pooling is not supported.";
+
+ assertThrowsSqlException(SQLException.class, error, () -> {
+ stmt.setPoolable(true);
+ });
+
+ // Nothing happens
+ stmt.setPoolable(false);
+ assertFalse(stmt.isPoolable());
+ }
+ }
+
+ @Test
+ public void closeOnCompletion() throws SQLException {
+ try (Statement stmt = createStatement()) {
+ JdbcStatement2 jdbc = stmt.unwrap(JdbcStatement2.class);
+
+ @SuppressWarnings("unchecked")
+ org.apache.ignite.sql.ResultSet<SqlRow> igniteRs =
Mockito.mock(org.apache.ignite.sql.ResultSet.class);
+ when(igniteRs.metadata()).thenReturn(new
ResultSetMetadataImpl(List.of()));
+
+ {
+ ResultSet rs = jdbc.createResultSet(igniteRs);
+ rs.close();
+ assertFalse(stmt.isClosed());
+ }
+
+ stmt.closeOnCompletion();
+
+ {
+ ResultSet rs = jdbc.createResultSet(igniteRs);
+ rs.close();
+ assertTrue(stmt.isClosed());
+ }
+ }
+ }
+
+ @Test
+ public void largeMaxRows() throws SQLException {
+ try (Statement stmt = createStatement()) {
+ assertThrows(UnsupportedOperationException.class, () ->
stmt.setLargeMaxRows(1));
+
+ assertEquals(0, stmt.getLargeMaxRows());
+ }
+ }
+
+ @Test
+ public void largeUpdateMethods() throws SQLException {
+ try (Statement stmt = createStatement()) {
+ String error = "executeLargeUpdate not implemented";
+
+ assertThrowsSqlException(SQLFeatureNotSupportedException.class,
+ () -> stmt.executeLargeUpdate("UPDATE t SET val=2"));
+
+ assertThrowsSqlException(SQLFeatureNotSupportedException.class,
+ error,
+ () -> stmt.executeLargeUpdate("UPDATE t SET val=2",
Statement.RETURN_GENERATED_KEYS));
+
+ assertThrowsSqlException(SQLFeatureNotSupportedException.class,
+ error,
+ () -> stmt.executeLargeUpdate("UPDATE t SET val=2",
Statement.NO_GENERATED_KEYS));
+
+ assertThrowsSqlException(SQLFeatureNotSupportedException.class,
+ error,
+ () -> stmt.executeLargeUpdate("UPDATE t SET val=2", new
int[]{0}));
+
+ assertThrowsSqlException(SQLFeatureNotSupportedException.class,
+ error,
+ () -> stmt.executeLargeUpdate("UPDATE t SET val=2", new
String[]{"C1"}));
+ }
+ }
+
+ private static Statement createStatement() throws SQLException {
+ Connection connection = Mockito.mock(Connection.class);
+ JdbcConnection2 connection2 = Mockito.mock(JdbcConnection2.class);
+
+ ConnectionProperties properties = new ConnectionPropertiesImpl();
+
+ when(connection.unwrap(JdbcConnection2.class)).thenReturn(connection2);
+ when(connection2.properties()).thenReturn(properties);
+
+ return createStatement(connection);
+ }
+
+ private static Statement createStatement(Connection connection) {
+ IgniteSql igniteSql = Mockito.mock(IgniteSql.class);
+ return new JdbcStatement2(connection, igniteSql, "PUBLIC",
ResultSet.HOLD_CURSORS_OVER_COMMIT);
+ }
+
+ private static void expectClosed(Executable method) {
+ assertThrowsSqlException(SQLException.class, "Statement is closed.",
method);
+ }
+}