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

ppa 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 74861d43904 IGNITE-26428 Jdbc. PreparedStatement for thin client 
backed connection (#6677)
74861d43904 is described below

commit 74861d439045a0bd3f1d6ce45a750a941ab6afbf
Author: Max Zhuravkov <[email protected]>
AuthorDate: Thu Oct 9 17:30:44 2025 +0300

    IGNITE-26428 Jdbc. PreparedStatement for thin client backed connection 
(#6677)
---
 .../internal/jdbc2/JdbcPreparedStatement2.java     | 830 +++++++++++++++++++++
 .../ignite/internal/jdbc2/JdbcStatement2.java      |  30 +-
 .../jdbc2/JdbcPreparedStatement2SelfTest.java      | 727 ++++++++++++++++++
 .../internal/jdbc2/JdbcStatement2SelfTest.java     |   6 +-
 4 files changed, 1575 insertions(+), 18 deletions(-)

diff --git 
a/modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc2/JdbcPreparedStatement2.java
 
b/modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc2/JdbcPreparedStatement2.java
new file mode 100644
index 00000000000..4e74ebd579f
--- /dev/null
+++ 
b/modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc2/JdbcPreparedStatement2.java
@@ -0,0 +1,830 @@
+/*
+ * 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 java.io.InputStream;
+import java.io.Reader;
+import java.math.BigDecimal;
+import java.net.URL;
+import java.sql.Array;
+import java.sql.Blob;
+import java.sql.Clob;
+import java.sql.Connection;
+import java.sql.Date;
+import java.sql.JDBCType;
+import java.sql.NClob;
+import java.sql.ParameterMetaData;
+import java.sql.PreparedStatement;
+import java.sql.Ref;
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.RowId;
+import java.sql.SQLException;
+import java.sql.SQLFeatureNotSupportedException;
+import java.sql.SQLType;
+import java.sql.SQLXML;
+import java.sql.Time;
+import java.sql.Timestamp;
+import java.sql.Types;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.UUID;
+import org.apache.ignite.internal.jdbc.proto.SqlStateCode;
+import org.apache.ignite.sql.IgniteSql;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Jdbc prepared statement.
+ */
+public class JdbcPreparedStatement2 extends JdbcStatement2 implements 
PreparedStatement {
+
+    /** Supported JDBC types. */
+    private static final Set<JDBCType> SUPPORTED_TYPES = EnumSet.of(
+            JDBCType.BOOLEAN,
+            JDBCType.TINYINT,
+            JDBCType.SMALLINT,
+            JDBCType.INTEGER,
+            JDBCType.BIGINT,
+            JDBCType.FLOAT,
+            JDBCType.REAL,
+            JDBCType.DOUBLE,
+            JDBCType.DECIMAL,
+            JDBCType.DATE,
+            JDBCType.TIME,
+            JDBCType.TIMESTAMP,
+            JDBCType.CHAR,
+            JDBCType.VARCHAR,
+            JDBCType.BINARY,
+            JDBCType.VARBINARY,
+            JDBCType.NULL,
+            JDBCType.OTHER // UUID.
+    );
+
+    private static final String SQL_SPECIFIC_TYPES_ARE_NOT_SUPPORTED =
+            "SQL-specific types are not supported.";
+
+    private static final String STREAMS_ARE_NOT_SUPPORTED =
+            "Streams are not supported.";
+
+    private static final String CONVERSION_TO_TARGET_SQL_TYPE_IS_NOT_SUPPORTED 
=
+            "Conversion to target sql type is not supported.";
+
+    private static final String PARAMETER_METADATA_IS_NOT_SUPPORTED =
+            "Parameter metadata is not supported.";
+
+    private static final String RESULT_SET_METADATA_IS_NOT_SUPPORTED =
+            "ResultSet metadata for prepared statement is not supported.";
+
+    private final String sql;
+
+    private List<Object> currentArguments = List.of();
+
+    JdbcPreparedStatement2(
+            Connection connection,
+            IgniteSql igniteSql,
+            String schema,
+            int resHoldability,
+            String sql
+    ) {
+        super(connection, igniteSql, schema, resHoldability);
+
+        this.sql = sql;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public ResultSet executeQuery() throws SQLException {
+        execute0(QUERY, sql, currentArguments.toArray());
+
+        ResultSet rs = getResultSet();
+
+        if (rs == null) {
+            throw new SQLException("The query isn't SELECT query: " + sql, 
SqlStateCode.PARSING_EXCEPTION);
+        }
+
+        return rs;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public ResultSet executeQuery(String sql) throws SQLException {
+        throw new SQLException("The method 'executeQuery(String)' is called on 
PreparedStatement instance.",
+                SqlStateCode.UNSUPPORTED_OPERATION);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int[] executeBatch() throws SQLException {
+        ensureNotClosed();
+
+        throw new UnsupportedOperationException("Batch operation");
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int executeUpdate() throws SQLException {
+        execute0(DML_OR_DDL, sql, currentArguments.toArray());
+
+        int res = getUpdateCount();
+
+        if (res == -1) {
+            throw new SQLException("The query is not DML statement: " + sql, 
SqlStateCode.PARSING_EXCEPTION);
+        }
+
+        return res;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int executeUpdate(String sql) throws SQLException {
+        throw new SQLException("The method 'executeUpdate(String)' is called 
on PreparedStatement instance.",
+                SqlStateCode.UNSUPPORTED_OPERATION);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int executeUpdate(String sql, int autoGeneratedKeys) throws 
SQLException {
+        throw new SQLException("The method 'executeUpdate(String, int)' is 
called on PreparedStatement instance.",
+                SqlStateCode.UNSUPPORTED_OPERATION);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int executeUpdate(String sql, String[] colNames) throws 
SQLException {
+        throw new SQLException("The method 'executeUpdate(String, String[])' 
is called on PreparedStatement "
+                + "instance.", SqlStateCode.UNSUPPORTED_OPERATION);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int executeUpdate(String sql, int[] colNames) throws SQLException {
+        throw new SQLException("The method 'executeUpdate(String, int[])' is 
called on PreparedStatement instance.",
+                SqlStateCode.UNSUPPORTED_OPERATION);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean execute() throws SQLException {
+        execute0(ALL, sql, currentArguments.toArray());
+
+        return isQuery();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean execute(String sql) throws SQLException {
+        throw new SQLException("The method 'execute(String)' is called on 
PreparedStatement instance.",
+                SqlStateCode.UNSUPPORTED_OPERATION);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean execute(String sql, int autoGeneratedKeys) throws 
SQLException {
+        throw new SQLException("The method 'execute(String, int)' is called on 
PreparedStatement "
+                + "instance.", SqlStateCode.UNSUPPORTED_OPERATION);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean execute(String sql, int[] colNames) throws SQLException {
+        throw new SQLException("The method 'execute(String, int[])' is called 
on PreparedStatement "
+                + "instance.", SqlStateCode.UNSUPPORTED_OPERATION);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean execute(String sql, String[] colNames) throws SQLException {
+        throw new SQLException("The method 'execute(String, String[]) is 
called on PreparedStatement "
+                + "instance.", SqlStateCode.UNSUPPORTED_OPERATION);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void addBatch() throws SQLException {
+        ensureNotClosed();
+
+        throw new UnsupportedOperationException("Batch operation");
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void addBatch(String sql) throws SQLException {
+        throw new SQLException("The method 'addBatch(String)' is called on 
PreparedStatement instance.",
+                SqlStateCode.UNSUPPORTED_OPERATION);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void clearBatch() throws SQLException {
+        ensureNotClosed();
+
+        throw new UnsupportedOperationException("Batch operation");
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setNull(int paramIdx, int sqlType) throws SQLException {
+        ensureNotClosed();
+        checkType(sqlType);
+
+        setArgumentValue(paramIdx, null);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setNull(int paramIdx, int sqlType, String typeName) throws 
SQLException {
+        ensureNotClosed();
+        checkType(sqlType);
+
+        setArgumentValue(paramIdx, null);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setBoolean(int paramIdx, boolean x) throws SQLException {
+        ensureNotClosed();
+        setArgumentValue(paramIdx, x);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setByte(int paramIdx, byte x) throws SQLException {
+        ensureNotClosed();
+        setArgumentValue(paramIdx, x);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setShort(int paramIdx, short x) throws SQLException {
+        ensureNotClosed();
+        setArgumentValue(paramIdx, x);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setInt(int paramIdx, int x) throws SQLException {
+        ensureNotClosed();
+        setArgumentValue(paramIdx, x);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setLong(int paramIdx, long x) throws SQLException {
+        ensureNotClosed();
+        setArgumentValue(paramIdx, x);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setFloat(int paramIdx, float x) throws SQLException {
+        ensureNotClosed();
+        setArgumentValue(paramIdx, x);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setDouble(int paramIdx, double x) throws SQLException {
+        ensureNotClosed();
+        setArgumentValue(paramIdx, x);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setBigDecimal(int paramIdx, BigDecimal x) throws SQLException {
+        if (x == null) {
+            setNull(paramIdx, Types.DECIMAL);
+        } else {
+            ensureNotClosed();
+            setArgumentValue(paramIdx, x);
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setString(int paramIdx, String x) throws SQLException {
+        if (x == null) {
+            setNull(paramIdx, Types.VARCHAR);
+        } else {
+            ensureNotClosed();
+            setArgumentValue(paramIdx, x);
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setBytes(int paramIdx, byte[] x) throws SQLException {
+        if (x == null) {
+            setNull(paramIdx, Types.VARBINARY);
+        } else {
+            ensureNotClosed();
+            setArgumentValue(paramIdx, x);
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setDate(int paramIdx, Date x) throws SQLException {
+        if (x == null) {
+            setNull(paramIdx, Types.DATE);
+        } else {
+            setLocalDate(paramIdx, x.toLocalDate());
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setDate(int paramIdx, Date x, Calendar cal) throws 
SQLException {
+        setDate(paramIdx, x);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setTime(int paramIdx, Time x) throws SQLException {
+        if (x == null) {
+            setNull(paramIdx, Types.TIME);
+        } else {
+            setLocalTime(paramIdx, x.toLocalTime());
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setTime(int paramIdx, Time x, Calendar cal) throws 
SQLException {
+        setTime(paramIdx, x);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setTimestamp(int paramIdx, Timestamp x) throws SQLException {
+        if (x == null) {
+            setNull(paramIdx, Types.TIMESTAMP);
+        } else {
+            setLocalDateTime(paramIdx, x.toLocalDateTime());
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setTimestamp(int paramIdx, Timestamp x, Calendar cal) throws 
SQLException {
+        setTimestamp(paramIdx, x);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setAsciiStream(int paramIdx, InputStream x, int len) throws 
SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException(STREAMS_ARE_NOT_SUPPORTED);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setAsciiStream(int paramIdx, InputStream x) throws 
SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException(STREAMS_ARE_NOT_SUPPORTED);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setAsciiStream(int paramIdx, InputStream x, long len) throws 
SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException(STREAMS_ARE_NOT_SUPPORTED);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setUnicodeStream(int paramIdx, InputStream x, int len) throws 
SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException(STREAMS_ARE_NOT_SUPPORTED);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setBinaryStream(int paramIdx, InputStream x, int len) throws 
SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException(STREAMS_ARE_NOT_SUPPORTED);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setBinaryStream(int paramIdx, InputStream x, long len) throws 
SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException(STREAMS_ARE_NOT_SUPPORTED);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setBinaryStream(int paramIdx, InputStream x) throws 
SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException(STREAMS_ARE_NOT_SUPPORTED);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void clearParameters() throws SQLException {
+        ensureNotClosed();
+
+        currentArguments = List.of();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setObject(int paramIdx, Object x) throws SQLException {
+        if (x == null) {
+            setNull(paramIdx, Types.NULL);
+        } else if (x instanceof Boolean) {
+            setBoolean(paramIdx, (Boolean) x);
+        } else if (x instanceof Byte) {
+            setByte(paramIdx, (Byte) x);
+        } else if (x instanceof Short) {
+            setShort(paramIdx, (Short) x);
+        } else if (x instanceof Integer) {
+            setInt(paramIdx, (Integer) x);
+        } else if (x instanceof Long) {
+            setLong(paramIdx, (Long) x);
+        } else if (x instanceof Float) {
+            setFloat(paramIdx, (Float) x);
+        } else if (x instanceof Double) {
+            setDouble(paramIdx, (Double) x);
+        } else if (x instanceof BigDecimal) {
+            setBigDecimal(paramIdx, (BigDecimal) x);
+        } else if (x instanceof String) {
+            setString(paramIdx, (String) x);
+        } else if (x instanceof byte[]) {
+            setBytes(paramIdx, (byte[]) x);
+        } else if (x instanceof Date) {
+            setDate(paramIdx, (Date) x);
+        } else if (x instanceof Time) {
+            setTime(paramIdx, (Time) x);
+        } else if (x instanceof Timestamp) {
+            setTimestamp(paramIdx, (Timestamp) x);
+        } else if (x instanceof LocalTime) {
+            setLocalTime(paramIdx, (LocalTime) x);
+        } else if (x instanceof LocalDate) {
+            setLocalDate(paramIdx, (LocalDate) x);
+        } else if (x instanceof LocalDateTime) {
+            setLocalDateTime(paramIdx, (LocalDateTime) x);
+        } else if (x instanceof Instant) {
+            setInstant(paramIdx, (Instant) x);
+        } else if (x instanceof UUID) {
+            setUuid(paramIdx, (UUID) x);
+        } else {
+            throw new SQLException("Parameter type is not supported: " + 
x.getClass().getName());
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setObject(int paramIdx, Object x, int targetSqlType, int 
scaleOrLen) throws SQLException {
+        ensureNotClosed();
+
+        throw new 
SQLFeatureNotSupportedException(CONVERSION_TO_TARGET_SQL_TYPE_IS_NOT_SUPPORTED);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setObject(int paramIdx, Object x, int targetSqlType) throws 
SQLException {
+        ensureNotClosed();
+
+        throw new 
SQLFeatureNotSupportedException(CONVERSION_TO_TARGET_SQL_TYPE_IS_NOT_SUPPORTED);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setObject(int paramIdx, Object x, SQLType targetSqlType) 
throws SQLException {
+        ensureNotClosed();
+
+        throw new 
SQLFeatureNotSupportedException(CONVERSION_TO_TARGET_SQL_TYPE_IS_NOT_SUPPORTED);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setObject(int paramIdx, Object x, SQLType targetSqlType, int 
scaleOrLength) throws SQLException {
+        ensureNotClosed();
+
+        throw new 
SQLFeatureNotSupportedException(CONVERSION_TO_TARGET_SQL_TYPE_IS_NOT_SUPPORTED);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setCharacterStream(int paramIdx, Reader x, int len) throws 
SQLException {
+        ensureNotClosed();
+
+        throw new 
SQLFeatureNotSupportedException(SQL_SPECIFIC_TYPES_ARE_NOT_SUPPORTED);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setCharacterStream(int paramIdx, Reader x, long len) throws 
SQLException {
+        ensureNotClosed();
+
+        throw new 
SQLFeatureNotSupportedException(SQL_SPECIFIC_TYPES_ARE_NOT_SUPPORTED);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setCharacterStream(int paramIdx, Reader x) throws SQLException 
{
+        ensureNotClosed();
+
+        throw new 
SQLFeatureNotSupportedException(SQL_SPECIFIC_TYPES_ARE_NOT_SUPPORTED);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setRef(int paramIdx, Ref x) throws SQLException {
+        ensureNotClosed();
+
+        throw new 
SQLFeatureNotSupportedException(SQL_SPECIFIC_TYPES_ARE_NOT_SUPPORTED);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setBlob(int paramIdx, Blob x) throws SQLException {
+        ensureNotClosed();
+
+        throw new 
SQLFeatureNotSupportedException(SQL_SPECIFIC_TYPES_ARE_NOT_SUPPORTED);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setBlob(int paramIdx, InputStream inputStream) throws 
SQLException {
+        ensureNotClosed();
+
+        throw new 
SQLFeatureNotSupportedException(SQL_SPECIFIC_TYPES_ARE_NOT_SUPPORTED);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setBlob(int paramIdx, InputStream inputStream, long len) 
throws SQLException {
+        ensureNotClosed();
+
+        throw new 
SQLFeatureNotSupportedException(SQL_SPECIFIC_TYPES_ARE_NOT_SUPPORTED);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setClob(int paramIdx, Clob x) throws SQLException {
+        ensureNotClosed();
+
+        throw new 
SQLFeatureNotSupportedException(SQL_SPECIFIC_TYPES_ARE_NOT_SUPPORTED);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setClob(int paramIdx, Reader reader, long len) throws 
SQLException {
+        ensureNotClosed();
+
+        throw new 
SQLFeatureNotSupportedException(SQL_SPECIFIC_TYPES_ARE_NOT_SUPPORTED);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setClob(int paramIdx, Reader reader) throws SQLException {
+        ensureNotClosed();
+
+        throw new 
SQLFeatureNotSupportedException(SQL_SPECIFIC_TYPES_ARE_NOT_SUPPORTED);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setArray(int paramIdx, Array x) throws SQLException {
+        ensureNotClosed();
+
+        throw new 
SQLFeatureNotSupportedException(SQL_SPECIFIC_TYPES_ARE_NOT_SUPPORTED);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public ResultSetMetaData getMetaData() throws SQLException {
+        ensureNotClosed();
+
+        throw new 
SQLFeatureNotSupportedException(RESULT_SET_METADATA_IS_NOT_SUPPORTED);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setURL(int paramIdx, URL x) throws SQLException {
+        ensureNotClosed();
+
+        throw new 
SQLFeatureNotSupportedException(SQL_SPECIFIC_TYPES_ARE_NOT_SUPPORTED);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public ParameterMetaData getParameterMetaData() throws SQLException {
+        ensureNotClosed();
+
+        throw new 
SQLFeatureNotSupportedException(PARAMETER_METADATA_IS_NOT_SUPPORTED);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setRowId(int paramIdx, RowId x) throws SQLException {
+        ensureNotClosed();
+
+        throw new 
SQLFeatureNotSupportedException(SQL_SPECIFIC_TYPES_ARE_NOT_SUPPORTED);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setNString(int paramIdx, String val) throws SQLException {
+        ensureNotClosed();
+
+        setString(paramIdx, val);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setNCharacterStream(int paramIdx, Reader val, long len) throws 
SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException(STREAMS_ARE_NOT_SUPPORTED);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setNCharacterStream(int paramIdx, Reader val) throws 
SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException(STREAMS_ARE_NOT_SUPPORTED);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setNClob(int paramIdx, NClob val) throws SQLException {
+        ensureNotClosed();
+
+        throw new 
SQLFeatureNotSupportedException(SQL_SPECIFIC_TYPES_ARE_NOT_SUPPORTED);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setNClob(int paramIdx, Reader reader) throws SQLException {
+        ensureNotClosed();
+
+        throw new 
SQLFeatureNotSupportedException(SQL_SPECIFIC_TYPES_ARE_NOT_SUPPORTED);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setNClob(int paramIdx, Reader reader, long len) throws 
SQLException {
+        ensureNotClosed();
+
+        throw new 
SQLFeatureNotSupportedException(SQL_SPECIFIC_TYPES_ARE_NOT_SUPPORTED);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setSQLXML(int paramIdx, SQLXML xmlObj) throws SQLException {
+        ensureNotClosed();
+
+        throw new 
SQLFeatureNotSupportedException(SQL_SPECIFIC_TYPES_ARE_NOT_SUPPORTED);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public long[] executeLargeBatch() throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("executeLargeBatch not 
implemented.");
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public long executeLargeUpdate() throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("executeLargeUpdate not 
implemented.");
+    }
+
+    @Override
+    public long executeLargeUpdate(String sql, int autoGeneratedKeys) throws 
SQLException {
+        ensureNotClosed();
+
+        throw new SQLException("The method 'executeLargeUpdate(String, int)' 
is called on PreparedStatement instance.",
+                SqlStateCode.UNSUPPORTED_OPERATION);
+    }
+
+    @Override
+    public long executeLargeUpdate(String sql) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLException("The method 'executeLargeUpdate(String)' is 
called on PreparedStatement instance.",
+                SqlStateCode.UNSUPPORTED_OPERATION);
+    }
+
+    @Override
+    public long executeLargeUpdate(String sql, int[] columnIndexes) throws 
SQLException {
+        ensureNotClosed();
+
+        throw new SQLException("The method 'executeLargeUpdate(String, int[])' 
is called on PreparedStatement instance.",
+                SqlStateCode.UNSUPPORTED_OPERATION);
+    }
+
+    @Override
+    public long executeLargeUpdate(String sql, String[] columnNames) throws 
SQLException {
+        ensureNotClosed();
+
+        throw new SQLException("The method 'executeLargeUpdate(String, 
String[])' is called on PreparedStatement instance.",
+                SqlStateCode.UNSUPPORTED_OPERATION);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public <T> T unwrap(Class<T> iface) throws SQLException {
+        if (!isWrapperFor(Objects.requireNonNull(iface))) {
+            throw new SQLException("Prepared 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(JdbcPreparedStatement2.class);
+    }
+
+    private void setLocalDate(int paramIdx, LocalDate x) throws SQLException {
+        ensureNotClosed();
+        setArgumentValue(paramIdx, x);
+    }
+
+    private void setLocalTime(int paramIdx, LocalTime x) throws SQLException {
+        ensureNotClosed();
+        setArgumentValue(paramIdx, x);
+    }
+
+    private void setLocalDateTime(int paramIdx, LocalDateTime x) throws 
SQLException {
+        ensureNotClosed();
+        setArgumentValue(paramIdx, x);
+    }
+
+    private void setInstant(int paramIdx, Instant x) throws SQLException {
+        ensureNotClosed();
+        setArgumentValue(paramIdx, x);
+    }
+
+    private void setUuid(int paramIdx, UUID x) throws SQLException {
+        ensureNotClosed();
+        setArgumentValue(paramIdx, x);
+    }
+
+    private void setArgumentValue(int paramIdx, @Nullable Object val) throws 
SQLException {
+        // The caller method ensures that this statement is not closed.
+
+        if (paramIdx < 1) {
+            throw new SQLException("Parameter index is invalid: " + paramIdx);
+        }
+
+        if (currentArguments.isEmpty()) {
+            currentArguments = new ArrayList<>(paramIdx);
+        }
+
+        while (currentArguments.size() < paramIdx) {
+            currentArguments.add(null);
+        }
+
+        currentArguments.set(paramIdx - 1, val);
+    }
+
+    List<Object> getArguments() {
+        return currentArguments;
+    }
+
+    private static void checkType(int sqlType) throws SQLException {
+        JDBCType jdbcType = JDBCType.valueOf(sqlType);
+        if (!SUPPORTED_TYPES.contains(jdbcType)) {
+            throw new 
SQLFeatureNotSupportedException(SQL_SPECIFIC_TYPES_ARE_NOT_SUPPORTED);
+        }
+    }
+}
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
index 53fefc6bd4b..f1612305525 100644
--- 
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
@@ -50,16 +50,18 @@ import org.jetbrains.annotations.Nullable;
  */
 public class JdbcStatement2 implements Statement {
 
-    private static final EnumSet<QueryModifier> QUERY = 
EnumSet.of(QueryModifier.ALLOW_ROW_SET_RESULT);
+    static final EnumSet<QueryModifier> QUERY = 
EnumSet.of(QueryModifier.ALLOW_ROW_SET_RESULT);
 
-    private static final EnumSet<QueryModifier> DML_OR_DDL = EnumSet.of(
+    static final EnumSet<QueryModifier> DML_OR_DDL = EnumSet.of(
             QueryModifier.ALLOW_AFFECTED_ROWS_RESULT, 
QueryModifier.ALLOW_APPLIED_RESULT);
 
+    static final Set<QueryModifier> ALL = QueryModifier.ALL;
+
     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";
+            "executeLargeUpdate not implemented.";
 
     private static final String FIELD_SIZE_LIMIT_IS_NOT_SUPPORTED =
             "Field size limit is not supported.";
@@ -363,7 +365,7 @@ public class JdbcStatement2 implements Statement {
     public boolean execute(String sql) throws SQLException {
         ensureNotClosed();
 
-        execute0(EnumSet.allOf(QueryModifier.class), 
Objects.requireNonNull(sql), ArrayUtils.OBJECT_EMPTY_ARRAY);
+        execute0(QueryModifier.ALL, Objects.requireNonNull(sql), 
ArrayUtils.OBJECT_EMPTY_ARRAY);
 
         return isQuery();
     }
@@ -414,7 +416,7 @@ public class JdbcStatement2 implements Statement {
     public @Nullable ResultSet getResultSet() throws SQLException {
         ensureNotClosed();
 
-        return isQuery() ? resultSet : null;
+        return resultSet;
     }
 
     /** {@inheritDoc} */
@@ -648,19 +650,17 @@ public class JdbcStatement2 implements Statement {
         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();
+        // This method is called after statement is executed, so the reference 
points to a correct result set.
+        // The statement is not expected to be used from multiple threads, so 
this reference points to a correct result set.
+        // getResultSet() performs its own result set checks.
+        JdbcResultSet rs = resultSet;
+        assert rs != null;
+
+        return rs.isQuery();
     }
 
-    private void ensureNotClosed() throws SQLException {
+    void ensureNotClosed() throws SQLException {
         if (isClosed()) {
             throw new SQLException(STATEMENT_IS_CLOSED);
         }
diff --git 
a/modules/jdbc/src/test/java/org/apache/ignite/internal/jdbc2/JdbcPreparedStatement2SelfTest.java
 
b/modules/jdbc/src/test/java/org/apache/ignite/internal/jdbc2/JdbcPreparedStatement2SelfTest.java
new file mode 100644
index 00000000000..fe2cdb287b6
--- /dev/null
+++ 
b/modules/jdbc/src/test/java/org/apache/ignite/internal/jdbc2/JdbcPreparedStatement2SelfTest.java
@@ -0,0 +1,727 @@
+/*
+ * 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.assertArrayEquals;
+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.assertTrue;
+import static org.mockito.Mockito.when;
+
+import java.io.InputStream;
+import java.io.StringReader;
+import java.math.BigDecimal;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.sql.Array;
+import java.sql.Blob;
+import java.sql.Clob;
+import java.sql.Connection;
+import java.sql.Date;
+import java.sql.JDBCType;
+import java.sql.NClob;
+import java.sql.PreparedStatement;
+import java.sql.Ref;
+import java.sql.ResultSet;
+import java.sql.RowId;
+import java.sql.SQLException;
+import java.sql.SQLXML;
+import java.sql.Statement;
+import java.sql.Time;
+import java.sql.Timestamp;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Set;
+import java.util.UUID;
+import org.apache.ignite.internal.jdbc.ConnectionProperties;
+import org.apache.ignite.internal.jdbc.ConnectionPropertiesImpl;
+import org.apache.ignite.sql.IgniteSql;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.function.Executable;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.EnumSource;
+import org.mockito.Mockito;
+
+/**
+ * Unit tests for {@link JdbcPreparedStatement2}.
+ */
+public class JdbcPreparedStatement2SelfTest extends JdbcStatement2SelfTest {
+
+    private static final Set<JDBCType> SUPPORTED_TYPES = EnumSet.of(
+            JDBCType.BOOLEAN,
+            JDBCType.TINYINT,
+            JDBCType.SMALLINT,
+            JDBCType.INTEGER,
+            JDBCType.BIGINT,
+            JDBCType.FLOAT,
+            JDBCType.REAL,
+            JDBCType.DOUBLE,
+            JDBCType.DECIMAL,
+            JDBCType.DATE,
+            JDBCType.TIME,
+            JDBCType.TIMESTAMP,
+            JDBCType.CHAR,
+            JDBCType.VARCHAR,
+            JDBCType.BINARY,
+            JDBCType.VARBINARY,
+            JDBCType.NULL,
+            JDBCType.OTHER // UUID.
+    );
+
+    @Test
+    public void assignAndClearParameters() throws SQLException {
+        try (PreparedStatement stmt = createStatement()) {
+            JdbcPreparedStatement2 ps = 
stmt.unwrap(JdbcPreparedStatement2.class);
+
+            stmt.setObject(1, 1);
+            stmt.setObject(3, 10L);
+
+            assertEquals(Arrays.asList(1, null, 10L), ps.getArguments());
+
+            stmt.clearParameters();
+            assertEquals(List.of(), ps.getArguments());
+
+            stmt.setObject(2, 1);
+            assertEquals(Arrays.asList(null, 1), ps.getArguments());
+
+            stmt.setObject(2, 2);
+            assertEquals(Arrays.asList(null, 2), ps.getArguments());
+
+            stmt.setObject(3, 4);
+            assertEquals(Arrays.asList(null, 2, 4), ps.getArguments());
+        }
+    }
+
+    @ParameterizedTest
+    @EnumSource(JDBCType.class)
+    public void setNull(JDBCType type) throws SQLException {
+        try (PreparedStatement stmt = createStatement()) {
+            JdbcPreparedStatement2 ps = (JdbcPreparedStatement2) stmt;
+
+            int jdbcSqlType = type.getVendorTypeNumber();
+
+            if (SUPPORTED_TYPES.contains(type)) {
+                stmt.setNull(1, jdbcSqlType);
+                assertFalse(ps.getArguments().isEmpty());
+                assertNull(ps.getArguments().get(0));
+
+                // Spec: If the parameter does not have a user-defined or REF 
type, the given typeName is ignored.
+
+                stmt.setNull(2, jdbcSqlType, null);
+                assertFalse(ps.getArguments().isEmpty());
+                assertNull(ps.getArguments().get(1));
+
+                stmt.setNull(2, jdbcSqlType, "TypeName");
+                assertFalse(ps.getArguments().isEmpty());
+                assertNull(ps.getArguments().get(1));
+
+            } else {
+                String error = "SQL-specific types are not supported.";
+
+                expectError(() -> stmt.setNull(1, jdbcSqlType), error);
+                expectError(() -> stmt.setNull(1, jdbcSqlType, null), error);
+                expectError(() -> stmt.setNull(1, jdbcSqlType, "TypeName"), 
error);
+            }
+        }
+    }
+
+    @Test
+    public void setBoolean() throws SQLException {
+        try (PreparedStatement stmt = createStatement()) {
+            JdbcPreparedStatement2 ps = (JdbcPreparedStatement2) stmt;
+
+            stmt.setBoolean(1, true);
+            assertFalse(ps.getArguments().isEmpty());
+            assertEquals(Boolean.TRUE, ps.getArguments().get(0));
+        }
+    }
+
+    @Test
+    public void setByte() throws SQLException {
+        try (PreparedStatement stmt = createStatement()) {
+            JdbcPreparedStatement2 ps = (JdbcPreparedStatement2) stmt;
+
+            stmt.setByte(1, (byte) 7);
+            assertFalse(ps.getArguments().isEmpty());
+            assertEquals((byte) 7, ps.getArguments().get(0));
+        }
+    }
+
+    @Test
+    public void setShort() throws SQLException {
+        try (PreparedStatement stmt = createStatement()) {
+            JdbcPreparedStatement2 ps = (JdbcPreparedStatement2) stmt;
+
+            stmt.setShort(1, (short) 123);
+            assertFalse(ps.getArguments().isEmpty());
+            assertEquals((short) 123, ps.getArguments().get(0));
+        }
+    }
+
+    @Test
+    public void setInt() throws SQLException {
+        try (PreparedStatement stmt = createStatement()) {
+            JdbcPreparedStatement2 ps = (JdbcPreparedStatement2) stmt;
+
+            stmt.setInt(1, 42);
+            assertFalse(ps.getArguments().isEmpty());
+            assertEquals(42, ps.getArguments().get(0));
+        }
+    }
+
+    @Test
+    public void setLong() throws SQLException {
+        try (PreparedStatement stmt = createStatement()) {
+            JdbcPreparedStatement2 ps = (JdbcPreparedStatement2) stmt;
+
+            stmt.setLong(1, 1322L);
+            assertFalse(ps.getArguments().isEmpty());
+            assertEquals(1322L, ps.getArguments().get(0));
+        }
+    }
+
+    @Test
+    public void setFloat() throws SQLException {
+        try (PreparedStatement stmt = createStatement()) {
+            JdbcPreparedStatement2 ps = (JdbcPreparedStatement2) stmt;
+
+            stmt.setFloat(1, 1.5f);
+            assertFalse(ps.getArguments().isEmpty());
+            assertEquals(1.5f, ps.getArguments().get(0));
+        }
+    }
+
+    @Test
+    public void setDouble() throws SQLException {
+        try (PreparedStatement stmt = createStatement()) {
+            JdbcPreparedStatement2 ps = (JdbcPreparedStatement2) stmt;
+
+            stmt.setDouble(1, 2.75d);
+            assertFalse(ps.getArguments().isEmpty());
+            assertEquals(2.75d, ps.getArguments().get(0));
+        }
+    }
+
+    @Test
+    public void setDecimal() throws SQLException {
+        try (PreparedStatement stmt = createStatement()) {
+            JdbcPreparedStatement2 ps = (JdbcPreparedStatement2) stmt;
+
+            BigDecimal dec = new BigDecimal("1234.5678");
+            stmt.setBigDecimal(1, dec);
+            assertFalse(ps.getArguments().isEmpty());
+            assertEquals(dec, ps.getArguments().get(0));
+
+            stmt.setBigDecimal(2, null);
+            assertTrue(ps.getArguments().size() >= 2);
+            assertNull(ps.getArguments().get(1));
+        }
+    }
+
+    @Test
+    public void setString() throws SQLException {
+        try (PreparedStatement stmt = createStatement()) {
+            JdbcPreparedStatement2 ps = (JdbcPreparedStatement2) stmt;
+
+            stmt.setString(1, "hello");
+            assertFalse(ps.getArguments().isEmpty());
+            assertEquals("hello", ps.getArguments().get(0));
+
+            stmt.setString(2, null);
+            assertTrue(ps.getArguments().size() >= 2);
+            assertNull(ps.getArguments().get(1));
+        }
+    }
+
+    @Test
+    public void setNationalString() throws SQLException {
+        try (PreparedStatement stmt = createStatement()) {
+            JdbcPreparedStatement2 ps = (JdbcPreparedStatement2) stmt;
+
+            stmt.setNString(1, "Hello");
+            assertFalse(ps.getArguments().isEmpty());
+            assertEquals("Hello", ps.getArguments().get(0));
+
+            stmt.setNString(2, null);
+            assertTrue(ps.getArguments().size() >= 2);
+            assertNull(ps.getArguments().get(1));
+        }
+    }
+
+    @Test
+    public void setBytes() throws SQLException {
+        try (PreparedStatement stmt = createStatement()) {
+            JdbcPreparedStatement2 ps = (JdbcPreparedStatement2) stmt;
+
+            byte[] bytes = {1, 2, 3, 4};
+            stmt.setBytes(1, bytes);
+            assertFalse(ps.getArguments().isEmpty());
+            assertArrayEquals(bytes, (byte[]) ps.getArguments().get(0));
+
+            stmt.setBytes(2, null);
+            assertTrue(ps.getArguments().size() >= 2);
+            assertNull(ps.getArguments().get(1));
+        }
+    }
+
+    @Test
+    public void setDate() throws SQLException {
+        try (PreparedStatement stmt = createStatement()) {
+            JdbcPreparedStatement2 ps = (JdbcPreparedStatement2) stmt;
+
+            LocalDate ld = LocalDate.of(2020, 1, 2);
+            stmt.setDate(1, Date.valueOf(ld));
+            assertFalse(ps.getArguments().isEmpty());
+            assertEquals(ld, ps.getArguments().get(0));
+
+            stmt.setDate(2, null);
+            assertTrue(ps.getArguments().size() >= 2);
+            assertNull(ps.getArguments().get(1));
+
+            stmt.setDate(3, Date.valueOf(ld), Calendar.getInstance());
+            assertTrue(ps.getArguments().size() >= 3);
+            assertEquals(ld, ps.getArguments().get(2));
+        }
+    }
+
+    @Test
+    public void setTime() throws SQLException {
+        try (PreparedStatement stmt = createStatement()) {
+            JdbcPreparedStatement2 ps = (JdbcPreparedStatement2) stmt;
+
+            LocalTime lt = LocalTime.of(11, 22, 33);
+            stmt.setTime(1, Time.valueOf(lt));
+            assertFalse(ps.getArguments().isEmpty());
+            assertEquals(lt, ps.getArguments().get(0));
+
+            stmt.setTime(2, null);
+            assertTrue(ps.getArguments().size() >= 2);
+            assertNull(ps.getArguments().get(1));
+
+            stmt.setTime(3, Time.valueOf(lt), Calendar.getInstance());
+            assertTrue(ps.getArguments().size() >= 3);
+            assertEquals(lt, ps.getArguments().get(2));
+        }
+    }
+
+    @Test
+    public void setTimestamp() throws SQLException {
+        try (PreparedStatement stmt = createStatement()) {
+            JdbcPreparedStatement2 ps = (JdbcPreparedStatement2) stmt;
+
+            LocalDateTime ldt = LocalDateTime.of(2021, 5, 6, 7, 8, 9);
+            stmt.setTimestamp(1, Timestamp.valueOf(ldt));
+            assertFalse(ps.getArguments().isEmpty());
+            assertEquals(ldt, ps.getArguments().get(0));
+
+            stmt.setTimestamp(2, null);
+            assertTrue(ps.getArguments().size() >= 2);
+            assertNull(ps.getArguments().get(1));
+
+            stmt.setTimestamp(3, Timestamp.valueOf(ldt), 
Calendar.getInstance());
+            assertTrue(ps.getArguments().size() >= 3);
+            assertEquals(ldt, ps.getArguments().get(2));
+        }
+    }
+
+    @Test
+    public void setObject() throws SQLException {
+        try (PreparedStatement stmt = createStatement()) {
+            JdbcPreparedStatement2 ps = (JdbcPreparedStatement2) stmt;
+
+            stmt.setObject(1, null);
+            assertNull(ps.getArguments().get(0));
+
+            stmt.setObject(2, (byte) 1);
+            assertEquals((byte) 1, ps.getArguments().get(1));
+
+            stmt.setObject(3, (short) 2);
+            assertEquals((short) 2, ps.getArguments().get(2));
+
+            stmt.setObject(4, 42);
+            assertEquals(42, ps.getArguments().get(3));
+
+            stmt.setObject(5, 9L);
+            assertEquals(9L, ps.getArguments().get(4));
+
+            stmt.setObject(6, 1.0f);
+            assertEquals(1.0f, ps.getArguments().get(5));
+
+            stmt.setObject(7, 2.0d);
+            assertEquals(2.0d, ps.getArguments().get(6));
+
+            stmt.setObject(8, BigDecimal.ONE);
+            assertEquals(BigDecimal.ONE, ps.getArguments().get(7));
+
+            stmt.setObject(9, "str");
+            assertEquals("str", ps.getArguments().get(8));
+
+            stmt.setObject(10, "123".getBytes(StandardCharsets.UTF_8));
+            assertArrayEquals("123".getBytes(StandardCharsets.UTF_8), (byte[]) 
ps.getArguments().get(9));
+
+            Date sqlDate = Date.valueOf(LocalDate.now());
+            stmt.setObject(11, sqlDate);
+            assertEquals(sqlDate.toLocalDate(), ps.getArguments().get(10));
+
+            Time sqlTime = Time.valueOf(LocalTime.now());
+            stmt.setObject(12, sqlTime);
+            assertEquals(sqlTime.toLocalTime(), ps.getArguments().get(11));
+
+            Timestamp sqlTimestamp = Timestamp.valueOf(LocalDateTime.now());
+            stmt.setObject(13, sqlTimestamp);
+            assertEquals(sqlTimestamp.toLocalDateTime(), 
ps.getArguments().get(12));
+
+            UUID uuid = UUID.randomUUID();
+            stmt.setObject(14, uuid);
+            assertEquals(uuid, ps.getArguments().get(13));
+
+            // java.time classes
+
+            LocalDate date = LocalDate.now();
+            stmt.setObject(15, date);
+            assertEquals(date, ps.getArguments().get(14));
+
+            LocalTime time = LocalTime.now();
+            stmt.setObject(16, time);
+            assertEquals(time, ps.getArguments().get(15));
+
+            LocalDateTime dateTime = LocalDateTime.now();
+            stmt.setObject(17, dateTime);
+            assertEquals(dateTime, ps.getArguments().get(16));
+
+            Instant instant = Instant.now();
+            stmt.setObject(18, instant);
+            assertEquals(instant, ps.getArguments().get(17));
+
+            // Unsupported
+            expectError(
+                    () -> stmt.setObject(1, new Abc()),
+                    "Parameter type is not supported: " + Abc.class.getName()
+            );
+        }
+    }
+
+    private static final class Abc {
+
+    }
+
+    @Test
+    @Override
+    public void close() throws SQLException {
+        try (PreparedStatement stmt = createStatement()) {
+            assertFalse(stmt.isClosed());
+
+            stmt.close();
+            assertTrue(stmt.isClosed());
+
+            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);
+
+            JdbcStatement2 stmt2 = (JdbcStatement2) stmt;
+            expectClosed(() -> stmt2.setQueryTimeout(-1));
+            expectClosed(() -> stmt2.setQueryTimeout(0));
+            expectClosed(() -> stmt2.setQueryTimeout(1));
+
+            expectClosed(stmt::cancel);
+
+            expectClosed(stmt::getWarnings);
+            expectClosed(stmt::clearWarnings);
+
+            expectClosed(() -> stmt.setCursorName("C"));
+            expectClosed(() -> stmt.setCursorName(null));
+
+            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::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::getResultSetHoldability);
+
+            expectClosed(() -> stmt.setPoolable(true));
+            expectClosed(() -> stmt.setPoolable(false));
+            expectClosed(stmt::isPoolable);
+
+            expectClosed(stmt::closeOnCompletion);
+            expectClosed(stmt::isCloseOnCompletion);
+
+            // PreparedStatement specific methods
+
+            expectClosed(stmt::executeQuery);
+            expectClosed(stmt::executeUpdate);
+
+            expectClosed(() -> stmt.setNull(1, 
JDBCType.INTEGER.getVendorTypeNumber()));
+            expectClosed(() -> stmt.setNull(1, 
JDBCType.INTEGER.getVendorTypeNumber(), "TypeName"));
+            expectClosed(() -> stmt.setBoolean(1, true));
+            expectClosed(() -> stmt.setByte(1, (byte) 1));
+            expectClosed(() -> stmt.setShort(1, (short) 1));
+            expectClosed(() -> stmt.setInt(1, 1));
+            expectClosed(() -> stmt.setLong(1, 1));
+            expectClosed(() -> stmt.setFloat(1, 1));
+            expectClosed(() -> stmt.setDouble(1, 1));
+            expectClosed(() -> stmt.setBigDecimal(1, BigDecimal.ZERO));
+            expectClosed(() -> stmt.setString(1, "1"));
+            expectClosed(() -> stmt.setBytes(1, new byte[]{1}));
+
+            expectClosed(() -> stmt.setDate(1, Date.valueOf(LocalDate.now())));
+            expectClosed(() -> stmt.setDate(1, Date.valueOf(LocalDate.now()), 
Calendar.getInstance()));
+
+            expectClosed(() -> stmt.setTime(1, Time.valueOf(LocalTime.now())));
+            expectClosed(() -> stmt.setTime(1, Time.valueOf(LocalTime.now()), 
Calendar.getInstance()));
+
+            expectClosed(() -> stmt.setTimestamp(1, 
Timestamp.valueOf(LocalDateTime.now())));
+            expectClosed(() -> stmt.setTimestamp(1, 
Timestamp.valueOf(LocalDateTime.now()), Calendar.getInstance()));
+
+            expectClosed(() -> stmt.setAsciiStream(1, 
InputStream.nullInputStream()));
+            expectClosed(() -> stmt.setAsciiStream(1, 
InputStream.nullInputStream(), 1));
+            expectClosed(() -> stmt.setAsciiStream(1, 
InputStream.nullInputStream(), 1L));
+
+            //noinspection deprecation
+            expectClosed(() -> stmt.setUnicodeStream(1, 
InputStream.nullInputStream(), 1));
+
+            expectClosed(() -> stmt.setBinaryStream(1, 
InputStream.nullInputStream()));
+            expectClosed(() -> stmt.setBinaryStream(1, 
InputStream.nullInputStream(), 10L));
+            expectClosed(() -> stmt.setBinaryStream(1, 
InputStream.nullInputStream(), 10));
+
+            expectClosed(stmt::clearParameters);
+            expectClosed(() -> stmt.setObject(1, 1));
+            // not supported
+            expectClosed(() -> stmt.setObject(1, 1, 
JDBCType.INTEGER.getVendorTypeNumber()));
+            // // not supported
+            expectClosed(() -> stmt.setObject(1, 1, JDBCType.INTEGER));
+            // not supported
+            expectClosed(() -> stmt.setObject(1, 1, 
JDBCType.VARCHAR.getVendorTypeNumber(), 10));
+            // not supported
+            expectClosed(() -> stmt.setObject(1, 1, JDBCType.VARCHAR, 10));
+
+            expectClosed(() -> stmt.setURL(1, new URL("https://test.com";)));
+
+            expectClosed(() -> stmt.setCharacterStream(1, new 
StringReader("1")));
+            expectClosed(() -> stmt.setCharacterStream(1, new 
StringReader("1"), 10));
+            expectClosed(() -> stmt.setCharacterStream(1, new 
StringReader("1"), 10L));
+
+            expectClosed(() -> stmt.setRef(1, Mockito.mock(Ref.class)));
+
+            expectClosed(() -> stmt.setBlob(1, Mockito.mock(Blob.class)));
+            expectClosed(() -> stmt.setBlob(1, InputStream.nullInputStream()));
+            expectClosed(() -> stmt.setBlob(1, InputStream.nullInputStream(), 
10));
+
+            expectClosed(() -> stmt.setClob(1, Mockito.mock(Clob.class)));
+            expectClosed(() -> stmt.setClob(1, new StringReader("1")));
+            expectClosed(() -> stmt.setClob(1, new StringReader("1"), 10));
+
+            expectClosed(() -> stmt.setArray(1, Mockito.mock(Array.class)));
+
+            expectClosed(stmt::getMetaData);
+            expectClosed(stmt::getParameterMetaData);
+
+            expectClosed(() -> stmt.setRowId(1, Mockito.mock(RowId.class)));
+
+            expectClosed(() -> stmt.setNCharacterStream(1, new 
StringReader("1")));
+            expectClosed(() -> stmt.setNCharacterStream(1, new 
StringReader("1"), 10));
+
+            expectClosed(() -> stmt.setNClob(1, new StringReader("1")));
+            expectClosed(() -> stmt.setNClob(1, new StringReader("1"), 10));
+            expectClosed(() -> stmt.setNClob(1, Mockito.mock(NClob.class)));
+
+            expectClosed(() -> stmt.setSQLXML(1, Mockito.mock(SQLXML.class)));
+
+            expectClosed(stmt::executeLargeUpdate);
+            expectClosed(stmt::executeLargeBatch);
+        }
+    }
+
+    @Test
+    public void executeSql() throws SQLException {
+        try (Statement stmt = createStatement()) {
+            expectError(() -> stmt.execute("UPDATE t SET c = 1"),
+                    "The method 'execute(String)' is called on 
PreparedStatement instance.");
+
+            expectError(() -> stmt.execute("UPDATE t SET c = 1", 
Statement.RETURN_GENERATED_KEYS),
+                    "The method 'execute(String, int)' is called on 
PreparedStatement instance.");
+
+            expectError(() -> stmt.execute("UPDATE t SET c = 1", new int[]{1}),
+                    "The method 'execute(String, int[])' is called on 
PreparedStatement instance.");
+
+            expectError(() -> stmt.execute("UPDATE t SET c = 1", new 
String[]{"id"}),
+                    "The method 'execute(String, String[]) is called on 
PreparedStatement instance.");
+        }
+    }
+
+    @Test
+    @Override
+    public void updateWithColumns() throws SQLException {
+        try (Statement stmt = createStatement()) {
+            expectError(() -> stmt.executeUpdate("UPDATE t SET c = 1"),
+                    "The method 'executeUpdate(String)' is called on 
PreparedStatement instance.");
+
+            expectError(() -> stmt.executeUpdate("UPDATE t SET c = 1", 
Statement.RETURN_GENERATED_KEYS),
+                    "The method 'executeUpdate(String, int)' is called on 
PreparedStatement instance.");
+
+            expectError(() -> stmt.executeUpdate("UPDATE t SET c = 1", new 
int[]{1}),
+                    "The method 'executeUpdate(String, int[])' is called on 
PreparedStatement instance.");
+
+            expectError(() -> stmt.executeUpdate("UPDATE t SET c = 1", new 
String[]{"id"}),
+                    "The method 'executeUpdate(String, String[])' is called on 
PreparedStatement instance.");
+        }
+    }
+
+    @Test
+    public void notSupportedMethods() throws SQLException {
+        try (PreparedStatement stmt = createStatement()) {
+
+            String conversionError = "Conversion to target sql type is not 
supported.";
+            expectError(() -> stmt.setObject(1, 1, 
JDBCType.INTEGER.getVendorTypeNumber()), conversionError);
+            expectError(() -> stmt.setObject(1, 1, JDBCType.INTEGER), 
conversionError);
+            expectError(() -> stmt.setObject(1, 1, 
JDBCType.VARCHAR.getVendorTypeNumber(), 10), conversionError);
+            expectError(() -> stmt.setObject(1, 1, JDBCType.VARCHAR, 10), 
conversionError);
+
+            String sqlTypeError = "SQL-specific types are not supported.";
+            expectError(() -> stmt.setURL(1, new URL("https://test.com";)), 
sqlTypeError);
+
+            expectError(() -> stmt.setRef(1, Mockito.mock(Ref.class)), 
sqlTypeError);
+
+            expectError(() -> stmt.setBlob(1, Mockito.mock(Blob.class)), 
sqlTypeError);
+            expectError(() -> stmt.setBlob(1, InputStream.nullInputStream()), 
sqlTypeError);
+            expectError(() -> stmt.setBlob(1, InputStream.nullInputStream(), 
10), sqlTypeError);
+
+            expectError(() -> stmt.setClob(1, Mockito.mock(Clob.class)), 
sqlTypeError);
+            expectError(() -> stmt.setClob(1, new StringReader("1")), 
sqlTypeError);
+            expectError(() -> stmt.setClob(1, new StringReader("1"), 10), 
sqlTypeError);
+
+            expectError(() -> stmt.setArray(1, Mockito.mock(Array.class)), 
sqlTypeError);
+
+            expectError(() -> stmt.setRowId(1, Mockito.mock(RowId.class)), 
sqlTypeError);
+
+            expectError(() -> stmt.setNClob(1, new StringReader("1")), 
sqlTypeError);
+            expectError(() -> stmt.setNClob(1, new StringReader("1"), 10), 
sqlTypeError);
+            expectError(() -> stmt.setNClob(1, Mockito.mock(NClob.class)), 
sqlTypeError);
+
+            expectError(() -> stmt.setSQLXML(1, Mockito.mock(SQLXML.class)), 
sqlTypeError);
+
+            String streamTypeError = "Streams are not supported";
+            expectError(() -> stmt.setCharacterStream(1, new 
StringReader("1")), sqlTypeError);
+            expectError(() -> stmt.setCharacterStream(1, new 
StringReader("1"), 10), sqlTypeError);
+            expectError(() -> stmt.setCharacterStream(1, new 
StringReader("1"), 10L), sqlTypeError);
+
+            expectError(() -> stmt.setNCharacterStream(1, new 
StringReader("1")), streamTypeError);
+            expectError(() -> stmt.setNCharacterStream(1, new 
StringReader("1"), 10), streamTypeError);
+            expectError(() -> stmt.setNCharacterStream(1, new 
StringReader("1"), 10L), streamTypeError);
+
+            expectError(() -> stmt.setAsciiStream(1, 
InputStream.nullInputStream()), streamTypeError);
+            expectError(() -> stmt.setAsciiStream(1, 
InputStream.nullInputStream(), 10), streamTypeError);
+            expectError(() -> stmt.setAsciiStream(1, 
InputStream.nullInputStream(), 10L), streamTypeError);
+
+            expectError(() -> stmt.setBinaryStream(1, 
InputStream.nullInputStream()), streamTypeError);
+            expectError(() -> stmt.setBinaryStream(1, 
InputStream.nullInputStream(), 10), streamTypeError);
+            expectError(() -> stmt.setBinaryStream(1, 
InputStream.nullInputStream(), 10L), streamTypeError);
+
+            //noinspection deprecation
+            expectError(() -> stmt.setUnicodeStream(1, 
InputStream.nullInputStream(), 10), streamTypeError);
+
+            expectError(stmt::getMetaData, "ResultSet metadata for prepared 
statement is not supported.");
+            expectError(stmt::getParameterMetaData, "Parameter metadata is not 
supported.");
+
+            expectError(stmt::executeLargeUpdate, "executeLargeUpdate not 
implemented.");
+            expectError(stmt::executeLargeBatch, "executeLargeBatch not 
implemented.");
+        }
+    }
+
+    @Test
+    @Override
+    public void largeUpdateMethods() throws SQLException {
+        try (Statement stmt = createStatement()) {
+            expectError(() -> stmt.executeLargeUpdate("UPDATE t SET val=2"),
+                    "The method 'executeLargeUpdate(String)' is called on 
PreparedStatement instance.");
+
+            expectError(() -> stmt.executeLargeUpdate("UPDATE t SET val=2", 
Statement.RETURN_GENERATED_KEYS),
+                    "The method 'executeLargeUpdate(String, int)' is called on 
PreparedStatement instance.");
+
+            expectError(() -> stmt.executeLargeUpdate("UPDATE t SET val=2", 
new int[]{0}),
+                    "The method 'executeLargeUpdate(String, int[])' is called 
on PreparedStatement instance.");
+
+            expectError(() -> stmt.executeLargeUpdate("UPDATE t SET val=2", 
new String[]{"C1"}),
+                    "The method 'executeLargeUpdate(String, String[])' is 
called on PreparedStatement instance.");
+        }
+    }
+
+    private static void expectError(Executable executable, String message) {
+        assertThrowsSqlException(SQLException.class, message, executable);
+    }
+
+    @Override
+    protected PreparedStatement 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);
+    }
+
+    @Override
+    protected PreparedStatement createStatement(Connection connection) {
+        IgniteSql igniteSql = Mockito.mock(IgniteSql.class);
+        return new JdbcPreparedStatement2(connection, igniteSql, "PUBLIC", 
ResultSet.HOLD_CURSORS_OVER_COMMIT, "SELECT 1");
+    }
+}
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
index 086dd9843ac..0f1f8b87070 100644
--- 
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
@@ -434,7 +434,7 @@ public class JdbcStatement2SelfTest extends 
BaseIgniteAbstractTest {
         }
     }
 
-    private static Statement createStatement() throws SQLException {
+    protected Statement createStatement() throws SQLException {
         Connection connection = Mockito.mock(Connection.class);
         JdbcConnection2 connection2 = Mockito.mock(JdbcConnection2.class);
 
@@ -446,12 +446,12 @@ public class JdbcStatement2SelfTest extends 
BaseIgniteAbstractTest {
         return createStatement(connection);
     }
 
-    private static Statement createStatement(Connection connection) {
+    protected 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) {
+    static void expectClosed(Executable method) {
         assertThrowsSqlException(SQLException.class, "Statement is closed.", 
method);
     }
 }

Reply via email to