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);
+ }
+}