This is an automated email from the ASF dual-hosted git repository.

korlov 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 761597a97c9 IGNITE-26506: Jdbc. Jdbc connection over thin client API 
(#6654)
761597a97c9 is described below

commit 761597a97c97fc50e66d070594ef8c0ffe650522
Author: Max Zhuravkov <[email protected]>
AuthorDate: Thu Oct 2 09:48:14 2025 +0300

    IGNITE-26506: Jdbc. Jdbc connection over thin client API (#6654)
---
 .../ignite/internal/jdbc2/JdbcConnection2.java     | 715 +++++++++++++++++++++
 .../internal/jdbc2/JdbcConnection2SelfTest.java    | 475 ++++++++++++++
 2 files changed, 1190 insertions(+)

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
new file mode 100644
index 00000000000..9fa8ff80b78
--- /dev/null
+++ 
b/modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc2/JdbcConnection2.java
@@ -0,0 +1,715 @@
+/*
+ * 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.CLOSE_CURSORS_AT_COMMIT;
+import static java.sql.ResultSet.CONCUR_READ_ONLY;
+import static java.sql.ResultSet.TYPE_FORWARD_ONLY;
+import static 
org.apache.ignite.internal.jdbc.proto.SqlStateCode.CONNECTION_CLOSED;
+
+import java.sql.Array;
+import java.sql.Blob;
+import java.sql.CallableStatement;
+import java.sql.Clob;
+import java.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.sql.NClob;
+import java.sql.PreparedStatement;
+import java.sql.SQLClientInfoException;
+import java.sql.SQLException;
+import java.sql.SQLFeatureNotSupportedException;
+import java.sql.SQLWarning;
+import java.sql.SQLXML;
+import java.sql.Savepoint;
+import java.sql.ShardingKey;
+import java.sql.Statement;
+import java.sql.Struct;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Properties;
+import java.util.concurrent.Executor;
+import org.apache.ignite.internal.jdbc.ConnectionProperties;
+import org.apache.ignite.internal.jdbc.JdbcDatabaseMetadata;
+import org.apache.ignite.internal.jdbc.proto.JdbcQueryEventHandler;
+import org.apache.ignite.internal.jdbc.proto.SqlStateCode;
+import org.apache.ignite.internal.sql.SqlCommon;
+import org.apache.ignite.sql.IgniteSql;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * {@link Connection} implementation backed by the thin client.
+ */
+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 
=
+            "Returning auto-generated keys is not supported.";
+
+    private static final String CALLABLE_FUNCTIONS_ARE_NOT_SUPPORTED =
+            "Callable functions are not supported.";
+
+    private static final String SQL_SPECIFIC_TYPES_ARE_NOT_SUPPORTED =
+            "SQL-specific types are not supported.";
+
+    private static final String INVALID_RESULT_SET_HOLDABILITY =
+            "Invalid result set holdability (only close cursors at commit 
option is supported).";
+
+    private static final String INVALID_RESULT_SET_TYPE =
+            "Invalid result set type (only forward is supported).";
+
+    private static final String INVALID_RESULT_SET_CONCURRENCY =
+            "Invalid concurrency (updates are not supported).";
+
+    private static final String 
TRANSACTION_CANNOT_BE_COMMITED_IN_AUTOCOMMIT_MODE =
+            "Transaction cannot be committed explicitly in auto-commit mode.";
+
+    private static final String CANNOT_SET_TRANSACTION_NONE =
+            "Cannot set transaction isolation level to TRANSACTION_NONE.";
+
+    private static final String INVALID_TRANSACTION_ISOLATION_LEVEL =
+            "Invalid transaction isolation level.";
+
+    private static final String SHARDING_KEYS_ARE_NOT_SUPPORTED =
+            "Sharding keys are not supported.";
+
+    private static final String SAVEPOINT_IN_AUTO_COMMIT_MODE =
+            "Savepoint cannot be set in auto-commit mode.";
+
+    private static final String SAVEPOINTS_ARE_NOT_SUPPORTED =
+            "Savepoints are not supported.";
+
+    private static final String TYPES_MAPPING_IS_NOT_SUPPORTED =
+            "Types mapping is not supported.";
+
+    private final JdbcDatabaseMetadata metadata;
+
+    private String schemaName;
+
+    private volatile boolean closed;
+
+    private int txIsolation;
+
+    private boolean autoCommit;
+
+    private boolean readOnly;
+
+    private int networkTimeoutMillis;
+
+    /**
+     * Creates new connection.
+     *
+     * @param client SQL client.
+     * @param eventHandler Event handler.
+     * @param props Connection properties.
+     */
+    public JdbcConnection2(
+            IgniteSql client,
+            JdbcQueryEventHandler eventHandler,
+            ConnectionProperties props
+    ) {
+        autoCommit = true;
+        networkTimeoutMillis = props.getConnectionTimeout();
+        txIsolation = TRANSACTION_SERIALIZABLE;
+        schemaName = readSchemaName(props.getSchema());
+
+        //noinspection ThisEscapedInObjectConstruction
+        metadata = new JdbcDatabaseMetadata(this, eventHandler, 
props.getUrl(), props.getUsername());
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Statement createStatement() throws SQLException {
+        return createStatement(TYPE_FORWARD_ONLY, CONCUR_READ_ONLY, 
CLOSE_CURSORS_AT_COMMIT);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Statement createStatement(int resSetType, int resSetConcurrency) 
throws SQLException {
+        return createStatement(resSetType, resSetConcurrency, 
CLOSE_CURSORS_AT_COMMIT);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Statement createStatement(int resSetType, int resSetConcurrency,
+            int resSetHoldability) throws SQLException {
+        ensureNotClosed();
+
+        checkCursorOptions(resSetType, resSetConcurrency, resSetHoldability);
+
+        throw new UnsupportedOperationException();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public PreparedStatement prepareStatement(String sql) throws SQLException {
+        return prepareStatement(sql, TYPE_FORWARD_ONLY, CONCUR_READ_ONLY, 
CLOSE_CURSORS_AT_COMMIT);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public PreparedStatement prepareStatement(String sql, int 
autoGeneratedKeys) throws SQLException {
+        ensureNotClosed();
+
+        if (autoGeneratedKeys == Statement.RETURN_GENERATED_KEYS) {
+            throw new 
SQLFeatureNotSupportedException(RETURNING_AUTO_GENERATED_KEYS_IS_NOT_SUPPORTED);
+        }
+
+        throw new UnsupportedOperationException();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public PreparedStatement prepareStatement(String sql, int resSetType,
+            int resSetConcurrency) throws SQLException {
+        return prepareStatement(sql, resSetType, resSetConcurrency, 
CLOSE_CURSORS_AT_COMMIT);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public PreparedStatement prepareStatement(String sql, int resSetType, int 
resSetConcurrency,
+            int resSetHoldability) throws SQLException {
+        ensureNotClosed();
+
+        checkCursorOptions(resSetType, resSetConcurrency, resSetHoldability);
+
+        if (sql == null) {
+            throw new SQLException("SQL string cannot be null.");
+        }
+
+        throw new UnsupportedOperationException();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public PreparedStatement prepareStatement(String sql, int[] colIndexes) 
throws SQLException {
+        ensureNotClosed();
+
+        throw new 
SQLFeatureNotSupportedException(RETURNING_AUTO_GENERATED_KEYS_IS_NOT_SUPPORTED);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public PreparedStatement prepareStatement(String sql, String[] colNames) 
throws SQLException {
+        ensureNotClosed();
+
+        throw new 
SQLFeatureNotSupportedException(RETURNING_AUTO_GENERATED_KEYS_IS_NOT_SUPPORTED);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String nativeSQL(String sql) throws SQLException {
+        ensureNotClosed();
+
+        Objects.requireNonNull(sql);
+
+        return sql;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setAutoCommit(boolean autoCommit) throws SQLException {
+        ensureNotClosed();
+
+        // TODO https://issues.apache.org/jira/browse/IGNITE-26139 Implement 
autocommit = false
+        if (autoCommit != this.autoCommit) {
+            this.autoCommit = autoCommit;
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean getAutoCommit() throws SQLException {
+        ensureNotClosed();
+
+        return autoCommit;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void commit() throws SQLException {
+        ensureNotClosed();
+
+        if (autoCommit) {
+            throw new 
SQLException(TRANSACTION_CANNOT_BE_COMMITED_IN_AUTOCOMMIT_MODE);
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void rollback() throws SQLException {
+        ensureNotClosed();
+
+        if (autoCommit) {
+            throw new 
SQLException(TRANSACTION_CANNOT_BE_COMMITED_IN_AUTOCOMMIT_MODE);
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void rollback(Savepoint savepoint) throws SQLException {
+        ensureNotClosed();
+
+        if (savepoint == null) {
+            throw new SQLException("Invalid savepoint.");
+        }
+
+        if (autoCommit) {
+            throw new 
SQLException(TRANSACTION_CANNOT_BE_COMMITED_IN_AUTOCOMMIT_MODE);
+        }
+
+        throw new 
SQLFeatureNotSupportedException(SAVEPOINTS_ARE_NOT_SUPPORTED);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void close() throws SQLException {
+        if (isClosed()) {
+            return;
+        }
+
+        closed = true;
+    }
+
+    /**
+     * Ensures that connection is not closed.
+     *
+     * @throws SQLException If connection is closed.
+     */
+    private void ensureNotClosed() throws SQLException {
+        if (closed) {
+            throw new SQLException(CONNECTION_IS_CLOSED, CONNECTION_CLOSED);
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean isClosed() throws SQLException {
+        return closed;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public DatabaseMetaData getMetaData() throws SQLException {
+        ensureNotClosed();
+
+        return metadata;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setReadOnly(boolean readOnly) throws SQLException {
+        ensureNotClosed();
+
+        this.readOnly = readOnly;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean isReadOnly() throws SQLException {
+        ensureNotClosed();
+
+        return readOnly;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setCatalog(String catalog) throws SQLException {
+        ensureNotClosed();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String getCatalog() throws SQLException {
+        ensureNotClosed();
+
+        return JdbcDatabaseMetadata.CATALOG_NAME;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setTransactionIsolation(int level) throws SQLException {
+        ensureNotClosed();
+
+        switch (level) {
+            case TRANSACTION_READ_UNCOMMITTED:
+            case TRANSACTION_READ_COMMITTED:
+            case TRANSACTION_REPEATABLE_READ:
+            case TRANSACTION_SERIALIZABLE:
+                break;
+            case TRANSACTION_NONE:
+                throw new SQLException(CANNOT_SET_TRANSACTION_NONE);
+
+            default:
+                throw new SQLException(INVALID_TRANSACTION_ISOLATION_LEVEL, 
SqlStateCode.INVALID_TRANSACTION_LEVEL);
+        }
+
+        txIsolation = level;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int getTransactionIsolation() throws SQLException {
+        ensureNotClosed();
+
+        return txIsolation;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    @Nullable
+    public SQLWarning getWarnings() throws SQLException {
+        ensureNotClosed();
+
+        return null;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void clearWarnings() throws SQLException {
+        ensureNotClosed();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Map<String, Class<?>> getTypeMap() throws SQLException {
+        ensureNotClosed();
+
+        throw new 
SQLFeatureNotSupportedException(TYPES_MAPPING_IS_NOT_SUPPORTED);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setTypeMap(Map<String, Class<?>> map) throws SQLException {
+        ensureNotClosed();
+
+        throw new 
SQLFeatureNotSupportedException(TYPES_MAPPING_IS_NOT_SUPPORTED);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setHoldability(int holdability) throws SQLException {
+        ensureNotClosed();
+
+        if (holdability != CLOSE_CURSORS_AT_COMMIT) {
+            throw new SQLException(INVALID_RESULT_SET_HOLDABILITY);
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int getHoldability() throws SQLException {
+        ensureNotClosed();
+
+        return CLOSE_CURSORS_AT_COMMIT;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Savepoint setSavepoint() throws SQLException {
+        ensureNotClosed();
+
+        if (autoCommit) {
+            throw new SQLException(SAVEPOINT_IN_AUTO_COMMIT_MODE);
+        }
+
+        throw new 
SQLFeatureNotSupportedException(SAVEPOINTS_ARE_NOT_SUPPORTED);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Savepoint setSavepoint(String name) throws SQLException {
+        ensureNotClosed();
+
+        if (name == null) {
+            throw new SQLException("Savepoint name cannot be null.");
+        }
+
+        if (autoCommit) {
+            throw new SQLException(SAVEPOINT_IN_AUTO_COMMIT_MODE);
+        }
+
+        throw new 
SQLFeatureNotSupportedException(SAVEPOINTS_ARE_NOT_SUPPORTED);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void releaseSavepoint(Savepoint savepoint) throws SQLException {
+        ensureNotClosed();
+
+        if (savepoint == null) {
+            throw new SQLException("Savepoint cannot be null.");
+        }
+
+        throw new 
SQLFeatureNotSupportedException(SAVEPOINTS_ARE_NOT_SUPPORTED);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public CallableStatement prepareCall(String sql) throws SQLException {
+        ensureNotClosed();
+
+        throw new 
SQLFeatureNotSupportedException(CALLABLE_FUNCTIONS_ARE_NOT_SUPPORTED);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public CallableStatement prepareCall(String sql, int resSetType, int 
resSetConcurrency)
+            throws SQLException {
+        ensureNotClosed();
+
+        throw new 
SQLFeatureNotSupportedException(CALLABLE_FUNCTIONS_ARE_NOT_SUPPORTED);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public CallableStatement prepareCall(String sql, int resSetType, int 
resSetConcurrency,
+            int resSetHoldability) throws SQLException {
+        ensureNotClosed();
+
+        throw new 
SQLFeatureNotSupportedException(CALLABLE_FUNCTIONS_ARE_NOT_SUPPORTED);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Clob createClob() throws SQLException {
+        ensureNotClosed();
+
+        throw new 
SQLFeatureNotSupportedException(SQL_SPECIFIC_TYPES_ARE_NOT_SUPPORTED);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Blob createBlob() throws SQLException {
+        ensureNotClosed();
+
+        throw new 
SQLFeatureNotSupportedException(SQL_SPECIFIC_TYPES_ARE_NOT_SUPPORTED);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public NClob createNClob() throws SQLException {
+        ensureNotClosed();
+
+        throw new 
SQLFeatureNotSupportedException(SQL_SPECIFIC_TYPES_ARE_NOT_SUPPORTED);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public SQLXML createSQLXML() throws SQLException {
+        ensureNotClosed();
+
+        throw new 
SQLFeatureNotSupportedException(SQL_SPECIFIC_TYPES_ARE_NOT_SUPPORTED);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean isValid(int timeout) throws SQLException {
+        if (timeout < 0) {
+            throw new SQLException("Invalid timeout: " + timeout);
+        }
+
+        return !closed;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setClientInfo(String name, String val) throws 
SQLClientInfoException {
+        if (closed) {
+            throw new SQLClientInfoException(CONNECTION_IS_CLOSED, null);
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setClientInfo(Properties props) throws SQLClientInfoException {
+        if (closed) {
+            throw new SQLClientInfoException(CONNECTION_IS_CLOSED, null);
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public @Nullable String getClientInfo(String name) throws SQLException {
+        ensureNotClosed();
+
+        return null;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Properties getClientInfo() throws SQLException {
+        ensureNotClosed();
+
+        return new Properties();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Array createArrayOf(String typeName, Object[] elements) throws 
SQLException {
+        ensureNotClosed();
+
+        throw new 
SQLFeatureNotSupportedException(SQL_SPECIFIC_TYPES_ARE_NOT_SUPPORTED);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Struct createStruct(String typeName, Object[] attrs) throws 
SQLException {
+        ensureNotClosed();
+
+        throw new 
SQLFeatureNotSupportedException(SQL_SPECIFIC_TYPES_ARE_NOT_SUPPORTED);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setSchema(String schema) throws SQLException {
+        ensureNotClosed();
+
+        this.schemaName = readSchemaName(schema);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String getSchema() throws SQLException {
+        ensureNotClosed();
+
+        return schemaName;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void abort(Executor executor) throws SQLException {
+        if (executor == null) {
+            throw new SQLException("Executor cannot be null.");
+        }
+
+        close();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public final void setNetworkTimeout(Executor executor, int ms) throws 
SQLException {
+        ensureNotClosed();
+
+        if (ms < 0) {
+            throw new SQLException("Network timeout cannot be negative.");
+        }
+
+        networkTimeoutMillis = ms;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int getNetworkTimeout() throws SQLException {
+        ensureNotClosed();
+
+        return networkTimeoutMillis;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void beginRequest() throws SQLException {
+        ensureNotClosed();
+
+        // No-op
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void endRequest() throws SQLException {
+        ensureNotClosed();
+
+        // No-op
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean setShardingKeyIfValid(ShardingKey shardingKey, ShardingKey 
superShardingKey,
+            int timeout) throws SQLException {
+        ensureNotClosed();
+
+        throw new 
SQLFeatureNotSupportedException(SHARDING_KEYS_ARE_NOT_SUPPORTED);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean setShardingKeyIfValid(ShardingKey shardingKey, int timeout) 
throws SQLException {
+        ensureNotClosed();
+
+        throw new 
SQLFeatureNotSupportedException(SHARDING_KEYS_ARE_NOT_SUPPORTED);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setShardingKey(ShardingKey shardingKey, ShardingKey 
superShardingKey) throws SQLException {
+        ensureNotClosed();
+
+        throw new 
SQLFeatureNotSupportedException(SHARDING_KEYS_ARE_NOT_SUPPORTED);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setShardingKey(ShardingKey shardingKey) throws SQLException {
+        ensureNotClosed();
+
+        throw new 
SQLFeatureNotSupportedException(SHARDING_KEYS_ARE_NOT_SUPPORTED);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public <T> T unwrap(Class<T> iface) throws SQLException {
+        if (!isWrapperFor(iface)) {
+            throw new SQLException("Connection is not a wrapper for " + 
iface.getName());
+        }
+
+        return (T) this;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean isWrapperFor(Class<?> iface) throws SQLException {
+        return iface != null && iface.isAssignableFrom(JdbcConnection2.class);
+    }
+
+    private static void checkCursorOptions(
+            int resSetType,
+            int resSetConcurrency,
+            int resHoldability
+    ) throws SQLFeatureNotSupportedException {
+
+        if (resSetType != TYPE_FORWARD_ONLY) {
+            throw new SQLFeatureNotSupportedException(INVALID_RESULT_SET_TYPE);
+        }
+
+        if (resSetConcurrency != CONCUR_READ_ONLY) {
+            throw new 
SQLFeatureNotSupportedException(INVALID_RESULT_SET_CONCURRENCY);
+        }
+
+        if (resHoldability != CLOSE_CURSORS_AT_COMMIT) {
+            throw new SQLFeatureNotSupportedException(
+                    INVALID_RESULT_SET_HOLDABILITY);
+        }
+    }
+
+    private static String readSchemaName(String schemaName) {
+        if (schemaName == null || schemaName.isEmpty()) {
+            return SqlCommon.DEFAULT_SCHEMA_NAME;
+        }
+        return schemaName;
+    }
+}
diff --git 
a/modules/jdbc/src/test/java/org/apache/ignite/internal/jdbc2/JdbcConnection2SelfTest.java
 
b/modules/jdbc/src/test/java/org/apache/ignite/internal/jdbc2/JdbcConnection2SelfTest.java
new file mode 100644
index 00000000000..5796cb03a9f
--- /dev/null
+++ 
b/modules/jdbc/src/test/java/org/apache/ignite/internal/jdbc2/JdbcConnection2SelfTest.java
@@ -0,0 +1,475 @@
+/*
+ * 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.assertNotNull;
+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 java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.SQLFeatureNotSupportedException;
+import java.sql.Savepoint;
+import java.sql.ShardingKey;
+import java.sql.Statement;
+import java.util.Map;
+import java.util.Properties;
+import java.util.function.Consumer;
+import org.apache.ignite.internal.jdbc.ConnectionProperties;
+import org.apache.ignite.internal.jdbc.ConnectionPropertiesImpl;
+import org.apache.ignite.internal.jdbc.proto.JdbcQueryEventHandler;
+import org.apache.ignite.internal.testframework.BaseIgniteAbstractTest;
+import org.apache.ignite.sql.IgniteSql;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.function.Executable;
+import org.mockito.Mockito;
+
+/**
+ * Tests for {@link JdbcConnection2}.
+ */
+public class JdbcConnection2SelfTest extends BaseIgniteAbstractTest {
+
+    @Test
+    public void nativeSql() throws SQLException {
+        try (Connection conn = createConnection()) {
+            String sql = "SELECT 1";
+            String nativeSql = conn.nativeSQL(sql);
+            assertSame(sql, nativeSql);
+        }
+    }
+
+    @Test
+    public void readOnly() throws SQLException {
+        try (Connection conn = createConnection()) {
+            assertFalse(conn.isReadOnly());
+
+            conn.setReadOnly(true);
+            assertTrue(conn.isReadOnly());
+
+            conn.setReadOnly(false);
+            assertFalse(conn.isReadOnly());
+        }
+    }
+
+    @Test
+    public void close() throws SQLException {
+        try (Connection conn = createConnection()) {
+            assertFalse(conn.isClosed());
+
+            conn.close();
+
+            assertTrue(conn.isClosed());
+
+            expectClosed(conn::createStatement);
+
+            expectClosed(() -> conn.prepareStatement("SELECT ?"));
+
+            expectClosed(() -> conn.prepareCall("SELECT F()"));
+
+            expectClosed(() -> conn.nativeSQL("SELECT 1"));
+
+            expectClosed(() -> conn.setAutoCommit(false));
+            expectClosed(conn::getAutoCommit);
+            expectClosed(() -> conn.setAutoCommit(true));
+
+            expectClosed(conn::commit);
+
+            expectClosed(conn::rollback);
+
+            expectClosed(conn::getMetaData);
+
+            expectClosed(() -> conn.setReadOnly(true));
+            expectClosed(conn::isReadOnly);
+
+            expectClosed(() -> conn.setCatalog("C"));
+            expectClosed(() -> conn.setCatalog(null));
+            expectClosed(conn::getCatalog);
+
+            expectClosed(() -> 
conn.setTransactionIsolation(Connection.TRANSACTION_NONE));
+            expectClosed(conn::getTransactionIsolation);
+
+            expectClosed(conn::getWarnings);
+            expectClosed(conn::clearWarnings);
+
+            expectClosed(() -> 
conn.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY));
+
+            expectClosed(() -> conn.prepareStatement("SELECT ?", 
ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY));
+
+            expectClosed(() -> conn.prepareCall("SELECT F()", 
ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY));
+
+            expectClosed(conn::getTypeMap);
+
+            expectClosed(() -> conn.setTypeMap(Map.of()));
+
+            expectClosed(() -> 
conn.setHoldability(ResultSet.HOLD_CURSORS_OVER_COMMIT));
+            expectClosed(conn::getHoldability);
+
+            expectClosed(conn::setSavepoint);
+            expectClosed(() -> conn.setSavepoint("S"));
+
+            Savepoint savepoint = Mockito.mock(Savepoint.class);
+            expectClosed(() -> conn.rollback(savepoint));
+            expectClosed(() -> conn.releaseSavepoint(savepoint));
+
+            expectClosed(() -> 
conn.createStatement(ResultSet.TYPE_FORWARD_ONLY,
+                    ResultSet.CONCUR_READ_ONLY, 
ResultSet.HOLD_CURSORS_OVER_COMMIT));
+
+            expectClosed(() -> conn.prepareStatement("SELECT ?",
+                    ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, 
ResultSet.HOLD_CURSORS_OVER_COMMIT));
+
+            expectClosed(() -> conn.prepareCall("SELECT F()",
+                    ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, 
ResultSet.HOLD_CURSORS_OVER_COMMIT));
+
+            expectClosed(() -> conn.prepareStatement("SELECT ?", 
Statement.NO_GENERATED_KEYS));
+            expectClosed(() -> conn.prepareStatement("SELECT ?", 
Statement.RETURN_GENERATED_KEYS));
+
+            expectClosed(() -> conn.prepareStatement("SELECT ?", new 
int[]{1}));
+
+            expectClosed(() -> conn.prepareStatement("SELECT ?", new 
String[]{"id"}));
+
+            expectClosed(conn::createClob);
+
+            expectClosed(conn::createBlob);
+
+            expectClosed(conn::createNClob);
+
+            expectClosed(conn::createSQLXML);
+
+            expectClosed(() -> conn.setClientInfo("A", "B"));
+            expectClosed(() -> conn.setClientInfo(new Properties()));
+            expectClosed(conn::getClientInfo);
+
+            expectClosed(() -> conn.createArrayOf("INTEGER", new Object[0]));
+            expectClosed(() -> conn.createStruct("MyStruct", new Object[0]));
+
+            expectClosed(() -> conn.getClientInfo("A"));
+            expectClosed(() -> conn.setSchema("S"));
+
+            expectClosed(conn::getSchema);
+
+            expectClosed(() -> conn.setNetworkTimeout(Runnable::run, 1));
+            expectClosed(conn::getNetworkTimeout);
+
+            expectClosed(conn::beginRequest);
+            expectClosed(conn::endRequest);
+
+            ShardingKey shardingKey = Mockito.mock(ShardingKey.class);
+            ShardingKey subShardingKey = Mockito.mock(ShardingKey.class);
+
+            expectClosed(() -> conn.setShardingKeyIfValid(shardingKey, 
subShardingKey, 1));
+            expectClosed(() -> conn.setShardingKeyIfValid(shardingKey, 1));
+
+            expectClosed(() -> conn.setShardingKey(shardingKey));
+            expectClosed(() -> conn.setShardingKey(shardingKey, 
subShardingKey));
+        }
+    }
+
+    @Test
+    public void abort() throws SQLException {
+        try (Connection conn = createConnection()) {
+            conn.abort(Runnable::run);
+
+            assertTrue(conn.isClosed());
+
+            // Does nothing
+            conn.close();
+            assertTrue(conn.isClosed());
+        }
+    }
+
+    @Test
+    public void notSupportedMethods() throws SQLException {
+        try (Connection conn = createConnection()) {
+
+            expectNotSupported(conn::getTypeMap);
+            expectNotSupported(() -> conn.setTypeMap(Map.of()));
+
+            /*
+            TODO https://issues.apache.org/jira/browse/IGNITE-26139 autocommit 
!= true
+             
+            Requires autocommit = false
+            expectNotSupported(conn::setSavepoint);
+            expectNotSupported(() -> conn.setSavepoint("S"));
+            
+            Savepoint savepoint = Mockito.mock(Savepoint.class);
+            expectNotSupported(() -> conn.rollback(savepoint));
+            expectNotSupported(() -> conn.releaseSavepoint(savepoint));
+             */
+
+            // createStatement - not supported flags
+
+            expectNotSupported(() -> 
conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,
+                    ResultSet.CONCUR_READ_ONLY, 
ResultSet.HOLD_CURSORS_OVER_COMMIT));
+
+            expectNotSupported(() -> 
conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE,
+                    ResultSet.CONCUR_READ_ONLY, 
ResultSet.HOLD_CURSORS_OVER_COMMIT));
+
+            expectNotSupported(() -> 
conn.createStatement(ResultSet.TYPE_FORWARD_ONLY,
+                    ResultSet.CONCUR_UPDATABLE, 
ResultSet.HOLD_CURSORS_OVER_COMMIT));
+
+            expectNotSupported(() -> 
conn.createStatement(ResultSet.TYPE_FORWARD_ONLY,
+                    ResultSet.CONCUR_READ_ONLY, 
ResultSet.HOLD_CURSORS_OVER_COMMIT));
+
+            // prepareStatement - not supported flags
+            expectNotSupported(() -> conn.prepareStatement("SELECT ?", 
Statement.RETURN_GENERATED_KEYS));
+
+            expectNotSupported(() -> conn.prepareStatement("SELECT ?", 
ResultSet.TYPE_SCROLL_INSENSITIVE,
+                    ResultSet.CONCUR_READ_ONLY, 
ResultSet.HOLD_CURSORS_OVER_COMMIT));
+
+            expectNotSupported(() -> conn.prepareStatement("SELECT ?", 
ResultSet.TYPE_SCROLL_SENSITIVE,
+                    ResultSet.CONCUR_READ_ONLY, 
ResultSet.HOLD_CURSORS_OVER_COMMIT));
+
+            expectNotSupported(() -> conn.prepareStatement("SELECT ?", 
ResultSet.TYPE_FORWARD_ONLY,
+                    ResultSet.CONCUR_UPDATABLE, 
ResultSet.HOLD_CURSORS_OVER_COMMIT));
+
+            expectNotSupported(() -> conn.prepareStatement("SELECT ?", 
ResultSet.TYPE_FORWARD_ONLY,
+                    ResultSet.CONCUR_READ_ONLY, 
ResultSet.HOLD_CURSORS_OVER_COMMIT));
+
+            expectNotSupported(() -> conn.prepareStatement("SELECT ?", new 
int[]{1}));
+            expectNotSupported(() -> conn.prepareStatement("SELECT ?", new 
String[]{"id"}));
+
+            // prepareCall
+
+            expectNotSupported(() -> conn.prepareCall("SELECT F()"));
+
+            expectNotSupported(() -> conn.prepareCall("SELECT F()", 
ResultSet.TYPE_SCROLL_INSENSITIVE,
+                    ResultSet.CONCUR_READ_ONLY, 
ResultSet.HOLD_CURSORS_OVER_COMMIT));
+
+            expectNotSupported(() -> conn.prepareCall("SELECT F()", 
ResultSet.TYPE_SCROLL_SENSITIVE,
+                    ResultSet.CONCUR_READ_ONLY, 
ResultSet.HOLD_CURSORS_OVER_COMMIT));
+
+            expectNotSupported(() -> conn.prepareCall("SELECT ?", 
ResultSet.TYPE_FORWARD_ONLY,
+                    ResultSet.CONCUR_UPDATABLE, 
ResultSet.HOLD_CURSORS_OVER_COMMIT));
+
+            expectNotSupported(() -> conn.prepareCall("SELECT F()", 
ResultSet.TYPE_FORWARD_ONLY,
+                    ResultSet.CONCUR_READ_ONLY, 
ResultSet.CLOSE_CURSORS_AT_COMMIT));
+
+            // Sharding key
+
+            ShardingKey shardingKey = Mockito.mock(ShardingKey.class);
+            ShardingKey subShardingKey = Mockito.mock(ShardingKey.class);
+
+            expectNotSupported(() -> conn.setShardingKeyIfValid(shardingKey, 
subShardingKey, 1));
+            expectNotSupported(() -> conn.setShardingKeyIfValid(shardingKey, 
1));
+
+            expectNotSupported(() -> conn.setShardingKey(shardingKey));
+            expectNotSupported(() -> conn.setShardingKey(shardingKey, 
subShardingKey));
+        }
+    }
+
+    @Test
+    public void notSupportedTypes() throws SQLException {
+        try (Connection conn = createConnection()) {
+            expectNotSupported(conn::createClob);
+            expectNotSupported(conn::createBlob);
+            expectNotSupported(conn::createNClob);
+            expectNotSupported(conn::createSQLXML);
+
+            expectNotSupported(() -> conn.createArrayOf("INTEGER", new 
Object[0]));
+            expectNotSupported(() -> conn.createStruct("MyStruct", new 
Object[0]));
+        }
+    }
+
+    @Test
+    public void catalog() throws SQLException {
+        try (Connection conn = createConnection()) {
+            assertEquals("IGNITE", conn.getCatalog());
+            // Does nothing
+            conn.setCatalog("C");
+            assertEquals("IGNITE", conn.getCatalog());
+        }
+    }
+
+    @Test
+    public void transactionIsolation() throws SQLException {
+        try (Connection conn = createConnection()) {
+            assertEquals(Connection.TRANSACTION_SERIALIZABLE, 
conn.getTransactionIsolation());
+
+            
conn.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED);
+            assertEquals(Connection.TRANSACTION_READ_UNCOMMITTED, 
conn.getTransactionIsolation());
+
+            
conn.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
+            assertEquals(Connection.TRANSACTION_READ_COMMITTED, 
conn.getTransactionIsolation());
+
+            
conn.setTransactionIsolation(Connection.TRANSACTION_REPEATABLE_READ);
+            assertEquals(Connection.TRANSACTION_REPEATABLE_READ, 
conn.getTransactionIsolation());
+
+            conn.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);
+            assertEquals(Connection.TRANSACTION_SERIALIZABLE, 
conn.getTransactionIsolation());
+
+            assertThrowsSqlException(SQLException.class,
+                    "Invalid transaction isolation level",
+                    () -> conn.setTransactionIsolation(123456)
+            );
+
+            // Does not change anything
+            assertEquals(Connection.TRANSACTION_SERIALIZABLE, 
conn.getTransactionIsolation());
+
+            assertThrowsSqlException(SQLException.class,
+                    "Cannot set transaction isolation level to 
TRANSACTION_NONE.",
+                    () -> 
conn.setTransactionIsolation(Connection.TRANSACTION_NONE)
+            );
+
+            // Does not change anything
+            assertEquals(Connection.TRANSACTION_SERIALIZABLE, 
conn.getTransactionIsolation());
+        }
+    }
+
+    @Test
+    public void holdability() throws SQLException {
+        try (Connection conn = createConnection()) {
+            assertEquals(ResultSet.CLOSE_CURSORS_AT_COMMIT, 
conn.getHoldability());
+
+            conn.setHoldability(ResultSet.CLOSE_CURSORS_AT_COMMIT);
+            assertEquals(ResultSet.CLOSE_CURSORS_AT_COMMIT, 
conn.getHoldability());
+
+            String error = "Invalid result set holdability (only close cursors 
at commit option is supported).";
+            assertThrowsSqlException(SQLException.class, error, () -> 
conn.setHoldability(ResultSet.HOLD_CURSORS_OVER_COMMIT));
+            assertThrowsSqlException(SQLException.class, error, () -> 
conn.setHoldability(1234));
+
+            // Does not change anything
+            assertEquals(ResultSet.CLOSE_CURSORS_AT_COMMIT, 
conn.getHoldability());
+        }
+    }
+
+    @Test
+    public void warnings() throws SQLException {
+        try (Connection conn = createConnection()) {
+            // Do nothing
+            assertNull(conn.getWarnings());
+            conn.clearWarnings();
+        }
+    }
+
+    @Test
+    public void valid() throws SQLException {
+        try (Connection conn = createConnection()) {
+            assertTrue(conn.isValid(0));
+            assertTrue(conn.isValid(1));
+            assertThrowsSqlException(SQLException.class, "Invalid timeout: 
-1", () -> conn.isValid(-1));
+
+            conn.close();
+
+            assertFalse(conn.isValid(0));
+            assertFalse(conn.isValid(1));
+            assertThrowsSqlException(SQLException.class, "Invalid timeout: 
-1", () -> conn.isValid(-1));
+        }
+    }
+
+    @Test
+    public void clientInfo() throws SQLException {
+        try (Connection conn = createConnection()) {
+            conn.setClientInfo("A", "B");
+            assertNull(conn.getClientInfo("A"));
+
+            conn.setClientInfo(new Properties());
+
+            Properties props = conn.getClientInfo();
+            assertNotNull(props);
+            assertTrue(props.isEmpty());
+        }
+    }
+
+    @Test
+    public void schema() throws SQLException {
+        try (Connection conn = createConnection()) {
+            // Default schema
+            assertEquals("PUBLIC", conn.getSchema());
+
+            conn.setSchema("abc");
+            assertEquals("abc", conn.getSchema());
+
+            conn.setSchema("\"Abc\"");
+            assertEquals("\"Abc\"", conn.getSchema());
+
+            // Empty value resets to default
+            conn.setSchema("");
+            assertEquals("PUBLIC", conn.getSchema());
+
+            conn.setSchema("S");
+            assertEquals("S", conn.getSchema());
+
+            conn.setSchema(null);
+            assertEquals("PUBLIC", conn.getSchema());
+        }
+
+        try (Connection conn = createConnection((props) -> {
+            props.setSchema("Abc");
+        })) {
+            assertEquals("Abc", conn.getSchema());
+        }
+
+        try (Connection conn = createConnection((props) -> {
+            props.setSchema("\"Abc\"");
+        })) {
+            assertEquals("\"Abc\"", conn.getSchema());
+        }
+    }
+
+    @Test
+    public void metadata() throws SQLException {
+        try (Connection conn = createConnection()) {
+            assertNotNull(conn.getMetaData());
+        }
+    }
+
+    @Test
+    public void wrap() throws SQLException {
+        try (Connection conn = createConnection()) {
+            assertTrue(conn.isWrapperFor(JdbcConnection2.class));
+            assertSame(conn, conn.unwrap(JdbcConnection2.class));
+
+            assertTrue(conn.isWrapperFor(Connection.class));
+            assertSame(conn, conn.unwrap(Connection.class));
+
+            assertFalse(conn.isWrapperFor(Statement.class));
+            assertThrowsSqlException(SQLException.class, "Connection is not a 
wrapper for ", () -> conn.unwrap(Statement.class));
+        }
+    }
+
+    private static Connection createConnection() throws SQLException {
+        return createConnection((props) -> {});
+    }
+
+    private static Connection createConnection(Consumer<ConnectionProperties> 
setup) throws SQLException {
+        IgniteSql igniteSql = Mockito.mock(IgniteSql.class);
+
+        ConnectionProperties properties = new ConnectionPropertiesImpl();
+        properties.setUrl("jdbc:ignite:thin://127.0.0.1:10800/");
+
+        setup.accept(properties);
+
+        JdbcQueryEventHandler eventHandler = 
Mockito.mock(JdbcQueryEventHandler.class);
+
+        return new JdbcConnection2(igniteSql, eventHandler, properties);
+    }
+
+    private static void expectClosed(Executable method) {
+        assertThrowsSqlException(SQLException.class, "Connection is closed.", 
method);
+    }
+
+    private static void expectNotSupported(Executable method) {
+        assertThrows(SQLFeatureNotSupportedException.class, method);
+    }
+}


Reply via email to