This is an automated email from the ASF dual-hosted git repository.
jiangML pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/shardingsphere.git
The following commit(s) were added to refs/heads/master by this push:
new 82d63200491 Fix incorrect BLOB data writing under MySQL protocol in
Proxy (#39072)
82d63200491 is described below
commit 82d632004917020166f2b9be949ace1bc4f3de90
Author: Raigor <[email protected]>
AuthorDate: Fri Jul 10 22:52:09 2026 +0800
Fix incorrect BLOB data writing under MySQL protocol in Proxy (#39072)
* Fix incorrect BLOB data writing under MySQL protocol in Proxy
* Update RELEASE-NOTES.md
---
RELEASE-NOTES.md | 1 +
.../binary/execute/MySQLComStmtExecutePacket.java | 24 ++++++++++++-
.../execute/MySQLComStmtExecutePacketTest.java | 40 +++++++++++++++++++---
3 files changed, 59 insertions(+), 6 deletions(-)
diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md
index 0e0cdefcf81..863215e80fa 100644
--- a/RELEASE-NOTES.md
+++ b/RELEASE-NOTES.md
@@ -38,6 +38,7 @@
1. Sharding: Fix AUTO_INTERVAL sharding failure under JVM default locales that
use comma decimal separators -
[#38806](https://github.com/apache/shardingsphere/pull/38806)
1. DistSQL: Fix case-sensitive storage unit matching in `SHOW RULES USED
STORAGE UNIT` - [#38848](https://github.com/apache/shardingsphere/pull/38848)
1. Sharding: Compute the Snowflake key generator epoch in UTC instead of the
JVM default timezone -
[#38932](https://github.com/apache/shardingsphere/pull/38932)
+1. Proxy: Fix MySQL BLOB data corruption when string-like prepared statement
parameters target BLOB columns -
[#39072](https://github.com/apache/shardingsphere/pull/39072)
### Enhancements
diff --git
a/database/protocol/dialect/mysql/src/main/java/org/apache/shardingsphere/database/protocol/mysql/packet/command/query/binary/execute/MySQLComStmtExecutePacket.java
b/database/protocol/dialect/mysql/src/main/java/org/apache/shardingsphere/database/protocol/mysql/packet/command/query/binary/execute/MySQLComStmtExecutePacket.java
index b8c33d92bde..d8289e7ee04 100644
---
a/database/protocol/dialect/mysql/src/main/java/org/apache/shardingsphere/database/protocol/mysql/packet/command/query/binary/execute/MySQLComStmtExecutePacket.java
+++
b/database/protocol/dialect/mysql/src/main/java/org/apache/shardingsphere/database/protocol/mysql/packet/command/query/binary/execute/MySQLComStmtExecutePacket.java
@@ -111,13 +111,35 @@ public final class MySQLComStmtExecutePacket extends
MySQLCommandPacket {
MySQLBinaryProtocolValue binaryProtocolValue =
MySQLBinaryProtocolValueFactory.getBinaryProtocolValue(parameterType);
Object value = nullBitmap.isNullParameter(paramIndex)
? null
- : binaryProtocolValue.read(payload,
(parameterFlags.get(paramIndex) &
MySQLColumnDefinitionFlag.UNSIGNED.getValue()) ==
MySQLColumnDefinitionFlag.UNSIGNED.getValue());
+ : readParameterValue(binaryProtocolValue,
parameterColumnTypes, paramIndex, parameterType, parameterFlags);
value = decodeStringParameterValue(parameterColumnTypes,
paramIndex, parameterType, value);
result.add(value);
}
return result;
}
+ private Object readParameterValue(final MySQLBinaryProtocolValue
binaryProtocolValue, final List<MySQLBinaryColumnType> parameterColumnTypes,
final int paramIndex,
+ final MySQLBinaryColumnType
parameterType, final List<Integer> parameterFlags) throws SQLException {
+ if (isStringParameterType(parameterType) &&
isBinaryColumnType(parameterColumnTypes, paramIndex)) {
+ return payload.readStringLenencByBytes();
+ }
+ return binaryProtocolValue.read(payload,
(parameterFlags.get(paramIndex) &
MySQLColumnDefinitionFlag.UNSIGNED.getValue()) ==
MySQLColumnDefinitionFlag.UNSIGNED.getValue());
+ }
+
+ private MySQLBinaryColumnType getParameterColumnType(final
List<MySQLBinaryColumnType> parameterColumnTypes, final int paramIndex) {
+ return paramIndex < parameterColumnTypes.size() ?
parameterColumnTypes.get(paramIndex) : null;
+ }
+
+ private boolean isStringParameterType(final MySQLBinaryColumnType
parameterType) {
+ return MySQLBinaryColumnType.STRING == parameterType ||
MySQLBinaryColumnType.VAR_STRING == parameterType ||
MySQLBinaryColumnType.VARCHAR == parameterType;
+ }
+
+ private boolean isBinaryColumnType(final List<MySQLBinaryColumnType>
parameterColumnTypes, final int paramIndex) {
+ MySQLBinaryColumnType columnType =
getParameterColumnType(parameterColumnTypes, paramIndex);
+ return MySQLBinaryColumnType.LONG_BLOB == columnType ||
MySQLBinaryColumnType.MEDIUM_BLOB == columnType
+ || MySQLBinaryColumnType.BLOB == columnType ||
MySQLBinaryColumnType.TINY_BLOB == columnType;
+ }
+
private Object decodeStringParameterValue(final
List<MySQLBinaryColumnType> parameterColumnTypes, final int paramIndex, final
MySQLBinaryColumnType parameterType, final Object value) {
if (!(value instanceof byte[]) || MySQLBinaryColumnType.STRING !=
parameterType || !isCharacterColumnType(parameterColumnTypes, paramIndex)) {
return value;
diff --git
a/database/protocol/dialect/mysql/src/test/java/org/apache/shardingsphere/database/protocol/mysql/packet/command/query/binary/execute/MySQLComStmtExecutePacketTest.java
b/database/protocol/dialect/mysql/src/test/java/org/apache/shardingsphere/database/protocol/mysql/packet/command/query/binary/execute/MySQLComStmtExecutePacketTest.java
index 35c53341bbf..c7910017c9b 100644
---
a/database/protocol/dialect/mysql/src/test/java/org/apache/shardingsphere/database/protocol/mysql/packet/command/query/binary/execute/MySQLComStmtExecutePacketTest.java
+++
b/database/protocol/dialect/mysql/src/test/java/org/apache/shardingsphere/database/protocol/mysql/packet/command/query/binary/execute/MySQLComStmtExecutePacketTest.java
@@ -110,12 +110,41 @@ class MySQLComStmtExecutePacketTest {
assertThat(actual.readParameters(parameterTypes,
Collections.singleton(0), Collections.emptyList(), Collections.emptyList()),
is(Collections.singletonList(null)));
}
+ @ParameterizedTest(name = "{0}")
+ @MethodSource("stringParameterTypesBoundToBlobColumnsArguments")
+ void assertReadParametersWithStringTypeBoundToBlobColumn(final String
name, final MySQLBinaryColumnType parameterType,
+ final
MySQLBinaryColumnType parameterColumnType) throws SQLException {
+ byte[] data = createPacketData(parameterType, new byte[]{(byte) 0xac,
(byte) 0xed, (byte) 0xff});
+ MySQLPacketPayload payload = new
MySQLPacketPayload(Unpooled.wrappedBuffer(data), StandardCharsets.UTF_8);
+ MySQLComStmtExecutePacket actual = new
MySQLComStmtExecutePacket(payload, 1);
+ List<MySQLPreparedStatementParameterType> parameterTypes =
actual.getNewParameterTypes();
+ Object actualValue = actual.readParameters(parameterTypes,
Collections.emptySet(), Collections.singletonList(0),
Collections.singletonList(parameterColumnType)).get(0);
+ assertTrue(actualValue instanceof byte[]);
+ assertArrayEquals(new byte[]{(byte) 0xac, (byte) 0xed, (byte) 0xff},
(byte[]) actualValue);
+ }
+
+ private static Stream<Arguments>
stringParameterTypesBoundToBlobColumnsArguments() {
+ return Stream.of(
+ Arguments.of("string-parameter-bound-to-tiny-blob-column",
MySQLBinaryColumnType.STRING, MySQLBinaryColumnType.TINY_BLOB),
+ Arguments.of("string-parameter-bound-to-blob-column",
MySQLBinaryColumnType.STRING, MySQLBinaryColumnType.BLOB),
+ Arguments.of("string-parameter-bound-to-medium-blob-column",
MySQLBinaryColumnType.STRING, MySQLBinaryColumnType.MEDIUM_BLOB),
+ Arguments.of("string-parameter-bound-to-long-blob-column",
MySQLBinaryColumnType.STRING, MySQLBinaryColumnType.LONG_BLOB),
+ Arguments.of("var-string-parameter-bound-to-tiny-blob-column",
MySQLBinaryColumnType.VAR_STRING, MySQLBinaryColumnType.TINY_BLOB),
+ Arguments.of("var-string-parameter-bound-to-blob-column",
MySQLBinaryColumnType.VAR_STRING, MySQLBinaryColumnType.BLOB),
+
Arguments.of("var-string-parameter-bound-to-medium-blob-column",
MySQLBinaryColumnType.VAR_STRING, MySQLBinaryColumnType.MEDIUM_BLOB),
+ Arguments.of("var-string-parameter-bound-to-long-blob-column",
MySQLBinaryColumnType.VAR_STRING, MySQLBinaryColumnType.LONG_BLOB),
+ Arguments.of("varchar-parameter-bound-to-tiny-blob-column",
MySQLBinaryColumnType.VARCHAR, MySQLBinaryColumnType.TINY_BLOB),
+ Arguments.of("varchar-parameter-bound-to-blob-column",
MySQLBinaryColumnType.VARCHAR, MySQLBinaryColumnType.BLOB),
+ Arguments.of("varchar-parameter-bound-to-medium-blob-column",
MySQLBinaryColumnType.VARCHAR, MySQLBinaryColumnType.MEDIUM_BLOB),
+ Arguments.of("varchar-parameter-bound-to-long-blob-column",
MySQLBinaryColumnType.VARCHAR, MySQLBinaryColumnType.LONG_BLOB));
+ }
+
@DisplayName("assertReadParametersWithStringDecoding")
@ParameterizedTest(name = "{0}")
@MethodSource("stringDecodingArguments")
void assertReadParametersWithStringDecoding(final String name, final
MySQLBinaryColumnType parameterType,
final
List<MySQLBinaryColumnType> parameterColumnTypes, final boolean expectedString)
throws SQLException {
- byte[] data = createPacketData(parameterType);
+ byte[] data = createPacketData(parameterType, new byte[]{0x61});
MySQLPacketPayload payload = new
MySQLPacketPayload(Unpooled.wrappedBuffer(data), StandardCharsets.UTF_8);
MySQLComStmtExecutePacket actual = new
MySQLComStmtExecutePacket(payload, 1);
List<MySQLPreparedStatementParameterType> parameterTypes =
actual.getNewParameterTypes();
@@ -134,7 +163,6 @@ class MySQLComStmtExecutePacketTest {
Arguments.of("string-column", MySQLBinaryColumnType.STRING,
Collections.singletonList(MySQLBinaryColumnType.STRING), true),
Arguments.of("var-string-column",
MySQLBinaryColumnType.STRING,
Collections.singletonList(MySQLBinaryColumnType.VAR_STRING), true),
Arguments.of("varchar-column", MySQLBinaryColumnType.STRING,
Collections.singletonList(MySQLBinaryColumnType.VARCHAR), true),
- Arguments.of("blob-column", MySQLBinaryColumnType.STRING,
Collections.singletonList(MySQLBinaryColumnType.BLOB), false),
Arguments.of("missing-column-type",
MySQLBinaryColumnType.STRING, Collections.emptyList(), false),
Arguments.of("blob-parameter-type",
MySQLBinaryColumnType.BLOB,
Collections.singletonList(MySQLBinaryColumnType.VAR_STRING), false));
}
@@ -146,7 +174,7 @@ class MySQLComStmtExecutePacketTest {
Arguments.of("statement-id-256", new byte[]{0x00, 0x01, 0x00,
0x00, 0x00, 0x01, 0x00, 0x00, 0x00}, 256, 0));
}
- private byte[] createPacketData(final MySQLBinaryColumnType parameterType)
{
+ private byte[] createPacketData(final MySQLBinaryColumnType parameterType,
final byte[] value) {
List<Byte> result = new ArrayList<>();
byte[] fixedPrefix = {0x01, 0x00, 0x00, 0x00, 0x09, 0x01, 0x00, 0x00,
0x00, 0x00, 0x01};
for (byte each : fixedPrefix) {
@@ -154,8 +182,10 @@ class MySQLComStmtExecutePacketTest {
}
result.add((byte) parameterType.getValue());
result.add((byte) 0x00);
- result.add((byte) 0x01);
- result.add((byte) 0x61);
+ result.add((byte) value.length);
+ for (byte each : value) {
+ result.add(each);
+ }
byte[] bytes = new byte[result.size()];
for (int i = 0; i < result.size(); i++) {
bytes[i] = result.get(i);