Skip to content

Commit 7bf5863

Browse files
committed
MySQL cursor fetch uses wrong row descriptor after initial fetch
See #1525 MySQL sends column definitions in response to a prepare command. But when executing the statement or fetching the first page of a cursor, it sends column definitions again. The row decoder should not use the definitions given in response to prepare command because they may not be accurate. The previous cursor implementation used them when fetching the second page. Signed-off-by: Thomas Segismont <[email protected]>
1 parent d887631 commit 7bf5863

File tree

7 files changed

+46
-14
lines changed

7 files changed

+46
-14
lines changed

vertx-mysql-client/src/main/java/io/vertx/mysqlclient/impl/codec/ExtendedQueryMySQLCommand.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package io.vertx.mysqlclient.impl.codec;
1818

1919
import io.netty.buffer.ByteBuf;
20+
import io.vertx.mysqlclient.impl.MySQLRowDescriptor;
2021
import io.vertx.mysqlclient.impl.protocol.CommandType;
2122
import io.vertx.sqlclient.Tuple;
2223
import io.vertx.sqlclient.codec.CommandResponse;
@@ -31,7 +32,14 @@ public ExtendedQueryMySQLCommand(ExtendedQueryCommand<R> cmd, MySQLPreparedState
3132
super(cmd, statement);
3233
if (cmd.fetch() > 0 && statement.isCursorOpen) {
3334
// restore the state we need for decoding fetch response based on the prepared statement
34-
columnDefinitions = statement.rowDesc.columnDefinitions();
35+
columnDefinitions = statement.cursorRowDescriptor.columnDefinitions();
36+
}
37+
}
38+
39+
@Override
40+
protected void handleRowDescriptorCreated(MySQLRowDescriptor mySQLRowDesc) {
41+
if (cmd.fetch() > 0) {
42+
statement.cursorRowDescriptor = mySQLRowDesc;
3543
}
3644
}
3745

@@ -42,7 +50,7 @@ void encode(MySQLEncoder encoder) {
4250
if (statement.isCursorOpen) {
4351
if (decoder == null) {
4452
// restore the state we need for decoding if column definitions are not included in the fetch response
45-
decoder = new RowResultDecoder<>(cmd.collector(), statement.rowDesc);
53+
decoder = new RowResultDecoder<>(cmd.collector(), statement.cursorRowDescriptor);
4654
}
4755
sendStatementFetchCommand(statement.statementId, cmd.fetch());
4856
} else {

vertx-mysql-client/src/main/java/io/vertx/mysqlclient/impl/codec/MySQLPreparedStatement.java

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,8 @@
2323
import io.vertx.mysqlclient.impl.datatype.DataType;
2424
import io.vertx.mysqlclient.impl.datatype.DataTypeCodec;
2525
import io.vertx.sqlclient.Tuple;
26-
import io.vertx.sqlclient.impl.*;
26+
import io.vertx.sqlclient.impl.ErrorMessageFactory;
2727
import io.vertx.sqlclient.internal.PreparedStatement;
28-
import io.vertx.sqlclient.internal.RowDescriptorBase;
2928
import io.vertx.sqlclient.internal.TupleBase;
3029

3130
import java.util.Arrays;
@@ -35,18 +34,17 @@ public class MySQLPreparedStatement implements PreparedStatement {
3534
final long statementId;
3635
final String sql;
3736
final MySQLParamDesc paramDesc;
38-
final MySQLRowDescriptor rowDesc;
3937
final boolean closeAfterUsage;
4038

4139
private boolean sendTypesToServer;
4240
private final DataType[] bindingTypes;
4341

4442
boolean isCursorOpen;
43+
MySQLRowDescriptor cursorRowDescriptor;
4544

46-
MySQLPreparedStatement(String sql, long statementId, MySQLParamDesc paramDesc, MySQLRowDescriptor rowDesc, boolean closeAfterUsage) {
45+
MySQLPreparedStatement(String sql, long statementId, MySQLParamDesc paramDesc, boolean closeAfterUsage) {
4746
this.statementId = statementId;
4847
this.paramDesc = paramDesc;
49-
this.rowDesc = rowDesc;
5048
this.sql = sql;
5149
this.closeAfterUsage = closeAfterUsage;
5250

@@ -56,8 +54,8 @@ public class MySQLPreparedStatement implements PreparedStatement {
5654
}
5755

5856
@Override
59-
public RowDescriptorBase rowDesc() {
60-
return rowDesc;
57+
public MySQLRowDescriptor rowDesc() {
58+
throw new UnsupportedOperationException("The client should use the column definitions provided by execute or fetch response instead of prepare response");
6159
}
6260

6361
@Override

vertx-mysql-client/src/main/java/io/vertx/mysqlclient/impl/codec/PrepareStatementMySQLCommand.java

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,10 @@
1818

1919
import io.netty.buffer.ByteBuf;
2020
import io.vertx.mysqlclient.impl.MySQLParamDesc;
21-
import io.vertx.mysqlclient.impl.MySQLRowDescriptor;
22-
import io.vertx.mysqlclient.impl.datatype.DataFormat;
2321
import io.vertx.mysqlclient.impl.protocol.ColumnDefinition;
2422
import io.vertx.mysqlclient.impl.protocol.CommandType;
25-
import io.vertx.sqlclient.internal.PreparedStatement;
2623
import io.vertx.sqlclient.codec.CommandResponse;
24+
import io.vertx.sqlclient.internal.PreparedStatement;
2725
import io.vertx.sqlclient.spi.protocol.PrepareStatementCommand;
2826

2927
import static io.vertx.mysqlclient.impl.protocol.Packets.ERROR_PACKET_HEADER;
@@ -135,7 +133,6 @@ private void handleReadyForQuery() {
135133
cmd.sql(),
136134
this.statementId,
137135
new MySQLParamDesc(paramDescs),
138-
MySQLRowDescriptor.create(columnDescs, DataFormat.BINARY),
139136
!cmd.isManaged())));
140137
}
141138

vertx-mysql-client/src/main/java/io/vertx/mysqlclient/impl/codec/QueryMySQLCommandBase.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@
2323
import io.vertx.mysqlclient.impl.protocol.ColumnDefinition;
2424
import io.vertx.mysqlclient.impl.util.BufferUtils;
2525
import io.vertx.sqlclient.Row;
26-
import io.vertx.sqlclient.internal.RowDescriptorBase;
2726
import io.vertx.sqlclient.codec.CommandResponse;
27+
import io.vertx.sqlclient.internal.RowDescriptorBase;
2828
import io.vertx.sqlclient.spi.protocol.QueryCommandBase;
2929

3030
import java.util.stream.Collector;
@@ -94,9 +94,13 @@ protected void handleResultsetColumnDefinitions(ByteBuf payload) {
9494
protected void handleResultsetColumnDefinitionsDecodingCompleted() {
9595
commandHandlerState = CommandHandlerState.HANDLING_ROW_DATA_OR_END_PACKET;
9696
MySQLRowDescriptor mySQLRowDesc = MySQLRowDescriptor.create(columnDefinitions, format); // use the column definitions if provided by execute or fetch response instead of prepare response
97+
handleRowDescriptorCreated(mySQLRowDesc);
9798
decoder = new RowResultDecoder<>(cmd.collector(), mySQLRowDesc);
9899
}
99100

101+
protected void handleRowDescriptorCreated(MySQLRowDescriptor mySQLRowDesc) {
102+
}
103+
100104
protected void handleRows(ByteBuf payload, int payloadLength) {
101105
/*
102106
Resultset row can begin with 0xfe byte (when using text protocol with a field length > 0xffffff)

vertx-mysql-client/src/main/java/io/vertx/mysqlclient/impl/codec/ResetStatementMySQLCommand.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ void encode(MySQLEncoder encoder) {
2929
statement.cleanBindings();
3030

3131
statement.isCursorOpen = false;
32+
statement.cursorRowDescriptor = null;
3233
sendStatementResetCommand(statement.statementId);
3334
}
3435

vertx-mysql-client/src/test/java/io/vertx/tests/mysqlclient/MySQLQueryTest.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import io.vertx.core.Vertx;
1515
import io.vertx.core.buffer.Buffer;
1616
import io.vertx.core.file.FileSystem;
17+
import io.vertx.ext.unit.Async;
1718
import io.vertx.ext.unit.TestContext;
1819
import io.vertx.ext.unit.junit.VertxUnitRunner;
1920
import io.vertx.mysqlclient.MySQLClient;
@@ -470,4 +471,21 @@ public void testLocalInfileRequestInPackets(TestContext ctx) {
470471
}));
471472
}));
472473
}
474+
475+
@Test
476+
public void testColumnDefinitionChangeWithCursor(TestContext ctx) {
477+
Async rows = ctx.async(100);
478+
Async completion = ctx.async();
479+
MySQLConnection.connect(vertx, options).onComplete(ctx.asyncAssertSuccess(conn -> {
480+
conn
481+
.prepare("SELECT row_number() over () as \"Number\", case when 1 then 1 else 0 end as \"Case\" FROM mysql.help_relation limit 0,100")
482+
.onComplete(ctx.asyncAssertSuccess(ps -> {
483+
// Make sure to fetch from the database twice
484+
RowStream<Row> stream = ps.createStream(50);
485+
stream.exceptionHandler(ctx::fail);
486+
stream.endHandler(v -> completion.complete());
487+
stream.handler(row -> rows.countDown());
488+
}));
489+
}));
490+
}
473491
}

vertx-mysql-client/src/test/resources/init.sql

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
#allow
2+
reading mysql schema
3+
GRANT
4+
SELECT
5+
ON mysql.* TO 'mysql';
6+
17
# testing change schema
28
CREATE DATABASE emptyschema;
39
GRANT ALL ON emptyschema.* TO 'mysql'@'%';

0 commit comments

Comments
 (0)