Skip to content

Commit 0cea83b

Browse files
authored
rework MySQL prepared statement binding parameters (#587)
Signed-off-by: Billy Yuan <billy112487983@gmail.com>
1 parent e53235c commit 0cea83b

File tree

9 files changed

+69
-61
lines changed

9 files changed

+69
-61
lines changed

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

Lines changed: 0 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -11,49 +11,17 @@
1111

1212
package io.vertx.mysqlclient.impl;
1313

14-
import io.vertx.mysqlclient.impl.datatype.DataType;
15-
import io.vertx.mysqlclient.impl.datatype.DataTypeCodec;
1614
import io.vertx.mysqlclient.impl.protocol.ColumnDefinition;
17-
import io.vertx.sqlclient.impl.ErrorMessageFactory;
1815
import io.vertx.sqlclient.impl.ParamDesc;
19-
import io.vertx.sqlclient.impl.TupleInternal;
2016

2117
public class MySQLParamDesc extends ParamDesc {
2218
private final ColumnDefinition[] paramDefinitions;
23-
private boolean sendTypesToServer;
2419

2520
public MySQLParamDesc(ColumnDefinition[] paramDefinitions) {
2621
this.paramDefinitions = paramDefinitions;
27-
this.sendTypesToServer = false;
2822
}
2923

3024
public ColumnDefinition[] paramDefinitions() {
3125
return paramDefinitions;
3226
}
33-
34-
public String prepare(TupleInternal values) {
35-
int numberOfParameters = values.size();
36-
int paramDescLength = paramDefinitions.length;
37-
if (numberOfParameters != paramDescLength) {
38-
return ErrorMessageFactory.buildWhenArgumentsLengthNotMatched(paramDescLength, numberOfParameters);
39-
}
40-
41-
// binding the parameters
42-
boolean reboundParameters = false;
43-
for (int i = 0; i < values.size(); i++) {
44-
Object value = values.getValue(i);
45-
DataType dataType = DataTypeCodec.inferDataTypeByEncodingValue(value);
46-
DataType paramDataType = paramDefinitions[i].getType();
47-
if (paramDataType != dataType) {
48-
paramDefinitions[i].setType(dataType);
49-
reboundParameters = true;
50-
}
51-
}
52-
sendTypesToServer = reboundParameters; // parameter must be re-bound
53-
return null;
54-
}
55-
56-
public boolean sendTypesToServer() {
57-
return sendTypesToServer;
58-
}
5927
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -322,7 +322,7 @@ private GeometryCollection getGeometryCollection(int pos) {
322322
public LocalTime getLocalTime(int pos) {
323323
ColumnDefinition columnDefinition = rowDesc.columnDefinitions()[pos];
324324
Object val = getValue(pos);
325-
if (columnDefinition.getType() == DataType.TIME && val instanceof Duration) {
325+
if (columnDefinition.type() == DataType.TIME && val instanceof Duration) {
326326
// map MySQL TIME data type to java.time.LocalTime
327327
Duration duration = (Duration) val;
328328
return LocalTime.ofNanoOfDay(duration.toNanos());

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
import io.netty.buffer.ByteBuf;
1515
import io.vertx.mysqlclient.impl.datatype.DataType;
1616
import io.vertx.mysqlclient.impl.datatype.DataTypeCodec;
17-
import io.vertx.mysqlclient.impl.protocol.ColumnDefinition;
1817
import io.vertx.mysqlclient.impl.protocol.CommandType;
1918
import io.vertx.sqlclient.Tuple;
2019
import io.vertx.sqlclient.impl.command.CommandResponse;
@@ -78,8 +77,10 @@ private void sendBatchStatementExecuteCommand(MySQLPreparedStatement statement,
7877
// iteration count, always 1
7978
packet.writeIntLE(1);
8079

81-
ColumnDefinition[] paramsColumnDefinitions = statement.paramDesc.paramDefinitions();
82-
int numOfParams = paramsColumnDefinitions.length;
80+
/*
81+
* Null-bit map and type should always be reconstructed for every batch of parameters here
82+
*/
83+
int numOfParams = statement.bindingTypes().length;
8384
int bitmapLength = (numOfParams + 7) / 8;
8485
byte[] nullBitmap = new byte[bitmapLength];
8586

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

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

1919
import io.netty.buffer.ByteBuf;
20+
import io.vertx.mysqlclient.impl.datatype.DataType;
2021
import io.vertx.mysqlclient.impl.datatype.DataTypeCodec;
21-
import io.vertx.mysqlclient.impl.protocol.ColumnDefinition;
2222
import io.vertx.mysqlclient.impl.protocol.CommandType;
2323
import io.vertx.sqlclient.Tuple;
2424
import io.vertx.sqlclient.impl.command.ExtendedQueryCommand;
@@ -48,7 +48,7 @@ void encode(MySQLEncoder encoder) {
4848
sendStatementExecuteCommand(statement, true, cmd.params(), CURSOR_TYPE_READ_ONLY);
4949
} else {
5050
// CURSOR_TYPE_NO_CURSOR
51-
sendStatementExecuteCommand(statement, statement.paramDesc.sendTypesToServer(), cmd.params(), CURSOR_TYPE_NO_CURSOR);
51+
sendStatementExecuteCommand(statement, statement.sendTypesToServer(), cmd.params(), CURSOR_TYPE_NO_CURSOR);
5252
}
5353
}
5454
}
@@ -116,8 +116,7 @@ private void sendStatementExecuteCommand(MySQLPreparedStatement statement, boole
116116
// iteration count, always 1
117117
packet.writeIntLE(1);
118118

119-
ColumnDefinition[] paramsColumnDefinitions = statement.paramDesc.paramDefinitions();
120-
int numOfParams = paramsColumnDefinitions.length;
119+
int numOfParams = statement.bindingTypes().length;
121120
int bitmapLength = (numOfParams + 7) / 8;
122121
byte[] nullBitmap = new byte[bitmapLength];
123122

@@ -129,16 +128,16 @@ private void sendStatementExecuteCommand(MySQLPreparedStatement statement, boole
129128
packet.writeBoolean(sendTypesToServer);
130129

131130
if (sendTypesToServer) {
132-
for (ColumnDefinition paramsColumnDefinition : paramsColumnDefinitions) {
133-
packet.writeByte(paramsColumnDefinition.getType().id);
131+
for (DataType bindingType : statement.bindingTypes()) {
132+
packet.writeByte(bindingType.id);
134133
packet.writeByte(0); // parameter flag: signed
135134
}
136135
}
137136

138137
for (int i = 0; i < numOfParams; i++) {
139138
Object value = params.getValue(i);
140139
if (value != null) {
141-
DataTypeCodec.encodeBinary(paramsColumnDefinitions[i].getType(), value, encoder.encodingCharset, packet);
140+
DataTypeCodec.encodeBinary(statement.bindingTypes()[i], value, encoder.encodingCharset, packet);
142141
} else {
143142
nullBitmap[i / 8] |= (1 << (i & 7));
144143
}

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

Lines changed: 48 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,11 @@
1919

2020
import io.vertx.mysqlclient.impl.MySQLParamDesc;
2121
import io.vertx.mysqlclient.impl.MySQLRowDesc;
22-
import io.vertx.sqlclient.impl.ParamDesc;
23-
import io.vertx.sqlclient.impl.PreparedStatement;
24-
import io.vertx.sqlclient.impl.RowDesc;
25-
import io.vertx.sqlclient.impl.TupleInternal;
22+
import io.vertx.mysqlclient.impl.datatype.DataType;
23+
import io.vertx.mysqlclient.impl.datatype.DataTypeCodec;
24+
import io.vertx.sqlclient.impl.*;
25+
26+
import java.util.Arrays;
2627

2728
class MySQLPreparedStatement implements PreparedStatement {
2829

@@ -32,6 +33,9 @@ class MySQLPreparedStatement implements PreparedStatement {
3233
final MySQLRowDesc rowDesc;
3334
final boolean cacheable;
3435

36+
private boolean sendTypesToServer;
37+
private final DataType[] bindingTypes;
38+
3539
boolean isCursorOpen;
3640

3741
MySQLPreparedStatement(String sql, long statementId, MySQLParamDesc paramDesc, MySQLRowDesc rowDesc, boolean cacheable) {
@@ -40,6 +44,10 @@ class MySQLPreparedStatement implements PreparedStatement {
4044
this.rowDesc = rowDesc;
4145
this.sql = sql;
4246
this.cacheable = cacheable;
47+
48+
this.bindingTypes = new DataType[paramDesc.paramDefinitions().length];
49+
// init param bindings
50+
cleanBindings();
4351
}
4452

4553
@Override
@@ -59,7 +67,42 @@ public String sql() {
5967

6068
@Override
6169
public String prepare(TupleInternal values) {
62-
return paramDesc.prepare(values);
70+
return bindParameters(paramDesc, values);
71+
}
72+
73+
boolean sendTypesToServer() {
74+
return sendTypesToServer;
75+
}
76+
77+
DataType[] bindingTypes() {
78+
return bindingTypes;
79+
}
80+
81+
void cleanBindings() {
82+
this.sendTypesToServer = true;
83+
Arrays.fill(bindingTypes, DataType.UNBIND);
84+
}
85+
86+
private String bindParameters(MySQLParamDesc paramDesc, TupleInternal params) {
87+
int numberOfParameters = params.size();
88+
int paramDescLength = paramDesc.paramDefinitions().length;
89+
if (numberOfParameters != paramDescLength) {
90+
return ErrorMessageFactory.buildWhenArgumentsLengthNotMatched(paramDescLength, numberOfParameters);
91+
}
92+
93+
// binding the parameters
94+
boolean reboundParameters = false;
95+
for (int i = 0; i < params.size(); i++) {
96+
Object value = params.getValue(i);
97+
DataType dataType = DataTypeCodec.inferDataTypeByEncodingValue(value);
98+
DataType paramDataType = bindingTypes[i];
99+
if (paramDataType != dataType) {
100+
bindingTypes[i] = dataType;
101+
reboundParameters = true;
102+
}
103+
}
104+
sendTypesToServer = reboundParameters; // parameter must be re-bound
105+
return null;
63106
}
64107

65108
@Override

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,26 +26,26 @@ class ResetStatementCommandCodec extends CommandCodec<Void, CloseCursorCommand>
2626
void encode(MySQLEncoder encoder) {
2727
super.encode(encoder);
2828
MySQLPreparedStatement statement = (MySQLPreparedStatement) cmd.statement();
29+
statement.cleanBindings();
2930

3031
statement.isCursorOpen = false;
31-
32-
sendStatementResetCommand(statement);
32+
sendStatementResetCommand(statement.statementId);
3333
}
3434

3535
@Override
3636
void decodePayload(ByteBuf payload, int payloadLength) {
3737
handleOkPacketOrErrorPacketPayload(payload);
3838
}
3939

40-
private void sendStatementResetCommand(MySQLPreparedStatement statement) {
40+
private void sendStatementResetCommand(long statementId) {
4141
ByteBuf packet = allocateBuffer(PAYLOAD_LENGTH + 4);
4242
// encode packet header
4343
packet.writeMediumLE(PAYLOAD_LENGTH);
4444
packet.writeByte(encoder.sequenceId);
4545

4646
// encode packet payload
4747
packet.writeByte(CommandType.COM_STMT_RESET);
48-
packet.writeIntLE((int) statement.statementId);
48+
packet.writeIntLE((int) statementId);
4949

5050
sendNonSplitPacket(packet);
5151
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ protected Row decodeRow(int len, ByteBuf in) {
5555
if (nullByte == 0) {
5656
// non-null
5757
ColumnDefinition columnDef = rowDesc.columnDefinitions()[c];
58-
DataType dataType = columnDef.getType();
58+
DataType dataType = columnDef.type();
5959
int collationId = rowDesc.columnDefinitions()[c].characterSet();
6060
int columnDefinitionFlags = columnDef.flags();
6161
decoded = DataTypeCodec.decodeBinary(dataType, collationId, columnDefinitionFlags, in);
@@ -69,7 +69,7 @@ protected Row decodeRow(int len, ByteBuf in) {
6969
if (in.getUnsignedByte(in.readerIndex()) == NULL) {
7070
in.skipBytes(1);
7171
} else {
72-
DataType dataType = rowDesc.columnDefinitions()[c].getType();
72+
DataType dataType = rowDesc.columnDefinitions()[c].type();
7373
int columnDefinitionFlags = rowDesc.columnDefinitions()[c].flags();
7474
int collationId = rowDesc.columnDefinitions()[c].characterSet();
7575
decoded = DataTypeCodec.decodeText(dataType, collationId, columnDefinitionFlags, in);

vertx-mysql-client/src/main/java/io/vertx/mysqlclient/impl/datatype/DataType.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,8 @@ public enum DataType {
4747
BIT(ColumnDefinition.ColumnType.MYSQL_TYPE_BIT, Long.class, Long.class),
4848
JSON(ColumnDefinition.ColumnType.MYSQL_TYPE_JSON, Object.class, Object.class),
4949
GEOMETRY(ColumnDefinition.ColumnType.MYSQL_TYPE_GEOMETRY, Geometry.class, Geometry.class),
50-
NULL(ColumnDefinition.ColumnType.MYSQL_TYPE_NULL, Object.class, Object.class); // useful for mariadb prepare statement response
50+
NULL(ColumnDefinition.ColumnType.MYSQL_TYPE_NULL, Object.class, Object.class), // useful for mariadb prepare statement response
51+
UNBIND(-1, Object.class, Object.class); // useful for binding param values
5152

5253
private static final Logger LOGGER = LoggerFactory.getLogger(DataType.class);
5354

vertx-mysql-client/src/main/java/io/vertx/mysqlclient/impl/protocol/ColumnDefinition.java

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ public final class ColumnDefinition {
2525
private final String orgName;
2626
private final int characterSet;
2727
private final long columnLength;
28-
private DataType type;
28+
private final DataType type;
2929
private final int flags;
3030
private final byte decimals;
3131

@@ -85,14 +85,10 @@ public long columnLength() {
8585
return columnLength;
8686
}
8787

88-
public DataType getType() {
88+
public DataType type() {
8989
return type;
9090
}
9191

92-
public void setType(DataType type) {
93-
this.type = type;
94-
}
95-
9692
public int flags() {
9793
return flags;
9894
}

0 commit comments

Comments
 (0)