Skip to content

Commit a55deb2

Browse files
committed
MySQL cursor fetch uses wrong row descriptor after initial fetch (eclipse-vertx#1562)
See eclipse-vertx#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 7af5dae commit a55deb2

File tree

8 files changed

+49
-11
lines changed

8 files changed

+49
-11
lines changed

vertx-mysql-client/src/main/java/io/vertx/mysqlclient/impl/codec/ExtendedQueryCommandCodec.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.MySQLRowDesc;
2021
import io.vertx.mysqlclient.impl.protocol.CommandType;
2122
import io.vertx.sqlclient.Tuple;
2223
import io.vertx.sqlclient.impl.command.CommandResponse;
@@ -31,7 +32,14 @@ class ExtendedQueryCommandCodec<R> extends ExtendedQueryCommandBaseCodec<R, Exte
3132
super(cmd);
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(MySQLRowDesc 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: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,18 +31,17 @@ class MySQLPreparedStatement implements PreparedStatement {
3131
final long statementId;
3232
final String sql;
3333
final MySQLParamDesc paramDesc;
34-
final MySQLRowDesc rowDesc;
3534
final boolean closeAfterUsage;
3635

3736
private boolean sendTypesToServer;
3837
private final DataType[] bindingTypes;
3938

4039
boolean isCursorOpen;
40+
MySQLRowDesc cursorRowDescriptor;
4141

42-
MySQLPreparedStatement(String sql, long statementId, MySQLParamDesc paramDesc, MySQLRowDesc rowDesc, boolean closeAfterUsage) {
42+
MySQLPreparedStatement(String sql, long statementId, MySQLParamDesc paramDesc, boolean closeAfterUsage) {
4343
this.statementId = statementId;
4444
this.paramDesc = paramDesc;
45-
this.rowDesc = rowDesc;
4645
this.sql = sql;
4746
this.closeAfterUsage = closeAfterUsage;
4847

@@ -58,7 +57,7 @@ public ParamDesc paramDesc() {
5857

5958
@Override
6059
public RowDesc rowDesc() {
61-
return rowDesc;
60+
throw new UnsupportedOperationException("The client should use the column definitions provided by execute or fetch response instead of prepare response");
6261
}
6362

6463
@Override

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

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,6 @@
1818

1919
import io.netty.buffer.ByteBuf;
2020
import io.vertx.mysqlclient.impl.MySQLParamDesc;
21-
import io.vertx.mysqlclient.impl.MySQLRowDesc;
22-
import io.vertx.mysqlclient.impl.datatype.DataFormat;
2321
import io.vertx.mysqlclient.impl.protocol.ColumnDefinition;
2422
import io.vertx.mysqlclient.impl.protocol.CommandType;
2523
import io.vertx.sqlclient.impl.PreparedStatement;
@@ -135,7 +133,6 @@ private void handleReadyForQuery() {
135133
cmd.sql(),
136134
this.statementId,
137135
new MySQLParamDesc(paramDescs),
138-
MySQLRowDesc.create(columnDescs, DataFormat.BINARY),
139136
!cmd.isManaged())));
140137
}
141138

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,9 +94,13 @@ protected void handleResultsetColumnDefinitions(ByteBuf payload) {
9494
protected void handleResultsetColumnDefinitionsDecodingCompleted() {
9595
commandHandlerState = CommandHandlerState.HANDLING_ROW_DATA_OR_END_PACKET;
9696
MySQLRowDesc mySQLRowDesc = MySQLRowDesc.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(MySQLRowDesc 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/ResetStatementCommandCodec.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/mysqlclient/MySQLQueryTest.java

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,11 @@
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.sqlclient.*;
2021
import org.junit.After;
21-
import org.junit.Assume;
2222
import org.junit.Before;
2323
import org.junit.Test;
2424
import org.junit.runner.RunWith;
@@ -29,6 +29,8 @@
2929
import java.util.List;
3030
import java.util.concurrent.ThreadLocalRandom;
3131

32+
import static org.junit.Assume.assumeFalse;
33+
3234
@RunWith(VertxUnitRunner.class)
3335
public class MySQLQueryTest extends MySQLTestBase {
3436

@@ -221,7 +223,7 @@ public void testDecodePacketSizeMoreThan16MB(TestContext ctx) {
221223

222224
@Test
223225
public void testEncodePacketSizeMoreThan16MB(TestContext ctx) {
224-
Assume.assumeFalse(rule.isUsingMySQL5_6());
226+
assumeFalse(rule.isUsingMySQL5_6());
225227
int dataSize = 20 * 1024 * 1024; // 20MB payload
226228
byte[] data = new byte[dataSize];
227229
ThreadLocalRandom.current().nextBytes(data);
@@ -366,4 +368,22 @@ public void testLocalInfileRequestInPackets(TestContext ctx) {
366368
}));
367369
}));
368370
}
371+
372+
@Test
373+
public void testColumnDefinitionChangeWithCursor(TestContext ctx) {
374+
assumeFalse("Query syntax not supported on MySQL 5", rule.isUsingMySQL5());
375+
Async rows = ctx.async(100);
376+
Async completion = ctx.async();
377+
MySQLConnection.connect(vertx, options).onComplete(ctx.asyncAssertSuccess(conn -> {
378+
conn
379+
.prepare("SELECT row_number() over () as \"Number\", case when 1 then 1 else 0 end as \"Case\" FROM mysql.help_relation limit 0,100")
380+
.onComplete(ctx.asyncAssertSuccess(ps -> {
381+
// Make sure to fetch from the database twice
382+
RowStream<Row> stream = ps.createStream(50);
383+
stream.exceptionHandler(ctx::fail);
384+
stream.endHandler(v -> completion.complete());
385+
stream.handler(row -> rows.countDown());
386+
}));
387+
}));
388+
}
369389
}

vertx-mysql-client/src/test/java/io/vertx/mysqlclient/junit/MySQLRule.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,10 @@ public boolean isUsingMariaDB() {
142142
return databaseServerInfo.getDatabaseType() == DatabaseType.MariaDB;
143143
}
144144

145+
public boolean isUsingMySQL5() {
146+
return databaseServerInfo.databaseType == DatabaseType.MySQL && databaseServerInfo.dockerImageTag.startsWith("5.");
147+
}
148+
145149
public boolean isUsingMySQL5_6() {
146150
return databaseServerInfo == DatabaseServerInfo.MySQL_V5_6;
147151
}

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

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

0 commit comments

Comments
 (0)