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

korlov pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/ignite-3.git


The following commit(s) were added to refs/heads/main by this push:
     new 1b0e43ea09 IGNITE-22413 Jdbc. Reduce amount of roundtrips on statement 
execution (#3880)
1b0e43ea09 is described below

commit 1b0e43ea09063fc83b716fa2411f509a6d040a08
Author: korlov42 <[email protected]>
AuthorDate: Fri Jun 7 13:10:41 2024 +0300

    IGNITE-22413 Jdbc. Reduce amount of roundtrips on statement execution 
(#3880)
---
 .../jdbc/proto/event/JdbcBatchExecuteResult.java   |   2 -
 .../internal/jdbc/proto/event/JdbcColumnMeta.java  |   6 +-
 .../jdbc/proto/event/JdbcConnectResult.java        |   6 +-
 .../jdbc/proto/event/JdbcMetaColumnsResult.java    |   6 +-
 .../proto/event/JdbcMetaPrimaryKeysResult.java     |   6 +-
 .../jdbc/proto/event/JdbcMetaSchemasResult.java    |   6 +-
 .../jdbc/proto/event/JdbcMetaTablesResult.java     |   6 +-
 .../jdbc/proto/event/JdbcQueryCloseResult.java     |   4 +-
 .../jdbc/proto/event/JdbcQueryFetchResult.java     |   6 +-
 .../jdbc/proto/event/JdbcQuerySingleResult.java    | 196 ++++++++++-----------
 .../internal/jdbc/proto/event/JdbcTableMeta.java   |   6 +-
 .../ignite/internal/jdbc/proto/event/Response.java |   6 +-
 .../ignite/client/handler/JdbcHandlerBase.java     |  51 ++++--
 .../client/handler/JdbcQueryCursorHandlerImpl.java |  10 +-
 .../handler/JdbcQueryCursorHandlerImplTest.java    | 139 +++++++--------
 .../handler/JdbcQueryEventHandlerImplTest.java     |   6 +-
 .../ignite/internal/jdbc/JdbcConnection.java       |   2 +-
 .../ignite/internal/jdbc/JdbcDatabaseMetadata.java |   8 +-
 .../internal/jdbc/JdbcPreparedStatement.java       |   2 +-
 .../internal/jdbc/JdbcQueryExecuteResponse.java    |   8 +-
 .../apache/ignite/internal/jdbc/JdbcResultSet.java | 147 +++++++++-------
 .../apache/ignite/internal/jdbc/JdbcStatement.java |  20 +--
 .../ignite/internal/jdbc/JdbcResultSetTest.java    |  84 +++++++--
 .../platforms/cpp/ignite/odbc/meta/table_meta.cpp  |   3 -
 .../ignite/odbc/query/column_metadata_query.cpp    |   8 +-
 .../cpp/ignite/odbc/query/primary_keys_query.cpp   |   4 +-
 .../cpp/ignite/odbc/query/table_metadata_query.cpp |   7 +-
 27 files changed, 389 insertions(+), 366 deletions(-)

diff --git 
a/modules/client-common/src/main/java/org/apache/ignite/internal/jdbc/proto/event/JdbcBatchExecuteResult.java
 
b/modules/client-common/src/main/java/org/apache/ignite/internal/jdbc/proto/event/JdbcBatchExecuteResult.java
index 19f01b4a57..107d6d856c 100644
--- 
a/modules/client-common/src/main/java/org/apache/ignite/internal/jdbc/proto/event/JdbcBatchExecuteResult.java
+++ 
b/modules/client-common/src/main/java/org/apache/ignite/internal/jdbc/proto/event/JdbcBatchExecuteResult.java
@@ -73,8 +73,6 @@ public class JdbcBatchExecuteResult extends Response {
         Objects.requireNonNull(updateCnts);
 
         this.updateCnts = updateCnts;
-
-        hasResults = true;
     }
 
     /**
diff --git 
a/modules/client-common/src/main/java/org/apache/ignite/internal/jdbc/proto/event/JdbcColumnMeta.java
 
b/modules/client-common/src/main/java/org/apache/ignite/internal/jdbc/proto/event/JdbcColumnMeta.java
index 6282793ef1..df1d31bfcf 100644
--- 
a/modules/client-common/src/main/java/org/apache/ignite/internal/jdbc/proto/event/JdbcColumnMeta.java
+++ 
b/modules/client-common/src/main/java/org/apache/ignite/internal/jdbc/proto/event/JdbcColumnMeta.java
@@ -155,8 +155,6 @@ public class JdbcColumnMeta extends Response {
         this.dataTypeCls = javaTypeName;
         this.precision = precision;
         this.scale = scale;
-
-        hasResults = true;
     }
 
     /**
@@ -263,7 +261,7 @@ public class JdbcColumnMeta extends Response {
     public void writeBinary(ClientMessagePacker packer) {
         super.writeBinary(packer);
 
-        if (!hasResults) {
+        if (!success()) {
             return;
         }
 
@@ -285,7 +283,7 @@ public class JdbcColumnMeta extends Response {
     public void readBinary(ClientMessageUnpacker unpacker) {
         super.readBinary(unpacker);
 
-        if (!hasResults) {
+        if (!success()) {
             return;
         }
 
diff --git 
a/modules/client-common/src/main/java/org/apache/ignite/internal/jdbc/proto/event/JdbcConnectResult.java
 
b/modules/client-common/src/main/java/org/apache/ignite/internal/jdbc/proto/event/JdbcConnectResult.java
index 71d0d3610b..0e2fbc4fcb 100644
--- 
a/modules/client-common/src/main/java/org/apache/ignite/internal/jdbc/proto/event/JdbcConnectResult.java
+++ 
b/modules/client-common/src/main/java/org/apache/ignite/internal/jdbc/proto/event/JdbcConnectResult.java
@@ -50,8 +50,6 @@ public class JdbcConnectResult extends Response {
      */
     public JdbcConnectResult(long connectionId) {
         this.connectionId = connectionId;
-
-        this.hasResults = true;
     }
 
     /** Returns an identifier of the connection. */
@@ -64,7 +62,7 @@ public class JdbcConnectResult extends Response {
     public void writeBinary(ClientMessagePacker packer) {
         super.writeBinary(packer);
 
-        if (!hasResults) {
+        if (!success()) {
             return;
         }
 
@@ -76,7 +74,7 @@ public class JdbcConnectResult extends Response {
     public void readBinary(ClientMessageUnpacker unpacker) {
         super.readBinary(unpacker);
 
-        if (!hasResults) {
+        if (!success()) {
             return;
         }
 
diff --git 
a/modules/client-common/src/main/java/org/apache/ignite/internal/jdbc/proto/event/JdbcMetaColumnsResult.java
 
b/modules/client-common/src/main/java/org/apache/ignite/internal/jdbc/proto/event/JdbcMetaColumnsResult.java
index 48924ca59c..c22821b925 100644
--- 
a/modules/client-common/src/main/java/org/apache/ignite/internal/jdbc/proto/event/JdbcMetaColumnsResult.java
+++ 
b/modules/client-common/src/main/java/org/apache/ignite/internal/jdbc/proto/event/JdbcMetaColumnsResult.java
@@ -58,8 +58,6 @@ public class JdbcMetaColumnsResult extends Response {
         Objects.requireNonNull(meta);
 
         this.meta = new ArrayList<>(meta);
-
-        this.hasResults = true;
     }
 
     /**
@@ -76,7 +74,7 @@ public class JdbcMetaColumnsResult extends Response {
     public void writeBinary(ClientMessagePacker packer) {
         super.writeBinary(packer);
 
-        if (!hasResults) {
+        if (!success()) {
             return;
         }
 
@@ -92,7 +90,7 @@ public class JdbcMetaColumnsResult extends Response {
     public void readBinary(ClientMessageUnpacker unpacker) {
         super.readBinary(unpacker);
 
-        if (!hasResults) {
+        if (!success()) {
             return;
         }
 
diff --git 
a/modules/client-common/src/main/java/org/apache/ignite/internal/jdbc/proto/event/JdbcMetaPrimaryKeysResult.java
 
b/modules/client-common/src/main/java/org/apache/ignite/internal/jdbc/proto/event/JdbcMetaPrimaryKeysResult.java
index b42d0d2a56..810720e298 100644
--- 
a/modules/client-common/src/main/java/org/apache/ignite/internal/jdbc/proto/event/JdbcMetaPrimaryKeysResult.java
+++ 
b/modules/client-common/src/main/java/org/apache/ignite/internal/jdbc/proto/event/JdbcMetaPrimaryKeysResult.java
@@ -48,8 +48,6 @@ public class JdbcMetaPrimaryKeysResult extends Response {
         Objects.requireNonNull(meta);
 
         this.meta = new ArrayList<>(meta);
-
-        this.hasResults = true;
     }
 
     /** {@inheritDoc} */
@@ -57,7 +55,7 @@ public class JdbcMetaPrimaryKeysResult extends Response {
     public void writeBinary(ClientMessagePacker packer) {
         super.writeBinary(packer);
 
-        if (!hasResults) {
+        if (!success()) {
             return;
         }
 
@@ -79,7 +77,7 @@ public class JdbcMetaPrimaryKeysResult extends Response {
     public void readBinary(ClientMessageUnpacker unpacker) {
         super.readBinary(unpacker);
 
-        if (!hasResults) {
+        if (!success()) {
             return;
         }
 
diff --git 
a/modules/client-common/src/main/java/org/apache/ignite/internal/jdbc/proto/event/JdbcMetaSchemasResult.java
 
b/modules/client-common/src/main/java/org/apache/ignite/internal/jdbc/proto/event/JdbcMetaSchemasResult.java
index eb00de8eef..92edbe56f0 100644
--- 
a/modules/client-common/src/main/java/org/apache/ignite/internal/jdbc/proto/event/JdbcMetaSchemasResult.java
+++ 
b/modules/client-common/src/main/java/org/apache/ignite/internal/jdbc/proto/event/JdbcMetaSchemasResult.java
@@ -46,8 +46,6 @@ public class JdbcMetaSchemasResult extends Response {
         Objects.requireNonNull(schemas);
 
         this.schemas = schemas;
-
-        this.hasResults = true;
     }
 
     /** {@inheritDoc} */
@@ -55,7 +53,7 @@ public class JdbcMetaSchemasResult extends Response {
     public void writeBinary(ClientMessagePacker packer) {
         super.writeBinary(packer);
 
-        if (!hasResults) {
+        if (!success()) {
             return;
         }
 
@@ -71,7 +69,7 @@ public class JdbcMetaSchemasResult extends Response {
     public void readBinary(ClientMessageUnpacker unpacker) {
         super.readBinary(unpacker);
 
-        if (!hasResults) {
+        if (!success()) {
             return;
         }
 
diff --git 
a/modules/client-common/src/main/java/org/apache/ignite/internal/jdbc/proto/event/JdbcMetaTablesResult.java
 
b/modules/client-common/src/main/java/org/apache/ignite/internal/jdbc/proto/event/JdbcMetaTablesResult.java
index 23454b1541..d33f534f79 100644
--- 
a/modules/client-common/src/main/java/org/apache/ignite/internal/jdbc/proto/event/JdbcMetaTablesResult.java
+++ 
b/modules/client-common/src/main/java/org/apache/ignite/internal/jdbc/proto/event/JdbcMetaTablesResult.java
@@ -46,8 +46,6 @@ public class JdbcMetaTablesResult extends Response {
         Objects.requireNonNull(meta);
 
         this.meta = meta;
-
-        this.hasResults = true;
     }
 
     /** {@inheritDoc} */
@@ -55,7 +53,7 @@ public class JdbcMetaTablesResult extends Response {
     public void writeBinary(ClientMessagePacker packer) {
         super.writeBinary(packer);
 
-        if (!hasResults) {
+        if (!success()) {
             return;
         }
 
@@ -71,7 +69,7 @@ public class JdbcMetaTablesResult extends Response {
     public void readBinary(ClientMessageUnpacker unpacker) {
         super.readBinary(unpacker);
 
-        if (!hasResults) {
+        if (!success()) {
             return;
         }
 
diff --git 
a/modules/client-common/src/main/java/org/apache/ignite/internal/jdbc/proto/event/JdbcQueryCloseResult.java
 
b/modules/client-common/src/main/java/org/apache/ignite/internal/jdbc/proto/event/JdbcQueryCloseResult.java
index e1239bae47..76a4580820 100644
--- 
a/modules/client-common/src/main/java/org/apache/ignite/internal/jdbc/proto/event/JdbcQueryCloseResult.java
+++ 
b/modules/client-common/src/main/java/org/apache/ignite/internal/jdbc/proto/event/JdbcQueryCloseResult.java
@@ -26,9 +26,7 @@ public class JdbcQueryCloseResult extends Response {
     /**
      * Default constructor is used for deserialization.
      */
-    public JdbcQueryCloseResult() {
-        hasResults = true;
-    }
+    public JdbcQueryCloseResult() { }
 
     /**
      * Constructor.
diff --git 
a/modules/client-common/src/main/java/org/apache/ignite/internal/jdbc/proto/event/JdbcQueryFetchResult.java
 
b/modules/client-common/src/main/java/org/apache/ignite/internal/jdbc/proto/event/JdbcQueryFetchResult.java
index dca2f1e2e1..6b56868f56 100644
--- 
a/modules/client-common/src/main/java/org/apache/ignite/internal/jdbc/proto/event/JdbcQueryFetchResult.java
+++ 
b/modules/client-common/src/main/java/org/apache/ignite/internal/jdbc/proto/event/JdbcQueryFetchResult.java
@@ -64,8 +64,6 @@ public class JdbcQueryFetchResult extends Response {
 
         this.rowTuples = rowTuples;
         this.last = last;
-
-        hasResults = true;
     }
 
     /**
@@ -91,7 +89,7 @@ public class JdbcQueryFetchResult extends Response {
     public void writeBinary(ClientMessagePacker packer) {
         super.writeBinary(packer);
 
-        if (!hasResults) {
+        if (!success()) {
             return;
         }
 
@@ -109,7 +107,7 @@ public class JdbcQueryFetchResult extends Response {
     public void readBinary(ClientMessageUnpacker unpacker) {
         super.readBinary(unpacker);
 
-        if (!hasResults) {
+        if (!success()) {
             return;
         }
 
diff --git 
a/modules/client-common/src/main/java/org/apache/ignite/internal/jdbc/proto/event/JdbcQuerySingleResult.java
 
b/modules/client-common/src/main/java/org/apache/ignite/internal/jdbc/proto/event/JdbcQuerySingleResult.java
index eae6474c4d..e1701c080b 100644
--- 
a/modules/client-common/src/main/java/org/apache/ignite/internal/jdbc/proto/event/JdbcQuerySingleResult.java
+++ 
b/modules/client-common/src/main/java/org/apache/ignite/internal/jdbc/proto/event/JdbcQuerySingleResult.java
@@ -25,41 +25,45 @@ import 
org.apache.ignite.internal.client.proto.ClientMessagePacker;
 import org.apache.ignite.internal.client.proto.ClientMessageUnpacker;
 import org.apache.ignite.internal.tostring.S;
 import org.apache.ignite.sql.ColumnType;
+import org.jetbrains.annotations.Nullable;
 
 /**
  * JDBC query execute result.
  */
 public class JdbcQuerySingleResult extends Response {
-    /** Cursor ID. */
-    private Long cursorId;
+    // === Common attributes ===
 
-    /** Serialized query result rows. */
-    private List<BinaryTupleReader> rowTuples;
+    /** Id of the cursor in case it was registered on server. */
+    private @Nullable Long cursorId;
 
-    /** Flag indicating the query has no unfetched results. */
-    private boolean last;
+    private boolean hasResultSet;
 
-    /** Flag indicating the query is SELECT/EXPLAIN query. {@code false} for 
DML/DDL/TX queries. */
-    private boolean isQuery;
+    /** Result is part of multi-statement query, there is at least one more 
result. */
+    private boolean hasNextResult;
 
-    /** Update count. */
-    private long updateCnt;
+    // === Attributes of response with result set ===
 
-    /** Ordered list of types of columns in serialized rows. */
-    private List<ColumnType> columnTypes;
+    /** Serialized query result rows. Null only when result has no resultSet. 
*/
+    private @Nullable List<BinaryTupleReader> rowTuples;
 
-    /** Decimal scales in appearance order. Can be empty in case no any 
decimal columns. */
-    private int[] decimalScales;
+    /** Flag indicating the query has un-fetched results. */
+    private boolean hasMoreData;
+
+    /** Ordered list of types of columns in serialized rows. Null only when 
result has no resultSet. */
+    private @Nullable List<ColumnType> columnTypes;
+
+    /** Decimal scales in appearance order. Can be empty in case no any 
decimal columns, or null when result has no resultSet. */
+    private int @Nullable [] decimalScales;
+
+    // === Attributes of response without result set ===
+
+    private long updateCnt = -1;
 
-    /** {@code true} if results are available, {@code false} otherwise. */
-    private boolean resultsAvailable;
 
     /**
      * Constructor.
      */
-    public JdbcQuerySingleResult() {
-        resultsAvailable = false;
-    }
+    public JdbcQuerySingleResult() { }
 
     /**
      * Constructor.
@@ -69,23 +73,27 @@ public class JdbcQuerySingleResult extends Response {
      */
     public JdbcQuerySingleResult(int status, String err) {
         super(status, err);
-
-        resultsAvailable = false;
     }
 
     /**
      * Constructor.
      *
-     * @param cursorId Cursor ID.
+     * @param cursorId Id of the cursor in case it was registered on server.
      * @param rowTuples Serialized SQL result rows.
      * @param columnTypes Ordered list of types of columns in serialized rows.
      * @param decimalScales Decimal scales in appearance order.
-     * @param last     Flag indicates the query has no unfetched results.
+     * @param hasMoreData Flag indicates the query has un-fetched results.
+     * @param hasNextResult Flag indicates that current result is part of 
multi-statement query, there is at least one more result.
      */
-    public JdbcQuerySingleResult(long cursorId, List<BinaryTupleReader> 
rowTuples, List<ColumnType> columnTypes, int[] decimalScales,
-            boolean last) {
-        super();
-
+    @SuppressWarnings("NullableProblems")
+    public JdbcQuerySingleResult(
+            @Nullable Long cursorId,
+            List<BinaryTupleReader> rowTuples,
+            List<ColumnType> columnTypes,
+            int[] decimalScales,
+            boolean hasMoreData,
+            boolean hasNextResult
+    ) {
         Objects.requireNonNull(rowTuples);
 
         this.cursorId = cursorId;
@@ -93,11 +101,10 @@ public class JdbcQuerySingleResult extends Response {
         this.columnTypes = columnTypes;
         this.decimalScales = decimalScales;
 
-        this.last = last;
-        this.isQuery = true;
+        this.hasMoreData = hasMoreData;
+        this.hasNextResult = hasNextResult;
 
-        hasResults = true;
-        resultsAvailable = true;
+        hasResultSet = true;
 
         assert decimalScales != null;
     }
@@ -105,77 +112,50 @@ public class JdbcQuerySingleResult extends Response {
     /**
      * Constructor.
      *
+     * @param cursorId Id of the cursor in case it was registered on server.
      * @param updateCnt Update count for DML queries.
+     * @param hasNextResult Flag indicates that current result is part of 
multi-statement query, there is at least one more result.
      */
-    public JdbcQuerySingleResult(long cursorId, long updateCnt) {
-        super();
-
+    public JdbcQuerySingleResult(@Nullable Long cursorId, long updateCnt, 
boolean hasNextResult) {
         this.updateCnt = updateCnt;
         this.cursorId = cursorId;
-
-        hasResults = false;
-        resultsAvailable = true;
+        this.hasNextResult = hasNextResult;
     }
 
-    /**
-     * Get the cursor id.
-     *
-     * @return Cursor ID.
-     */
-    public Long cursorId() {
+    /** Return id of the cursor in case it was registered on server, returns 
null otherwise. */
+    public @Nullable Long cursorId() {
         return cursorId;
     }
 
-    /**
-     * Get the items.
-     *
-     * @return Serialized query result rows.
-     */
-    public List<BinaryTupleReader> items() {
+    /** Return result rows in serialized form, return null if result has no 
result set. */
+    public @Nullable List<BinaryTupleReader> items() {
         return rowTuples;
     }
 
-    /**
-     * Types of columns in serialized rows.
-     *
-     * @return Ordered list of types of columns in serialized rows.
-     */
-    public List<ColumnType> columnTypes() {
+    /** Return types of columns in serialized rows if result has result set, 
return null otherwise. */
+    public @Nullable List<ColumnType> columnTypes() {
         return columnTypes;
     }
 
-    /**
-     * Decimal scales.
-     *
-     * @return Decimal scales in appearance order in columns. Can be empty in 
case no any decimal columns.
-     */
-    public int[] decimalScales() {
+    /** Return decimal scales in appearance order in columns if result has 
result set, return null otherwise. */
+    public int @Nullable [] decimalScales() {
         return decimalScales;
     }
 
-    /**
-     * Get the last flag.
-     *
-     * @return Flag indicating the query has no unfetched results.
-     */
-    public boolean last() {
-        return last;
+    /** Returns {@code true} if there is more data available in current result 
set. */
+    @SuppressWarnings("BooleanMethodIsAlwaysInverted")
+    public boolean hasMoreData() {
+        return hasMoreData;
     }
 
-    /**
-     * Get the isQuery flag.
-     *
-     * @return Flag indicating the query is SELECT query. {@code false} for 
DML/DDL queries.
-     */
-    public boolean isQuery() {
-        return isQuery;
+    /** Returns {@code true} if result contains rows. */
+    public boolean hasResultSet() {
+        return hasResultSet;
     }
 
-    /** Results availability flag.
-     * If no more results available, returns {@code false}
-     */
-    public boolean resultAvailable() {
-        return resultsAvailable;
+    /** Returns {@code true} if result is part of multi-statement query and 
there is at least one more result. */
+    public boolean hasNextResult() {
+        return hasNextResult;
     }
 
     /**
@@ -192,29 +172,30 @@ public class JdbcQuerySingleResult extends Response {
     public void writeBinary(ClientMessagePacker packer) {
         super.writeBinary(packer);
 
-        packer.packBoolean(resultsAvailable);
-        if (resultsAvailable) {
-            packer.packLong(updateCnt);
-
-            if (cursorId != null) {
-                packer.packLong(cursorId);
-            } else {
-                packer.packNil();
-            }
+        if (!success()) {
+            return;
         }
 
-        if (!hasResults) {
+        packer.packLongNullable(cursorId);
+        packer.packBoolean(hasResultSet);
+        packer.packBoolean(hasNextResult);
+
+        if (!hasResultSet) {
+            packer.packLong(updateCnt);
+
             return;
         }
 
-        packer.packBoolean(isQuery);
-        packer.packBoolean(last);
+        assert decimalScales != null;
+        assert columnTypes != null;
+        assert rowTuples != null;
 
+        packer.packBoolean(hasMoreData);
         packer.packIntArray(decimalScales);
 
         packer.packInt(this.columnTypes.size());
-        for (int i = 0; i < this.columnTypes.size(); i++) {
-            packer.packInt(this.columnTypes.get(i).id());
+        for (ColumnType columnType : this.columnTypes) {
+            packer.packInt(columnType.id());
         }
 
         packer.packInt(rowTuples.size());
@@ -228,24 +209,27 @@ public class JdbcQuerySingleResult extends Response {
     @Override
     public void readBinary(ClientMessageUnpacker unpacker) {
         super.readBinary(unpacker);
-        resultsAvailable = unpacker.unpackBoolean();
-        if (resultsAvailable) {
-            updateCnt = unpacker.unpackLong();
 
-            if (unpacker.tryUnpackNil()) {
-                cursorId = null;
-            } else {
-                cursorId = unpacker.unpackLong();
-            }
+        if (!success()) {
+            return;
         }
 
-        if (!hasResults) {
-            return;
+        if (unpacker.tryUnpackNil()) {
+            cursorId = null;
+        } else {
+            cursorId = unpacker.unpackLong();
         }
 
-        isQuery = unpacker.unpackBoolean();
-        last = unpacker.unpackBoolean();
+        hasResultSet = unpacker.unpackBoolean();
+        hasNextResult = unpacker.unpackBoolean();
+
+        if (!hasResultSet) {
+            updateCnt = unpacker.unpackLong();
+
+            return;
+        }
 
+        hasMoreData = unpacker.unpackBoolean();
         decimalScales = unpacker.unpackIntArray();
 
         int count = unpacker.unpackInt();
diff --git 
a/modules/client-common/src/main/java/org/apache/ignite/internal/jdbc/proto/event/JdbcTableMeta.java
 
b/modules/client-common/src/main/java/org/apache/ignite/internal/jdbc/proto/event/JdbcTableMeta.java
index 9e5166bb8a..f27305dafe 100644
--- 
a/modules/client-common/src/main/java/org/apache/ignite/internal/jdbc/proto/event/JdbcTableMeta.java
+++ 
b/modules/client-common/src/main/java/org/apache/ignite/internal/jdbc/proto/event/JdbcTableMeta.java
@@ -52,8 +52,6 @@ public class JdbcTableMeta extends Response {
         this.schemaName = schemaName;
         this.tblName = tblName;
         this.tblType = tblType;
-
-        this.hasResults = true;
     }
 
     /**
@@ -88,7 +86,7 @@ public class JdbcTableMeta extends Response {
     public void writeBinary(ClientMessagePacker packer) {
         super.writeBinary(packer);
 
-        if (!hasResults) {
+        if (!success()) {
             return;
         }
 
@@ -102,7 +100,7 @@ public class JdbcTableMeta extends Response {
     public void readBinary(ClientMessageUnpacker unpacker) {
         super.readBinary(unpacker);
 
-        if (!hasResults) {
+        if (!success()) {
             return;
         }
 
diff --git 
a/modules/client-common/src/main/java/org/apache/ignite/internal/jdbc/proto/event/Response.java
 
b/modules/client-common/src/main/java/org/apache/ignite/internal/jdbc/proto/event/Response.java
index bbb76085eb..9e625e2827 100644
--- 
a/modules/client-common/src/main/java/org/apache/ignite/internal/jdbc/proto/event/Response.java
+++ 
b/modules/client-common/src/main/java/org/apache/ignite/internal/jdbc/proto/event/Response.java
@@ -65,7 +65,6 @@ public abstract class Response implements ClientMessage {
     /** {@inheritDoc} */
     @Override
     public void writeBinary(ClientMessagePacker packer) {
-        packer.packBoolean(hasResults);
         packer.packInt(status);
 
         if (StringUtil.isNullOrEmpty(err)) {
@@ -78,7 +77,6 @@ public abstract class Response implements ClientMessage {
     /** {@inheritDoc} */
     @Override
     public void readBinary(ClientMessageUnpacker unpacker) {
-        hasResults = unpacker.unpackBoolean();
         status = unpacker.unpackInt();
 
         if (!unpacker.tryUnpackNil()) {
@@ -127,8 +125,8 @@ public abstract class Response implements ClientMessage {
      *
      * @return Has results.
      */
-    public boolean hasResults() {
-        return hasResults;
+    public boolean success() {
+        return status == STATUS_SUCCESS;
     }
 
     /** {@inheritDoc} */
diff --git 
a/modules/client-handler/src/main/java/org/apache/ignite/client/handler/JdbcHandlerBase.java
 
b/modules/client-handler/src/main/java/org/apache/ignite/client/handler/JdbcHandlerBase.java
index cad0258a35..42a9fa3ec5 100644
--- 
a/modules/client-handler/src/main/java/org/apache/ignite/client/handler/JdbcHandlerBase.java
+++ 
b/modules/client-handler/src/main/java/org/apache/ignite/client/handler/JdbcHandlerBase.java
@@ -63,37 +63,54 @@ abstract class JdbcHandlerBase {
      */
     CompletionStage<JdbcQuerySingleResult> 
createJdbcResult(AsyncSqlCursor<InternalSqlRow> cur, int pageSize) {
         return cur.requestNextAsync(pageSize).thenApply(batch -> {
-            boolean hasNext = batch.hasMore();
-
-            long cursorId;
-            try {
-                cursorId = resources.put(new ClientResource(cur, 
cur::closeAsync));
-            } catch (IgniteInternalCheckedException e) {
-                cur.closeAsync();
-
-                return new JdbcQuerySingleResult(Response.STATUS_FAILED,
-                        "Unable to store query cursor.");
+            Long cursorId = null;
+            if (cur.hasNextResult()) {
+                // in case of multi statement we need to save cursor in 
resources, so later we can derive it and 
+                // move to the next result
+                try {
+                    cursorId = resources.put(new ClientResource(cur, 
cur::closeAsync));
+                } catch (IgniteInternalCheckedException e) {
+                    cur.closeAsync();
+
+                    return new JdbcQuerySingleResult(Response.STATUS_FAILED,
+                            "Unable to store query cursor.");
+                }
             }
 
             switch (cur.queryType()) {
                 case EXPLAIN:
                 case QUERY: {
+                    if (cursorId == null) {
+                        // for queries with result set we still need to save 
cursor to resources, so later we can
+                        // derive result's metadata which is loaded on demand 
by driver
+                        try {
+                            cursorId = resources.put(new ClientResource(cur, 
cur::closeAsync));
+                        } catch (IgniteInternalCheckedException e) {
+                            cur.closeAsync();
+
+                            return new 
JdbcQuerySingleResult(Response.STATUS_FAILED,
+                                    "Unable to store query cursor.");
+                        }
+                    }
+
                     List<ColumnMetadata> columns = cur.metadata().columns();
 
-                    return buildSingleRequest(batch, columns, cursorId, 
!hasNext);
+                    return buildSingleRequest(batch, columns, cursorId, 
cur.hasNextResult());
                 }
                 case DML: {
-                    if (!validateDmlResult(cur.metadata(), hasNext)) {
+                    boolean hasMoreData = batch.hasMore();
+
+                    if (!validateDmlResult(cur.metadata(), hasMoreData)) {
                         return new 
JdbcQuerySingleResult(Response.STATUS_FAILED, "Unexpected result for DML 
query");
                     }
 
                     long updCount = (long) batch.items().get(0).get(0);
 
-                    return new JdbcQuerySingleResult(cursorId, updCount);
+                    return new JdbcQuerySingleResult(cursorId, updCount, 
cur.hasNextResult());
                 }
                 case DDL:
                 case TX_CONTROL:
-                    return new JdbcQuerySingleResult(cursorId, 0);
+                    return new JdbcQuerySingleResult(cursorId, 0, 
cur.hasNextResult());
                 default:
                     return new JdbcQuerySingleResult(UNSUPPORTED_OPERATION,
                             "Query type is not supported yet [queryType=" + 
cur.queryType() + ']');
@@ -104,8 +121,8 @@ abstract class JdbcHandlerBase {
     private static JdbcQuerySingleResult buildSingleRequest(
             BatchedResult<InternalSqlRow> batch,
             List<ColumnMetadata> columns,
-            long cursorId,
-            boolean hasNext
+            @Nullable Long cursorId,
+            boolean hasNextResult
     ) {
         List<BinaryTupleReader> rows = new ArrayList<>(batch.items().size());
         for (InternalSqlRow item : batch.items()) {
@@ -124,7 +141,7 @@ abstract class JdbcHandlerBase {
         }
         decimalScales = Arrays.copyOf(decimalScales, countOfDecimal);
 
-        return new JdbcQuerySingleResult(cursorId, rows, schema, 
decimalScales, hasNext);
+        return new JdbcQuerySingleResult(cursorId, rows, schema, 
decimalScales, batch.hasMore(), hasNextResult);
     }
 
     JdbcQuerySingleResult createErrorResult(String logMessage, Throwable 
origin, @Nullable String errMessagePrefix) {
diff --git 
a/modules/client-handler/src/main/java/org/apache/ignite/client/handler/JdbcQueryCursorHandlerImpl.java
 
b/modules/client-handler/src/main/java/org/apache/ignite/client/handler/JdbcQueryCursorHandlerImpl.java
index ad893bbd9d..4cc235aa76 100644
--- 
a/modules/client-handler/src/main/java/org/apache/ignite/client/handler/JdbcQueryCursorHandlerImpl.java
+++ 
b/modules/client-handler/src/main/java/org/apache/ignite/client/handler/JdbcQueryCursorHandlerImpl.java
@@ -95,7 +95,7 @@ public class JdbcQueryCursorHandlerImpl extends 
JdbcHandlerBase implements JdbcQ
     public CompletableFuture<JdbcQuerySingleResult> 
getMoreResultsAsync(JdbcFetchQueryResultsRequest req) {
         AsyncSqlCursor<InternalSqlRow> asyncSqlCursor;
         try {
-            asyncSqlCursor = 
resources.get(req.cursorId()).get(AsyncSqlCursor.class);
+            asyncSqlCursor = 
resources.remove(req.cursorId()).get(AsyncSqlCursor.class);
         } catch (IgniteInternalCheckedException e) {
             StringWriter sw = getWriterWithStackTrace(e);
 
@@ -103,11 +103,15 @@ public class JdbcQueryCursorHandlerImpl extends 
JdbcHandlerBase implements JdbcQ
                     "Failed to find query cursor [curId=" + req.cursorId() + 
"]. Error message:" + sw));
         }
 
+        CompletableFuture<Void> cursorCloseFuture = 
asyncSqlCursor.closeAsync();
+
         if (!asyncSqlCursor.hasNextResult()) {
-            return CompletableFuture.completedFuture(new 
JdbcQuerySingleResult());
+            // driver should check presence of next result set on client side 
and avoid unnecessary calls
+            return CompletableFuture.completedFuture(new 
JdbcQuerySingleResult(Response.STATUS_FAILED,
+                    "Cursor doesn't have next result"));
         }
 
-        return asyncSqlCursor.closeAsync().thenCompose(c -> 
asyncSqlCursor.nextResult())
+        return cursorCloseFuture.thenCompose(c -> asyncSqlCursor.nextResult())
                 .thenCompose(cur -> createJdbcResult(cur, req.fetchSize()))
                 .exceptionally(t -> {
                     iterateThroughResultsAndCloseThem(asyncSqlCursor);
diff --git 
a/modules/client-handler/src/test/java/org/apache/ignite/client/handler/JdbcQueryCursorHandlerImplTest.java
 
b/modules/client-handler/src/test/java/org/apache/ignite/client/handler/JdbcQueryCursorHandlerImplTest.java
index 96c225fb68..009f0643e6 100644
--- 
a/modules/client-handler/src/test/java/org/apache/ignite/client/handler/JdbcQueryCursorHandlerImplTest.java
+++ 
b/modules/client-handler/src/test/java/org/apache/ignite/client/handler/JdbcQueryCursorHandlerImplTest.java
@@ -17,35 +17,42 @@
 
 package org.apache.ignite.client.handler;
 
+import static java.util.concurrent.CompletableFuture.completedFuture;
+import static java.util.concurrent.CompletableFuture.failedFuture;
 import static org.apache.ignite.internal.testframework.IgniteTestUtils.await;
-import static 
org.apache.ignite.internal.util.CompletableFutures.nullCompletedFuture;
-import static org.apache.ignite.lang.ErrorGroups.Catalog.VALIDATION_ERR;
+import static 
org.apache.ignite.internal.testframework.matchers.CompletableFutureMatcher.willCompleteSuccessfully;
 import static org.apache.ignite.lang.ErrorGroups.Sql.STMT_PARSE_ERR;
-import static org.mockito.ArgumentMatchers.anyInt;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.mockito.ArgumentMatchers.anyLong;
-import static org.mockito.Mockito.lenient;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
+import java.util.Collections;
+import java.util.List;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.TimeUnit;
 import org.apache.ignite.internal.jdbc.proto.JdbcQueryCursorHandler;
 import 
org.apache.ignite.internal.jdbc.proto.event.JdbcFetchQueryResultsRequest;
+import org.apache.ignite.internal.jdbc.proto.event.JdbcQuerySingleResult;
 import org.apache.ignite.internal.lang.IgniteInternalCheckedException;
-import org.apache.ignite.internal.lang.IgniteInternalException;
+import org.apache.ignite.internal.sql.ResultSetMetadataImpl;
 import org.apache.ignite.internal.sql.engine.AsyncSqlCursor;
 import org.apache.ignite.internal.sql.engine.AsyncSqlCursorImpl;
 import org.apache.ignite.internal.sql.engine.InternalSqlRow;
 import org.apache.ignite.internal.sql.engine.SqlQueryType;
+import org.apache.ignite.internal.sql.engine.util.IteratorToDataCursorAdapter;
 import org.apache.ignite.internal.testframework.BaseIgniteAbstractTest;
 import org.apache.ignite.sql.ResultSetMetadata;
 import org.apache.ignite.sql.SqlException;
+import org.jetbrains.annotations.Nullable;
+import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.ValueSource;
-import org.mockito.invocation.InvocationOnMock;
 import org.mockito.junit.jupiter.MockitoExtension;
-import org.mockito.stubbing.Answer;
 
 /**
  * Test to verify {@link JdbcQueryCursorHandlerImpl}.
@@ -57,76 +64,58 @@ public class JdbcQueryCursorHandlerImplTest extends 
BaseIgniteAbstractTest {
     @ValueSource(booleans = {true, false})
     void testGetMoreResultsProcessExceptions(boolean nextResultThrow) throws 
IgniteInternalCheckedException {
         ClientResourceRegistry resourceRegistryMocked = 
mock(ClientResourceRegistry.class);
-        ClientResource rsrc = mock(ClientResource.class);
+        ClientResource resource = mock(ClientResource.class);
 
         JdbcQueryCursorHandler cursorHandler = new 
JdbcQueryCursorHandlerImpl(resourceRegistryMocked);
 
-        when(resourceRegistryMocked.get(anyLong())).thenAnswer(new 
Answer<ClientResource>() {
-            @Override
-            public ClientResource answer(InvocationOnMock invocation) {
-                return rsrc;
-            }
-        });
-
-        when(rsrc.get(AsyncSqlCursor.class)).thenAnswer(new 
Answer<AsyncSqlCursor<InternalSqlRow>>() {
-            @Override
-            public AsyncSqlCursor<InternalSqlRow> answer(InvocationOnMock 
invocation) {
-                return new AsyncSqlCursor<>() {
-                    @Override
-                    public SqlQueryType queryType() {
-                        throw new UnsupportedOperationException("queryType");
-                    }
-
-                    @Override
-                    public ResultSetMetadata metadata() {
-                        throw new UnsupportedOperationException("metadata");
-                    }
-
-                    @Override
-                    public boolean hasNextResult() {
-                        return true;
-                    }
-
-                    @Override
-                    public CompletableFuture<Void> onClose() {
-                        return null;
-                    }
-
-                    @Override
-                    public CompletableFuture<Void> onFirstPageReady() {
-                        return null;
-                    }
-
-                    @Override
-                    public CompletableFuture<AsyncSqlCursor<InternalSqlRow>> 
nextResult() {
-                        if (nextResultThrow) {
-                            throw new SqlException(STMT_PARSE_ERR, new 
Exception("nextResult exception"));
-                        } else {
-                            AsyncSqlCursorImpl<InternalSqlRow> sqlCursor = 
mock(AsyncSqlCursorImpl.class);
-
-                            
lenient().when(sqlCursor.requestNextAsync(anyInt()))
-                                    
.thenAnswer((Answer<BatchedResult<InternalSqlRow>>) invocation -> {
-                                        throw new 
IgniteInternalException(VALIDATION_ERR, "requestNextAsync error");
-                                    }
-                            );
-
-                            return 
CompletableFuture.completedFuture(sqlCursor);
-                        }
-                    }
-
-                    @Override
-                    public CompletableFuture<BatchedResult<InternalSqlRow>> 
requestNextAsync(int rows) {
-                        return nullCompletedFuture();
-                    }
-
-                    @Override
-                    public CompletableFuture<Void> closeAsync() {
-                        return nullCompletedFuture();
-                    }
-                };
-            }
-        });
-
-        await(cursorHandler.getMoreResultsAsync(new 
JdbcFetchQueryResultsRequest(1, 100)), 5, TimeUnit.SECONDS);
+        when(resourceRegistryMocked.remove(anyLong())).thenReturn(resource);
+
+        AsyncSqlCursor<InternalSqlRow> cursor = createCursor(
+                nextResultThrow 
+                        ? failedFuture(new SqlException(STMT_PARSE_ERR, new 
Exception("nextResult exception"))) 
+                        : completedFuture(createCursor(null))
+        );
+
+        when(resource.get(AsyncSqlCursor.class)).thenReturn(cursor);
+
+        JdbcQuerySingleResult result = await(
+                cursorHandler.getMoreResultsAsync(new 
JdbcFetchQueryResultsRequest(1, 100)), 5, TimeUnit.SECONDS
+        );
+        assertEquals(nextResultThrow, !result.success());
+    }
+
+    @Test
+    void exceptionThrownWhenCursorDoesntHaveNextResult() throws 
IgniteInternalCheckedException {
+        ClientResourceRegistry resourceRegistryMocked = 
mock(ClientResourceRegistry.class);
+        ClientResource resource = mock(ClientResource.class);
+
+        JdbcQueryCursorHandler cursorHandler = new 
JdbcQueryCursorHandlerImpl(resourceRegistryMocked);
+
+        when(resourceRegistryMocked.remove(anyLong())).thenReturn(resource);
+
+        AsyncSqlCursor<InternalSqlRow> cursor = createCursor(null);
+
+        when(resource.get(AsyncSqlCursor.class)).thenReturn(cursor);
+
+        JdbcQuerySingleResult result = await(
+                cursorHandler.getMoreResultsAsync(new 
JdbcFetchQueryResultsRequest(1, 100)), 5, TimeUnit.SECONDS
+        );
+        assertFalse(result.success());
+        assertThat(result.err(), containsString("Cursor doesn't have next 
result"));
+        // handler must close cursor
+        assertThat(cursor.onClose(), willCompleteSuccessfully());
+    }
+
+    private static AsyncSqlCursor<InternalSqlRow> createCursor(
+            @Nullable CompletableFuture<AsyncSqlCursor<InternalSqlRow>> 
nextCursorFuture
+    ) {
+        ResultSetMetadata emptyMeta = new ResultSetMetadataImpl(List.of());
+
+        return new AsyncSqlCursorImpl<>(
+                SqlQueryType.QUERY,
+                emptyMeta,
+                new IteratorToDataCursorAdapter<>(Collections.emptyIterator()),
+                nextCursorFuture
+        );
     }
 }
diff --git 
a/modules/client-handler/src/test/java/org/apache/ignite/client/handler/JdbcQueryEventHandlerImplTest.java
 
b/modules/client-handler/src/test/java/org/apache/ignite/client/handler/JdbcQueryEventHandlerImplTest.java
index d61112dc81..b2a463adca 100644
--- 
a/modules/client-handler/src/test/java/org/apache/ignite/client/handler/JdbcQueryEventHandlerImplTest.java
+++ 
b/modules/client-handler/src/test/java/org/apache/ignite/client/handler/JdbcQueryEventHandlerImplTest.java
@@ -107,7 +107,7 @@ class JdbcQueryEventHandlerImplTest extends 
BaseIgniteAbstractTest {
 
         assertThat(result, notNullValue());
         assertThat(result.status(), is(STATUS_FAILED));
-        assertThat(result.hasResults(), is(false));
+        assertThat(result.success(), is(false));
         assertThat(result.err(), containsString("Unable to connect"));
     }
 
@@ -137,7 +137,7 @@ class JdbcQueryEventHandlerImplTest extends 
BaseIgniteAbstractTest {
         JdbcBatchExecuteResult res = fut.get();
 
         assertThat(res.status(), is(STATUS_FAILED));
-        assertThat(res.hasResults(), is(false));
+        assertThat(res.success(), is(false));
         assertThat(res.err(), containsString("Connection is closed"));
     }
 
@@ -204,7 +204,7 @@ class JdbcQueryEventHandlerImplTest extends 
BaseIgniteAbstractTest {
 
         assertThat(result, notNullValue());
         assertThat(result.status(), is(STATUS_SUCCESS));
-        assertThat(result.hasResults(), is(true));
+        assertThat(result.success(), is(true));
 
         return result.connectionId();
     }
diff --git 
a/modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc/JdbcConnection.java
 
b/modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc/JdbcConnection.java
index c316648c55..fd47071249 100644
--- 
a/modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc/JdbcConnection.java
+++ 
b/modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc/JdbcConnection.java
@@ -156,7 +156,7 @@ public class JdbcConnection implements Connection {
         try {
             JdbcConnectResult result = 
handler.connect(connProps.getConnectionTimeZone()).get();
 
-            if (!result.hasResults()) {
+            if (!result.success()) {
                 throw 
IgniteQueryErrorCode.createJdbcSqlException(result.err(), result.status());
             }
 
diff --git 
a/modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc/JdbcDatabaseMetadata.java
 
b/modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc/JdbcDatabaseMetadata.java
index 719e540a4b..f72b2edf82 100644
--- 
a/modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc/JdbcDatabaseMetadata.java
+++ 
b/modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc/JdbcDatabaseMetadata.java
@@ -869,7 +869,7 @@ public class JdbcDatabaseMetadata implements 
DatabaseMetaData {
             JdbcMetaTablesResult res
                     = conn.handler().tablesMetaAsync(new 
JdbcMetaTablesRequest(schemaPtrn, tblNamePtrn, tblTypes)).get();
 
-            if (!res.hasResults()) {
+            if (!res.success()) {
                 throw IgniteQueryErrorCode.createJdbcSqlException(res.err(), 
res.status());
             }
 
@@ -912,7 +912,7 @@ public class JdbcDatabaseMetadata implements 
DatabaseMetaData {
         try {
             JdbcMetaSchemasResult res = conn.handler().schemasMetaAsync(new 
JdbcMetaSchemasRequest(schemaPtrn)).get();
 
-            if (!res.hasResults()) {
+            if (!res.success()) {
                 throw IgniteQueryErrorCode.createJdbcSqlException(res.err(), 
res.status());
             }
 
@@ -994,7 +994,7 @@ public class JdbcDatabaseMetadata implements 
DatabaseMetaData {
             JdbcMetaColumnsResult res = conn.handler().columnsMetaAsync(new 
JdbcMetaColumnsRequest(schemaPtrn, tblNamePtrn, colNamePtrn))
                     .get();
 
-            if (!res.hasResults()) {
+            if (!res.success()) {
                 throw IgniteQueryErrorCode.createJdbcSqlException(res.err(), 
res.status());
             }
 
@@ -1096,7 +1096,7 @@ public class JdbcDatabaseMetadata implements 
DatabaseMetaData {
         try {
             JdbcMetaPrimaryKeysResult res = 
conn.handler().primaryKeysMetaAsync(new JdbcMetaPrimaryKeysRequest(schema, 
tbl)).get();
 
-            if (!res.hasResults()) {
+            if (!res.success()) {
                 throw IgniteQueryErrorCode.createJdbcSqlException(res.err(), 
res.status());
             }
 
diff --git 
a/modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc/JdbcPreparedStatement.java
 
b/modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc/JdbcPreparedStatement.java
index 7105a83c5c..a38d9f2563 100644
--- 
a/modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc/JdbcPreparedStatement.java
+++ 
b/modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc/JdbcPreparedStatement.java
@@ -148,7 +148,7 @@ public class JdbcPreparedStatement extends JdbcStatement 
implements PreparedStat
         try {
             JdbcBatchExecuteResult res = 
conn.handler().batchPrepStatementAsync(conn.connectionId(), req).get();
 
-            if (!res.hasResults()) {
+            if (!res.success()) {
                 throw new BatchUpdateException(res.err(),
                         
IgniteQueryErrorCode.codeToSqlState(res.getErrorCode()),
                         res.getErrorCode(),
diff --git 
a/modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc/JdbcQueryExecuteResponse.java
 
b/modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc/JdbcQueryExecuteResponse.java
index a3202921a7..46d837e937 100644
--- 
a/modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc/JdbcQueryExecuteResponse.java
+++ 
b/modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc/JdbcQueryExecuteResponse.java
@@ -71,12 +71,8 @@ public class JdbcQueryExecuteResponse extends Response {
 
     /** {@inheritDoc} */
     @Override
-    public boolean hasResults() {
-        return result.hasResults();
-    }
-
-    boolean hasResult() {
-        return result.resultAvailable();
+    public boolean success() {
+        return result.success();
     }
 
     /**
diff --git 
a/modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc/JdbcResultSet.java 
b/modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc/JdbcResultSet.java
index 75892545a6..d84abc6a75 100644
--- 
a/modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc/JdbcResultSet.java
+++ 
b/modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc/JdbcResultSet.java
@@ -72,7 +72,6 @@ import 
org.apache.ignite.internal.jdbc.proto.event.JdbcQueryCloseResult;
 import org.apache.ignite.internal.jdbc.proto.event.JdbcQueryFetchResult;
 import org.apache.ignite.internal.jdbc.proto.event.JdbcQueryMetadataRequest;
 import org.apache.ignite.internal.jdbc.proto.event.JdbcQuerySingleResult;
-import org.apache.ignite.internal.jdbc.proto.event.Response;
 import org.apache.ignite.internal.util.TransformingIterator;
 import org.apache.ignite.sql.ColumnType;
 import org.jetbrains.annotations.Nullable;
@@ -99,23 +98,22 @@ public class JdbcResultSet implements ResultSet {
         }
     };
 
-    /** Statement. */
     private final JdbcStatement stmt;
-
-    /** Cursor ID. */
-    private final Long cursorId;
+    private final @Nullable Long cursorId;
+    private final boolean hasResultSet;
+    private final boolean hasNextResult;
 
     /** Column order map. */
-    private Map<String, Integer> colOrder;
+    private @Nullable Map<String, Integer> colOrder;
 
     /** Rows. */
-    private List<BinaryTupleReader> rows;
+    private @Nullable List<BinaryTupleReader> rows;
 
     /** Rows iterator. */
-    private Iterator<List<Object>> rowsIter;
+    private @Nullable Iterator<List<Object>> rowsIter;
 
     /** Current row. */
-    private List<Object> curRow;
+    private @Nullable List<Object> curRow;
 
     /** Current position. */
     private int curPos;
@@ -126,15 +124,15 @@ public class JdbcResultSet implements ResultSet {
     /** Closed flag. */
     private boolean closed;
 
+    /** If {#code true} indicates that handler still holds cursor in 
resources. */
+    private boolean holdsResource;
+
     /** Was {@code NULL} flag. */
     private boolean wasNull;
 
     /** Fetch size. */
     private int fetchSize;
 
-    /** Is query flag. */
-    private boolean isQuery;
-
     /** Update count. */
     private long updCnt;
 
@@ -145,35 +143,44 @@ public class JdbcResultSet implements ResultSet {
     private JdbcQueryCursorHandler cursorHandler;
 
     /** Jdbc metadata. */
-    private JdbcResultSetMetadata jdbcMeta;
+    private @Nullable JdbcResultSetMetadata jdbcMeta;
 
     /** Count of columns in resultSet row. */
     private int columnCount;
 
     /** Function to deserialize raw rows to list of objects. */
-    private Function<BinaryTupleReader, List<Object>> transformer;
-
-    /** If {#code true} indicates that handler still holds cursor in 
resources. */
-    private boolean holdsResource = true;
+    private @Nullable Function<BinaryTupleReader, List<Object>> transformer;
 
     /**
      * Creates new result set.
      *
-     * @param handler     JdbcQueryCursorHandler.
-     * @param stmt        Statement.
-     * @param cursorId    Cursor ID.
-     * @param fetchSize   Fetch size.
-     * @param finished    Finished flag.
-     * @param rows        Rows.
-     * @param isQry       Is Result ser for Select query.
-     * @param updCnt      Update count.
-     * @param closeStmt   Close statement on the result set close.
+     * @param handler JdbcQueryCursorHandler.
+     * @param stmt Statement.
+     * @param cursorId Cursor ID.
+     * @param fetchSize Fetch size.
+     * @param finished Finished flag.
+     * @param rows Rows.
+     * @param hasResultSet Is Result ser for Select query.
+     * @param hasNextResult Whether this result is part of multi statement and 
there is at least one more result available.
+     * @param updCnt Update count.
+     * @param closeStmt Close statement on the result set close.
      * @param columnCount Count of columns in resultSet row.
      * @param transformer Function to deserialize raw rows to list of objects.
      */
-    JdbcResultSet(JdbcQueryCursorHandler handler, JdbcStatement stmt, Long 
cursorId, int fetchSize, boolean finished,
-            List<BinaryTupleReader> rows, boolean isQry, long updCnt, boolean 
closeStmt, int columnCount,
-            Function<BinaryTupleReader, List<Object>> transformer) {
+    JdbcResultSet(
+            JdbcQueryCursorHandler handler,
+            JdbcStatement stmt,
+            @Nullable Long cursorId,
+            int fetchSize,
+            boolean finished,
+            @Nullable List<BinaryTupleReader> rows,
+            boolean hasResultSet,
+            boolean hasNextResult,
+            long updCnt,
+            boolean closeStmt,
+            int columnCount,
+            @Nullable Function<BinaryTupleReader, List<Object>> transformer
+    ) {
         assert stmt != null;
         assert fetchSize > 0;
 
@@ -182,18 +189,21 @@ public class JdbcResultSet implements ResultSet {
         this.cursorId = cursorId;
         this.fetchSize = fetchSize;
         this.finished = finished;
-        this.isQuery = isQry;
+        this.hasResultSet = hasResultSet;
+        this.hasNextResult = hasNextResult;
         this.closeStmt = closeStmt;
         this.columnCount = columnCount;
-        this.transformer = transformer;
 
-        if (isQuery) {
-            this.rows = rows;
+        if (this.hasResultSet) {
+            this.transformer = Objects.requireNonNull(transformer);
+            this.rows = Objects.requireNonNull(rows);
 
-            rowsIter = rows != null ? new 
TransformingIterator<>(rows.iterator(), transformer) : null;
+            rowsIter = new TransformingIterator<>(rows.iterator(), 
transformer);
         } else {
             this.updCnt = updCnt;
         }
+
+        holdsResource = cursorId != null;
     }
 
     /**
@@ -204,12 +214,14 @@ public class JdbcResultSet implements ResultSet {
      *
      * @exception SQLException if a database access error occurs
      */
-    public JdbcResultSet(List<List<Object>> rows, List<JdbcColumnMeta> meta) 
throws SQLException {
+    JdbcResultSet(List<List<Object>> rows, List<JdbcColumnMeta> meta) throws 
SQLException {
         stmt = null;
         cursorId = null;
 
         finished = true;
-        isQuery = true;
+        hasResultSet = true;
+        hasNextResult = false;
+        holdsResource = false;
 
         this.rowsIter = rows.iterator();
         this.jdbcMeta = new JdbcResultSetMetadata(meta);
@@ -223,32 +235,39 @@ public class JdbcResultSet implements ResultSet {
 
     @Nullable JdbcResultSet getNextResultSet() throws SQLException {
         try {
-            JdbcFetchQueryResultsRequest req = new 
JdbcFetchQueryResultsRequest(cursorId, fetchSize);
-            JdbcQuerySingleResult res = 
cursorHandler.getMoreResultsAsync(req).get();
+            if (hasNextResult) {
+                assert cursorId != null;
 
-            close0(true);
+                // all resources will be freed on server by 
`getMoreResultsAsync` call, so we need to reflect this in local result set
+                closed = true;
+                holdsResource = false;
 
-            if (!res.resultAvailable()) {
-                if (res.status() == Response.STATUS_FAILED) {
+                JdbcFetchQueryResultsRequest req = new 
JdbcFetchQueryResultsRequest(cursorId, fetchSize);
+                JdbcQuerySingleResult res = 
cursorHandler.getMoreResultsAsync(req).get();
+
+                if (!res.success()) {
                     throw 
IgniteQueryErrorCode.createJdbcSqlException(res.err(), res.status());
                 }
 
-                return null;
-            }
+                Long newCursorId = res.cursorId();
 
-            long cursorId0 = res.cursorId();
+                List<ColumnType> columnTypes = res.columnTypes();
+                int[] decimalScales = res.decimalScales();
 
-            List<ColumnType> columnTypes = res.columnTypes();
-            int[] decimalScales = res.decimalScales();
+                rows = List.of();
 
-            rows = List.of();
+                Function<BinaryTupleReader, List<Object>> transformer = 
createTransformer(columnTypes, decimalScales);
 
-            Function<BinaryTupleReader, List<Object>> transformer = 
createTransformer(columnTypes, decimalScales);
+                int colCount = columnTypes == null ? 0 : columnTypes.size();
 
-            int colCount = columnTypes == null ? 0 : columnTypes.size();
+                return new JdbcResultSet(cursorHandler, stmt, newCursorId, 
fetchSize, !res.hasMoreData(), res.items(),
+                        res.hasResultSet(), res.hasNextResult(), 
res.updateCount(), closeStmt, colCount, transformer);
+            } else {
+                // cursor doesn't have next result, thus let's just close 
current one
+                close0(true);
 
-            return new JdbcResultSet(cursorHandler, stmt, cursorId0, 
fetchSize, res.last(), res.items(),
-                    res.isQuery(), res.updateCount(), closeStmt, colCount, 
transformer);
+                return null;
+            }
         } catch (InterruptedException e) {
             throw new SQLException("Thread was interrupted.", e);
         } catch (ExecutionException e) {
@@ -266,7 +285,7 @@ public class JdbcResultSet implements ResultSet {
             try {
                 JdbcQueryFetchResult res = cursorHandler.fetchAsync(new 
JdbcFetchQueryResultsRequest(cursorId, fetchSize)).get();
 
-                if (!res.hasResults()) {
+                if (!res.success()) {
                     throw 
IgniteQueryErrorCode.createJdbcSqlException(res.err(), res.status());
                 }
 
@@ -308,7 +327,7 @@ public class JdbcResultSet implements ResultSet {
     /** {@inheritDoc} */
     @Override
     public void close() throws SQLException {
-        close0(false);
+        close0(!hasNextResult);
 
         if (closeStmt) {
             stmt.closeIfAllResultsClosed();
@@ -323,17 +342,19 @@ public class JdbcResultSet implements ResultSet {
      * @throws SQLException On error.
      */
     void close0(boolean removeFromResources) throws SQLException {
-        if (!holdsResource && (isClosed() || cursorId == null)) {
-            return;
-        }
+        try {
+            if (!holdsResource) {
+                return;
+            }
 
-        holdsResource = !removeFromResources;
+            holdsResource = !removeFromResources;
+
+            assert cursorId != null;
 
-        try {
             if (stmt != null) {
                 JdbcQueryCloseResult res = cursorHandler.closeAsync(new 
JdbcQueryCloseRequest(cursorId, removeFromResources)).get();
 
-                if (!res.hasResults()) {
+                if (!res.success()) {
                     throw 
IgniteQueryErrorCode.createJdbcSqlException(res.err(), res.status());
                 }
             }
@@ -2106,8 +2127,8 @@ public class JdbcResultSet implements ResultSet {
      *
      * @return Is query flag.
      */
-    public boolean isQuery() {
-        return isQuery;
+    public boolean hasResultSet() {
+        return hasResultSet;
     }
 
     /**
@@ -2122,6 +2143,8 @@ public class JdbcResultSet implements ResultSet {
         ensureHasCurrentRow();
 
         try {
+            assert curRow != null;
+
             Object val = curRow.get(colIdx - 1);
 
             wasNull = val == null;
@@ -2268,7 +2291,7 @@ public class JdbcResultSet implements ResultSet {
      * @throws SQLException On error.
      */
     private void initMeta() throws SQLException {
-        if (finished && !isQuery) {
+        if (finished && !hasResultSet) {
             throw new SQLException("Server cursor is already closed.", 
SqlStateCode.INVALID_CURSOR_STATE);
         }
 
diff --git 
a/modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc/JdbcStatement.java 
b/modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc/JdbcStatement.java
index 41fb9667cd..0729268247 100644
--- 
a/modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc/JdbcStatement.java
+++ 
b/modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc/JdbcStatement.java
@@ -150,16 +150,12 @@ public class JdbcStatement implements Statement {
             throw new SQLException("Query execution canceled.", 
SqlStateCode.QUERY_CANCELLED, e);
         }
 
-        if (!res.hasResult()) {
+        if (!res.success()) {
             throw IgniteQueryErrorCode.createJdbcSqlException(res.err(), 
res.status());
         }
 
         JdbcQuerySingleResult executeResult = res.result();
 
-        if (!executeResult.resultAvailable()) {
-            throw 
IgniteQueryErrorCode.createJdbcSqlException(executeResult.err(), 
executeResult.status());
-        }
-
         resSets = new ArrayList<>();
 
         JdbcQueryCursorHandler handler = new 
JdbcClientQueryCursorHandler(res.getChannel());
@@ -170,9 +166,9 @@ public class JdbcStatement implements Statement {
 
         Function<BinaryTupleReader, List<Object>> transformer = 
createTransformer(columnTypes, decimalScales);
 
-        resSets.add(new JdbcResultSet(handler, this, executeResult.cursorId(), 
pageSize,
-                executeResult.last(), executeResult.items(), 
executeResult.isQuery(), executeResult.updateCount(),
-                closeOnCompletion, columnTypes.size(), transformer));
+        resSets.add(new JdbcResultSet(handler, this, executeResult.cursorId(), 
pageSize, !executeResult.hasMoreData(),
+                executeResult.items(), executeResult.hasResultSet(), 
executeResult.hasNextResult(),
+                executeResult.updateCount(), closeOnCompletion, 
columnTypes.size(), transformer));
     }
 
     /** {@inheritDoc} */
@@ -399,7 +395,7 @@ public class JdbcStatement implements Statement {
 
         @Nullable JdbcResultSet rs = resSets.get(curRes);
 
-        if (rs == null || !rs.isQuery()) {
+        if (rs == null || !rs.hasResultSet()) {
             return null;
         }
 
@@ -417,7 +413,7 @@ public class JdbcStatement implements Statement {
 
         @Nullable JdbcResultSet rs = resSets.get(curRes);
 
-        if (rs == null || rs.isQuery()) {
+        if (rs == null || rs.hasResultSet()) {
             return -1;
         }
 
@@ -577,7 +573,7 @@ public class JdbcStatement implements Statement {
         try {
             JdbcBatchExecuteResult res = 
conn.handler().batchAsync(conn.connectionId(), req).get();
 
-            if (!res.hasResults()) {
+            if (!res.success()) {
                 throw new BatchUpdateException(res.err(),
                         
IgniteQueryErrorCode.codeToSqlState(res.getErrorCode()),
                         res.getErrorCode(),
@@ -690,7 +686,7 @@ public class JdbcStatement implements Statement {
      * @return isQuery flag.
      */
     protected boolean isQuery() {
-        return Objects.requireNonNull(resSets).get(0).isQuery();
+        return Objects.requireNonNull(resSets).get(0).hasResultSet();
     }
 
     /**
diff --git 
a/modules/jdbc/src/test/java/org/apache/ignite/internal/jdbc/JdbcResultSetTest.java
 
b/modules/jdbc/src/test/java/org/apache/ignite/internal/jdbc/JdbcResultSetTest.java
index a8637c397b..d62f5bfb28 100644
--- 
a/modules/jdbc/src/test/java/org/apache/ignite/internal/jdbc/JdbcResultSetTest.java
+++ 
b/modules/jdbc/src/test/java/org/apache/ignite/internal/jdbc/JdbcResultSetTest.java
@@ -19,13 +19,14 @@ package org.apache.ignite.internal.jdbc;
 
 import static 
org.apache.ignite.internal.jdbc.proto.IgniteQueryErrorCode.codeToSqlState;
 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.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
 import java.sql.SQLException;
@@ -39,6 +40,8 @@ import org.apache.ignite.internal.jdbc.proto.event.Response;
 import org.apache.ignite.internal.testframework.BaseIgniteAbstractTest;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
 import org.mockito.ArgumentCaptor;
 import org.mockito.junit.jupiter.MockitoExtension;
 
@@ -46,22 +49,17 @@ import org.mockito.junit.jupiter.MockitoExtension;
 @ExtendWith(MockitoExtension.class)
 public class JdbcResultSetTest extends BaseIgniteAbstractTest {
     @Test
-    public void getNextResultSetTest() throws SQLException {
+    public void exceptionIsPropagatedFromGetNextResultResponse() throws 
SQLException {
         String errorStr = "Failed to fetch query results";
 
         JdbcQueryCursorHandler handler = mock(JdbcQueryCursorHandler.class);
         JdbcStatement stmt = mock(JdbcStatement.class);
 
-        JdbcResultSet rs = spy(new JdbcResultSet(handler, stmt, 1L, 1, true, 
List.of(), true, 0, false, 1, null));
+        JdbcResultSet rs = createResultSet(handler, stmt, true);
 
         
when(handler.getMoreResultsAsync(any())).thenReturn(CompletableFuture.completedFuture(
                 new JdbcQuerySingleResult(Response.STATUS_FAILED, errorStr)));
 
-        JdbcQueryCloseResult closeRes = mock(JdbcQueryCloseResult.class);
-
-        
when(handler.closeAsync(any())).thenReturn(CompletableFuture.completedFuture(closeRes));
-        when(closeRes.hasResults()).thenReturn(true);
-
         SQLException ex = assertThrows(SQLException.class, 
rs::getNextResultSet);
 
         String actualMessage = ex.getMessage();
@@ -69,21 +67,65 @@ public class JdbcResultSetTest extends 
BaseIgniteAbstractTest {
         assertEquals(errorStr, actualMessage);
         assertEquals(codeToSqlState(Response.STATUS_FAILED), ex.getSQLState());
 
-        verify(rs).close0(anyBoolean());
+        verify(handler).getMoreResultsAsync(any());
+        verifyNoMoreInteractions(handler);
+
+        assertTrue(rs.isClosed());
     }
 
     @Test
-    public void checkClose() throws SQLException {
+    public void getNextResultWhenNextResultAvailable() throws SQLException {
         JdbcQueryCursorHandler handler = mock(JdbcQueryCursorHandler.class);
         JdbcStatement stmt = mock(JdbcStatement.class);
 
-        JdbcResultSet rs = spy(new JdbcResultSet(handler, stmt, 1L, 1, true, 
List.of(), true, 0, false, 1, null));
+        JdbcResultSet rs = createResultSet(handler, stmt, true);
+
+        int expectedUpdateCount = 10;
+
+        when(handler.getMoreResultsAsync(any()))
+                .thenReturn(CompletableFuture.completedFuture(new 
JdbcQuerySingleResult(null, expectedUpdateCount, false)));
+
+        JdbcResultSet nextRs = rs.getNextResultSet();
+
+        assertNotNull(nextRs);
+        assertEquals(expectedUpdateCount, nextRs.updatedCount());
 
-        JdbcQueryCloseResult closeRequest = mock(JdbcQueryCloseResult.class);
+        verify(handler).getMoreResultsAsync(any());
+        verifyNoMoreInteractions(handler);
+
+        assertTrue(rs.isClosed());
+    }
+
+    @Test
+    public void getNextResultWhenNextResultIsNotAvailable() throws 
SQLException {
+        JdbcQueryCursorHandler handler = mock(JdbcQueryCursorHandler.class);
+        JdbcStatement stmt = mock(JdbcStatement.class);
 
-        when(closeRequest.hasResults()).thenReturn(true);
+        JdbcResultSet rs = createResultSet(handler, stmt, false);
 
-        
when(handler.closeAsync(any())).thenReturn(CompletableFuture.completedFuture(closeRequest));
+        when(handler.closeAsync(any()))
+                .thenReturn(CompletableFuture.completedFuture(new 
JdbcQueryCloseResult()));
+
+        JdbcResultSet nextRs = rs.getNextResultSet();
+
+        assertNull(nextRs);
+
+        verify(handler).closeAsync(any());
+        verifyNoMoreInteractions(handler);
+
+        assertTrue(rs.isClosed());
+    }
+
+    @ParameterizedTest(name = "hasNextResult: {0}")
+    @ValueSource(booleans = {true, false})
+    public void checkClose(boolean hasNextResult) throws SQLException {
+        JdbcQueryCursorHandler handler = mock(JdbcQueryCursorHandler.class);
+        JdbcStatement stmt = mock(JdbcStatement.class);
+
+        JdbcResultSet rs = createResultSet(handler, stmt, hasNextResult);
+
+        when(handler.closeAsync(any()))
+                .thenReturn(CompletableFuture.completedFuture(new 
JdbcQueryCloseResult()));
 
         rs.close();
 
@@ -91,6 +133,14 @@ public class JdbcResultSetTest extends 
BaseIgniteAbstractTest {
 
         verify(handler).closeAsync(argument.capture());
 
-        assertFalse(argument.getValue().removeFromResources());
+        assertEquals(!hasNextResult, 
argument.getValue().removeFromResources());
+    }
+
+    private static JdbcResultSet createResultSet(
+            JdbcQueryCursorHandler handler,
+            JdbcStatement statement,
+            boolean hasNextResult
+    ) {
+        return new JdbcResultSet(handler, statement, 1L, 1, true, List.of(), 
true, hasNextResult, 0, false, 1, r -> List.of());
     }
 }
diff --git a/modules/platforms/cpp/ignite/odbc/meta/table_meta.cpp 
b/modules/platforms/cpp/ignite/odbc/meta/table_meta.cpp
index 25306f49af..54b725747b 100644
--- a/modules/platforms/cpp/ignite/odbc/meta/table_meta.cpp
+++ b/modules/platforms/cpp/ignite/odbc/meta/table_meta.cpp
@@ -20,9 +20,6 @@
 namespace ignite {
 
 void table_meta::read(protocol::reader &reader) {
-    auto has_data = reader.read_bool();
-    assert(has_data);
-
     auto status = reader.read_int32();
     assert(status == 0);
 
diff --git a/modules/platforms/cpp/ignite/odbc/query/column_metadata_query.cpp 
b/modules/platforms/cpp/ignite/odbc/query/column_metadata_query.cpp
index e5742cd1e3..6bc60b75cf 100644
--- a/modules/platforms/cpp/ignite/odbc/query/column_metadata_query.cpp
+++ b/modules/platforms/cpp/ignite/odbc/query/column_metadata_query.cpp
@@ -78,9 +78,6 @@ std::vector<odbc_column_meta> 
read_column_meta(protocol::reader &reader) {
     columns.reserve(size);
 
     for (std::int32_t column_idx = 0; column_idx < size; ++column_idx) {
-        auto has_data = reader.read_bool();
-        assert(has_data);
-
         auto status = reader.read_int32();
         assert(status == 0);
 
@@ -290,16 +287,13 @@ sql_result 
column_metadata_query::make_request_get_columns_meta() {
             });
 
         protocol::reader reader{response.get_bytes_view()};
-        m_has_result_set = reader.read_bool();
 
         auto status = reader.read_int32();
         auto err_msg = reader.read_string_nullable();
         if (err_msg)
             throw odbc_error(response_status_to_sql_state(status), *err_msg);
 
-        if (m_has_result_set) {
-            m_meta = read_column_meta(reader);
-        }
+        m_meta = read_column_meta(reader);
 
         m_executed = true;
     });
diff --git a/modules/platforms/cpp/ignite/odbc/query/primary_keys_query.cpp 
b/modules/platforms/cpp/ignite/odbc/query/primary_keys_query.cpp
index aa118c9d1d..a430066a87 100644
--- a/modules/platforms/cpp/ignite/odbc/query/primary_keys_query.cpp
+++ b/modules/platforms/cpp/ignite/odbc/query/primary_keys_query.cpp
@@ -130,15 +130,13 @@ sql_result 
primary_keys_query::make_request_get_primary_keys() {
             });
 
         protocol::reader reader{response.get_bytes_view()};
-        bool has_result_set = reader.read_bool();
 
         auto status = reader.read_int32();
         auto err_msg = reader.read_string_nullable();
         if (err_msg)
             throw odbc_error(response_status_to_sql_state(status), *err_msg);
 
-        if (has_result_set)
-            m_meta = read_key_meta(reader);
+        m_meta = read_key_meta(reader);
 
         m_executed = true;
     });
diff --git a/modules/platforms/cpp/ignite/odbc/query/table_metadata_query.cpp 
b/modules/platforms/cpp/ignite/odbc/query/table_metadata_query.cpp
index e7ca838bd1..25fdf99428 100644
--- a/modules/platforms/cpp/ignite/odbc/query/table_metadata_query.cpp
+++ b/modules/platforms/cpp/ignite/odbc/query/table_metadata_query.cpp
@@ -192,16 +192,13 @@ sql_result 
table_metadata_query::make_request_get_tables_meta() {
             });
 
         protocol::reader reader{response.get_bytes_view()};
-        m_has_result_set = reader.read_bool();
-
+        
         auto status = reader.read_int32();
         auto err_msg = reader.read_string_nullable();
         if (err_msg)
             throw odbc_error(response_status_to_sql_state(status), *err_msg);
 
-        if (m_has_result_set) {
-            m_meta = read_table_meta_vector(reader);
-        }
+        m_meta = read_table_meta_vector(reader);
 
         m_executed = true;
     });

Reply via email to