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