IGNITE-20661 JDBC. Support multi statement queries by JDBC#2906
IGNITE-20661 JDBC. Support multi statement queries by JDBC#2906zstan merged 38 commits intoapache:mainfrom
Conversation
866604b to
5923904
Compare
|
@korlov42 can u make a review plz ? |
...client-handler/src/main/java/org/apache/ignite/client/handler/JdbcQueryEventHandlerImpl.java
Outdated
Show resolved
Hide resolved
...mmon/src/main/java/org/apache/ignite/internal/jdbc/proto/event/JdbcGetMoreResultsResult.java
Outdated
Show resolved
Hide resolved
...client-handler/src/main/java/org/apache/ignite/client/handler/JdbcQueryEventHandlerImpl.java
Show resolved
Hide resolved
modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc/JdbcResultSet.java
Outdated
Show resolved
Hide resolved
modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc/JdbcResultSet.java
Outdated
Show resolved
Hide resolved
| JdbcResultSet rs = resSets.get(curRes); | ||
|
|
||
| if (!rs.isQuery()) { | ||
| if (rs == null || !rs.isQuery()) { |
There was a problem hiding this comment.
if null is considering as legit element of resSets collection, then we need to mark generic type as @Nullable
There was a problem hiding this comment.
this one was not addressed, as far as I can see
There was a problem hiding this comment.
a few words about comments above:
at the line 407 you've added null checking for element of resSets collection. This implies that now null is considered legit item of the collection. If so, it's better to state in resSets declaration -- List<JdbcResultSet> resSets; --> List<@Nullable JdbcResultSet> resSets; . Now IDEA shows 4 more warning because of this annotation. Two of these warning actually may cause NPE
There was a problem hiding this comment.
getResultSet() marked as nullable, @nullable JdbcResultSet rs = too, did i miss smth ?
There was a problem hiding this comment.
i found that resSets has volatile notation seems our Statement implementation is not a thread safe at all.
apeend @nullable, fix nullability checks
modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc/JdbcStatement.java
Outdated
Show resolved
Hide resolved
modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc/JdbcStatement.java
Outdated
Show resolved
Hide resolved
modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc/JdbcStatement.java
Show resolved
Hide resolved
modules/jdbc/src/integrationTest/java/org/apache/ignite/jdbc/ItJdbcMultiStatementSelfTest.java
Outdated
Show resolved
Hide resolved
modules/jdbc/src/integrationTest/java/org/apache/ignite/jdbc/ItJdbcMultiStatementSelfTest.java
Outdated
Show resolved
Hide resolved
modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc/JdbcStatement.java
Outdated
Show resolved
Hide resolved
modules/client-handler/src/main/java/org/apache/ignite/client/handler/ClientHandlerModule.java
Outdated
Show resolved
Hide resolved
| // a bit hack, instead of calling stmt.close(), gives a chance to catch potential forgiven cursor. | ||
| stmt.execute("SELECT 1"); | ||
| ResultSet rs = stmt.getResultSet(); | ||
| rs.close(); |
There was a problem hiding this comment.
to be honest, I don't quite get the purpose of these lines
There was a problem hiding this comment.
the very first execution of stmt.execute alredy registers a first cursor in resourses, thus for correct checking resource leakage without this approach i would need to make one of actions, or call :
- stmt.close() - but this concrete implementation can work properly well while sequential .getResultset() can holds a bug.
- always get last resultset in tests and forcibly close it.
modules/jdbc/src/integrationTest/java/org/apache/ignite/jdbc/ItJdbcMultiStatementSelfTest.java
Outdated
Show resolved
Hide resolved
| assertEquals(false, res); | ||
| stmt.getResultSet(); | ||
| stmt.getMoreResults(); |
There was a problem hiding this comment.
| assertEquals(false, res); | |
| stmt.getResultSet(); | |
| stmt.getMoreResults(); | |
| assertFalse(res); | |
| assertNull(stmt.getResultSet()); | |
| assertFalse(stmt.getMoreResults()); |
🤔
There was a problem hiding this comment.
I understand you, but this case is not for check intermediate statement commands but about correct results after multiple calls, but ok, i fix it, it makes no harm.
modules/jdbc/src/integrationTest/java/org/apache/ignite/jdbc/ItJdbcStatementSelfTest.java
Outdated
Show resolved
Hide resolved
modules/jdbc/src/integrationTest/java/org/apache/ignite/jdbc/ItJdbcMultiStatementSelfTest.java
Outdated
Show resolved
Hide resolved
|
|
||
| /** | ||
| * Tests for ddl queries that contain multiply sql statements, separated by ";". | ||
| * Tests for queries that contain multiply sql statements, separated by ";". |
There was a problem hiding this comment.
please add some test(s) for autocommit=false (without TX_CONTROL statements) seems this should work fine,
There was a problem hiding this comment.
i think we need to check it after IGNITE-20463
There was a problem hiding this comment.
seems need to be addressed
modules/jdbc/src/integrationTest/java/org/apache/ignite/jdbc/ItJdbcMultiStatementSelfTest.java
Outdated
Show resolved
Hide resolved
| * @param req Results request. | ||
| * @return Result future. | ||
| */ | ||
| CompletableFuture<JdbcQuerySingleResult> getMoreResultsAsync(JdbcQueryFetchRequest req); |
There was a problem hiding this comment.
usage of JdbcQueryFetchRequest as an input argument to getMoreResults handler kinda misleading. Let's introduce separate class
There was a problem hiding this comment.
probably we need to rename somehow ? but hold additional equal class just different action seems not so good too, wdyt ?
There was a problem hiding this comment.
I don't mind to rename it, but have no good options for name.
btw, what is wrong with new class?
|
|
||
| close0(true); | ||
|
|
||
| if (!res.hasResults()) { |
There was a problem hiding this comment.
what if we got an error from handler? Let's add a test to cover this case
There was a problem hiding this comment.
probably I'm missing something, but I still no sight of error handling. Could you point out to me on particular test that covers failed response?
There was a problem hiding this comment.
ItJdbcMultiStatementSelfTest#testSimpleQueryError got an error from handler, or you are talking about something more ?
There was a problem hiding this comment.
as for now - we stop further script execution if exception is raised, i.e.
CREATE TABLE test (id INT PRIMARY KEY);
CREATE TABLE test (id INT PRIMARY KEY);
SELECT 1;
we can`t obtain SELECT 1 results, thus it seems enough, or you think that some mock are still necessary here ?
There was a problem hiding this comment.
Seems we need additional check here, something like
if (res.status() == STATUS_FAILED) {
throw createJdbcSqlException(res.err(), res.status());
}
If the JdbcQueryCursorHandler returns a future with an error, we are not handling it properly here.
(see related comment in JdbcQueryCursorHandlerImpl class)
There was a problem hiding this comment.
done, append additional handler test
| JdbcResultSet rs = resSets.get(curRes); | ||
|
|
||
| if (!rs.isQuery()) { | ||
| if (rs == null || !rs.isQuery()) { |
There was a problem hiding this comment.
this one was not addressed, as far as I can see
modules/jdbc/src/integrationTest/java/org/apache/ignite/jdbc/ItJdbcMultiStatementSelfTest.java
Outdated
Show resolved
Hide resolved
| assertFalse(res); | ||
| assertNull(stmt.getResultSet()); | ||
| assertFalse(stmt.getMoreResults()); | ||
| assertEquals(-1, getResultSetSize()); |
There was a problem hiding this comment.
if you want to check updateCount, please do it explicitly
There was a problem hiding this comment.
change getResultSetSize javadoc
There was a problem hiding this comment.
Fully support aforementioned comments. Why don't check update Count explicitly?
modules/jdbc/src/integrationTest/java/org/apache/ignite/jdbc/ItJdbcMultiStatementSelfTest.java
Outdated
Show resolved
Hide resolved
modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc/JdbcResultSet.java
Show resolved
Hide resolved
modules/jdbc/src/integrationTest/java/org/apache/ignite/jdbc/ItJdbcStatementSelfTest.java
Show resolved
Hide resolved
modules/jdbc/src/integrationTest/java/org/apache/ignite/jdbc/ItJdbcStatementSelfTest.java
Show resolved
Hide resolved
modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc/JdbcPreparedStatement.java
Outdated
Show resolved
Hide resolved
| * @param req Results request. | ||
| * @return Result future. | ||
| */ | ||
| CompletableFuture<JdbcQuerySingleResult> getMoreResultsAsync(JdbcQueryFetchRequest req); |
There was a problem hiding this comment.
I don't mind to rename it, but have no good options for name.
btw, what is wrong with new class?
| */ | ||
| public JdbcQuerySingleResult(long cursorId, List<BinaryTupleReader> rowTuples, List<ColumnType> columnTypes, int[] decimalScales, | ||
| boolean last) { | ||
| boolean last, boolean isQuery, long updateCnt) { |
There was a problem hiding this comment.
we already have contractor for DML statements, so let's use that one
There was a problem hiding this comment.
this particular constructor was used to create result for statement returning result set. With that said, it's better keep it as is (without additional isQuery and updateCnt) to avoid confusion and possible misusage
...client-handler/src/main/java/org/apache/ignite/client/handler/JdbcQueryEventHandlerImpl.java
Show resolved
Hide resolved
...client-handler/src/main/java/org/apache/ignite/client/handler/JdbcQueryEventHandlerImpl.java
Show resolved
Hide resolved
|
|
||
| close0(true); | ||
|
|
||
| if (!res.hasResults()) { |
There was a problem hiding this comment.
probably I'm missing something, but I still no sight of error handling. Could you point out to me on particular test that covers failed response?
modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc/JdbcResultSet.java
Show resolved
Hide resolved
modules/jdbc/src/integrationTest/java/org/apache/ignite/jdbc/ItJdbcMultiStatementSelfTest.java
Show resolved
Hide resolved
modules/jdbc/src/integrationTest/java/org/apache/ignite/jdbc/ItJdbcMultiStatementSelfTest.java
Show resolved
Hide resolved
|
|
||
| close0(true); | ||
|
|
||
| if (!res.hasResults()) { |
There was a problem hiding this comment.
Seems we need additional check here, something like
if (res.status() == STATUS_FAILED) {
throw createJdbcSqlException(res.err(), res.status());
}
If the JdbcQueryCursorHandler returns a future with an error, we are not handling it properly here.
(see related comment in JdbcQueryCursorHandlerImpl class)
...lient-handler/src/main/java/org/apache/ignite/client/handler/JdbcQueryCursorHandlerImpl.java
Show resolved
Hide resolved
| case CLOSE_CURRENT_RESULT: | ||
| if (curRes > 0) { | ||
| resSets.get(curRes - 1).close0(); | ||
| resSets.get(curRes - 1).close0(false); |
There was a problem hiding this comment.
Why we are calling close0 with false flag here?
The following example demonstrates resource leakage (or may be I missed something):
@Test
public void testX() throws Exception {
long initial = openCursorsRegistered();
stmt.executeUpdate("CREATE TABLE TESTX(ID INT PRIMARY KEY)");
stmt.getMoreResults();
stmt.close();
assertEquals(0L, initial - openCursorsRegistered());
}
As I see, the current combination in close0 of isQuery, finished, etc. flags were being used because we were not registering cursors for DML, DDL (and possibly other) queries.
Since we are now registering cursors for this types of queries, I think it is worth reviewing the current conditions in close0, because now they look too complex and leading to errors 😎 .
There was a problem hiding this comment.
this is connection context , append below to test and it will pass:
conn.close();
assertTrue(waitForCondition(() -> 0L == openCursorsRegistered(), 5000));
There was a problem hiding this comment.
Yes you are right. I missed this.
But as I see, now - getNextResultSet() already closes resultset with enforce=true, why we need this second call here?
| */ | ||
| public JdbcQuerySingleResult(long cursorId, List<BinaryTupleReader> rowTuples, List<ColumnType> columnTypes, int[] decimalScales, | ||
| boolean last) { | ||
| boolean last, boolean isQuery, long updateCnt) { |
There was a problem hiding this comment.
this particular constructor was used to create result for statement returning result set. With that said, it's better keep it as is (without additional isQuery and updateCnt) to avoid confusion and possible misusage
| */ | ||
| public JdbcQuerySingleResult(boolean hasNext, long updCount) { | ||
| hasResults = hasNext; | ||
| this.updateCnt = updCount; |
There was a problem hiding this comment.
if there are no results, why should we provide updateCount?
| @Override | ||
| public void writeBinary(ClientMessagePacker packer) { | ||
| super.writeBinary(packer); | ||
| packer.packLong(updateCnt); |
There was a problem hiding this comment.
if there are no results, we should not pack updateCount
|
|
||
| List<ColumnMetadata> columns = cur.metadata().columns(); | ||
|
|
||
| return buildSingleRequest(batch, columns, cursorId, !batch.hasMore(), queryType); |
There was a problem hiding this comment.
I see that first page response for first statement and next statement are created differently: for first statement only subset of query types goes through buildSingleRequest, while for next statement all types goes through this method. Let's align the creation of result for the first page
| CompletableFuture<JdbcQuerySingleResult> fut = IgniteTestUtils.runAsync(() -> | ||
| await(cursorHandler.getMoreResultsAsync(new JdbcFetchQueryResultsRequest(1, 100)), 5, TimeUnit.SECONDS) | ||
| ); | ||
|
|
||
| try { | ||
| await(fut, 5, TimeUnit.SECONDS); | ||
| } catch (Throwable e) { | ||
| fail("Unexpected exception is raised."); | ||
| } |
There was a problem hiding this comment.
to be honest, I don't quite understand what you are trying to verify here...
There was a problem hiding this comment.
I verify that JdbcQueryCursorHandlerImpl correctly handles exceptions raised from AsyncSqlCursor, do you think it`s redundant ?
| @Nullable | ||
| public ResultSet getResultSet() throws SQLException { |
There was a problem hiding this comment.
| @Nullable | |
| public ResultSet getResultSet() throws SQLException { | |
| public @Nullable ResultSet getResultSet() throws SQLException { |
| JdbcResultSet rs = resSets.get(curRes); | ||
|
|
||
| if (!rs.isQuery()) { | ||
| if (rs == null || !rs.isQuery()) { |
There was a problem hiding this comment.
a few words about comments above:
at the line 407 you've added null checking for element of resSets collection. This implies that now null is considered legit item of the collection. If so, it's better to state in resSets declaration -- List<JdbcResultSet> resSets; --> List<@Nullable JdbcResultSet> resSets; . Now IDEA shows 4 more warning because of this annotation. Two of these warning actually may cause NPE
| throw exceptionally; | ||
| } | ||
|
|
||
| return nextResultSet != null && nextResultSet.holdResults(); |
There was a problem hiding this comment.
this condition is not quite correct: empty result set still result set, thus true should be returned (try this query SELECT 1; SELECT 1 FROM table(system_range(1, 0)))
There was a problem hiding this comment.
changed, not here but logic at all
|
|
||
| default: | ||
| throw new SQLException("Invalid 'current' parameter."); | ||
| throw new SQLException("Invalid 'curr' parameter."); |
There was a problem hiding this comment.
better to use current because it is how it named in interface
| @Override | ||
| public void close() throws SQLException { | ||
| close0(); | ||
| closed = true; |
There was a problem hiding this comment.
all resources must be cleaned up as soon as possible, it's not enough to just pretend that result set is closed
98af8e0 to
51ccd71
Compare
...-common/src/main/java/org/apache/ignite/internal/jdbc/proto/event/JdbcQuerySingleResult.java
Outdated
Show resolved
Hide resolved
...-common/src/main/java/org/apache/ignite/internal/jdbc/proto/event/JdbcQuerySingleResult.java
Outdated
Show resolved
Hide resolved
modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc/JdbcStatement.java
Outdated
Show resolved
Hide resolved
modules/jdbc/src/integrationTest/java/org/apache/ignite/jdbc/ItJdbcMultiStatementSelfTest.java
Show resolved
Hide resolved
dc98aa0 to
ded00df
Compare
| if (cursorId != null) { | ||
| packer.packLong(cursorId); | ||
| } else { | ||
| packer.packNil(); | ||
| } |
There was a problem hiding this comment.
| if (cursorId != null) { | |
| packer.packLong(cursorId); | |
| } else { | |
| packer.packNil(); | |
| } | |
| packer.packLongNullable(cursorId); |
| } | ||
| }); | ||
|
|
||
| try { |
There was a problem hiding this comment.
what reason to have the try/catch block?
There was a problem hiding this comment.
look, this test check that no exception is raised from JdbcQueryCursorHandlerImpl#getMoreResultsAsync
comment exception handling in mention method and try to run test.
Thank you for submitting the pull request.
To streamline the review process of the patch and ensure better code quality
we ask both an author and a reviewer to verify the following:
The Review Checklist
- There is a single JIRA ticket related to the pull request.
- The web-link to the pull request is attached to the JIRA ticket.
- The JIRA ticket has the Patch Available state.
- The description of the JIRA ticket explains WHAT was made, WHY and HOW.
- The pull request title is treated as the final commit message. The following pattern must be used: IGNITE-XXXX Change summary where XXXX - number of JIRA issue.
Notes