Skip to content

Commit bf0ecd1

Browse files
committed
Fix for Bug#45554 (Bug#11754018), Connector/J does not encode binary data if useServerPrepStatements=false.
Change-Id: I9699429d9e7c29d43d2bb1c28cd928c4fe972930
1 parent a716971 commit bf0ecd1

File tree

5 files changed

+94
-112
lines changed

5 files changed

+94
-112
lines changed

CHANGES

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

44
Version 9.5.0
55

6+
- Fix for Bug#45554 (Bug#11754018), Connector/J does not encode binary data if useServerPrepStatements=false.
7+
68
- Fix for Bug#114974 (Bug#36614381), the SQL in batch will not clear after statement close.
79
Thanks to Chengyi Dong for his contribution.
810

src/main/protocol-impl/java/com/mysql/cj/protocol/a/InputStreamValueEncoder.java

Lines changed: 7 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ public void encodeAsBinary(Message msg, BindValue binding) {
6161

6262
protected byte[] streamToBytes(InputStream in, long length, NativePacketPayload packet) {
6363
boolean useLength = length == -1 ? false : this.propertySet.getBooleanProperty(PropertyKey.useStreamLengthsInPrepStmts).getValue();
64-
in.mark(Integer.MAX_VALUE); // we may need to read this same stream several times, so we need to reset it at the end.
64+
in.mark(Integer.MAX_VALUE); // We may need to read this same stream several times, so we need to reset it at the end.
6565
try {
6666
if (this.streamConvertBuf == null) {
6767
this.streamConvertBuf = new byte[4096];
@@ -71,27 +71,21 @@ protected byte[] streamToBytes(InputStream in, long length, NativePacketPayload
7171
int lengthLeftToRead = (int) (length - bcnt);
7272

7373
ByteArrayOutputStream bytesOut = null;
74-
boolean hexEscape = false;
7574
if (packet == null) {
7675
bytesOut = new ByteArrayOutputStream();
7776
} else {
78-
hexEscape = this.serverSession.isNoBackslashEscapesSet();
79-
packet.writeBytes(StringLengthDataType.STRING_FIXED, StringUtils.getBytes(hexEscape ? "x" : "_binary"));
77+
packet.writeBytes(StringLengthDataType.STRING_FIXED, StringUtils.getBytes("X"));
8078
packet.writeInteger(IntegerDataType.INT1, (byte) '\'');
8179
}
8280

8381
while (bcnt > 0) {
8482
if (packet == null) {
8583
bytesOut.write(this.streamConvertBuf, 0, bcnt);
8684
} else {
87-
if (hexEscape) {
88-
StringUtils.hexEscapeBlock(this.streamConvertBuf, bcnt, (lowBits, highBits) -> {
89-
packet.writeInteger(IntegerDataType.INT1, lowBits);
90-
packet.writeInteger(IntegerDataType.INT1, highBits);
91-
});
92-
} else {
93-
escapeblockFast(this.streamConvertBuf, packet, bcnt);
94-
}
85+
StringUtils.hexEscapeBlock(this.streamConvertBuf, bcnt, (lowBits, highBits) -> {
86+
packet.writeInteger(IntegerDataType.INT1, lowBits);
87+
packet.writeInteger(IntegerDataType.INT1, highBits);
88+
});
9589
}
9690

9791
if (useLength) {
@@ -127,40 +121,4 @@ protected byte[] streamToBytes(InputStream in, long length, NativePacketPayload
127121
}
128122
}
129123

130-
private final void escapeblockFast(byte[] buf, NativePacketPayload packet, int size) {
131-
int lastwritten = 0;
132-
133-
for (int i = 0; i < size; i++) {
134-
byte b = buf[i];
135-
136-
if (b == '\0') {
137-
// write stuff not yet written
138-
if (i > lastwritten) {
139-
packet.writeBytes(StringLengthDataType.STRING_FIXED, buf, lastwritten, i - lastwritten);
140-
}
141-
142-
// write escape
143-
packet.writeInteger(IntegerDataType.INT1, (byte) '\\');
144-
packet.writeInteger(IntegerDataType.INT1, (byte) '0');
145-
lastwritten = i + 1;
146-
} else {
147-
if (b == '\\' || b == '\'') {
148-
// write stuff not yet written
149-
if (i > lastwritten) {
150-
packet.writeBytes(StringLengthDataType.STRING_FIXED, buf, lastwritten, i - lastwritten);
151-
}
152-
153-
// write escape
154-
packet.writeInteger(IntegerDataType.INT1, b);
155-
lastwritten = i; // not i+1 as b wasn't written.
156-
}
157-
}
158-
}
159-
160-
// write out remaining stuff from buffer
161-
if (lastwritten < size) {
162-
packet.writeBytes(StringLengthDataType.STRING_FIXED, buf, lastwritten, size - lastwritten);
163-
}
164-
}
165-
166-
}
124+
}

src/test/java/testsuite/BaseTestCase.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -830,9 +830,7 @@ protected String randomString() {
830830

831831
protected void cleanupTempFiles(final File exampleTempFile, final String tempfilePrefix) {
832832
File tempfilePath = exampleTempFile.getParentFile();
833-
834833
File[] possibleFiles = tempfilePath.listFiles((dir, name) -> name.indexOf(tempfilePrefix) != -1 && !exampleTempFile.getName().equals(name));
835-
836834
if (possibleFiles != null) {
837835
for (int i = 0; i < possibleFiles.length; i++) {
838836
try {

src/test/java/testsuite/regression/StatementRegressionTest.java

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
import java.io.FileWriter;
4343
import java.io.IOException;
4444
import java.io.InputStream;
45+
import java.io.OutputStream;
4546
import java.io.PrintStream;
4647
import java.io.Reader;
4748
import java.io.StringReader;
@@ -51,6 +52,7 @@
5152
import java.math.BigInteger;
5253
import java.net.URL;
5354
import java.nio.file.Files;
55+
import java.nio.file.Path;
5456
import java.nio.file.Paths;
5557
import java.sql.Array;
5658
import java.sql.BatchUpdateException;
@@ -14402,4 +14404,47 @@ public void testBug114974() throws Exception {
1440214404
assertFalse(this.rs.next());
1440314405
}
1440414406

14407+
/**
14408+
* Tests fix for Bug#45554 (Bug#11754018), Connector/J does not encode binary data if useServerPrepStatements=false.
14409+
*
14410+
* @throws Exception
14411+
*/
14412+
@Test
14413+
public void testBug45554() throws Exception {
14414+
final String testString = "€\\\\";
14415+
final byte[] testStringBytes = testString.getBytes();
14416+
14417+
Path filePath = Files.createTempFile("testBug45554", ".dat");
14418+
OutputStream os = Files.newOutputStream(filePath);
14419+
os.write(testString.getBytes());
14420+
os.close();
14421+
14422+
boolean useSPS = false;
14423+
String[] charSets = new String[] { "UTF8", "GBK", "SJIS" };
14424+
14425+
do {
14426+
for (String cs : charSets) {
14427+
String testCase = String.format("Case [useSPS: %s, charset: %s]", useSPS ? "Y" : "N", cs);
14428+
14429+
Properties props = new Properties();
14430+
props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), Boolean.toString(useSPS));
14431+
props.setProperty(PropertyKey.characterEncoding.getKeyName(), cs);
14432+
14433+
try (Connection testConn = getConnectionWithProps(props)) {
14434+
this.rs = this.stmt.executeQuery("SELECT '" + testString.replace("\\", "\\\\") + "'");
14435+
assertTrue(this.rs.next(), testCase);
14436+
assertArrayEquals(testStringBytes, this.rs.getBytes(1), testCase);
14437+
14438+
InputStream is = Files.newInputStream(filePath);
14439+
this.pstmt = testConn.prepareStatement("SELECT ?");
14440+
this.pstmt.setBinaryStream(1, is);
14441+
this.rs = this.pstmt.executeQuery();
14442+
assertTrue(this.rs.next(), testCase);
14443+
assertArrayEquals(testStringBytes, this.rs.getBytes(1), testCase);
14444+
is.close();
14445+
}
14446+
}
14447+
} while (useSPS = !useSPS);
14448+
}
14449+
1440514450
}

src/test/java/testsuite/simple/ConnectionTest.java

Lines changed: 40 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626
import static org.junit.jupiter.api.Assertions.assertNotNull;
2727
import static org.junit.jupiter.api.Assertions.assertTrue;
2828
import static org.junit.jupiter.api.Assertions.fail;
29-
import static org.junit.jupiter.api.Assumptions.assumeFalse;
3029
import static org.junit.jupiter.api.Assumptions.assumeTrue;
3130

3231
import java.io.BufferedInputStream;
@@ -849,39 +848,22 @@ public void testUseCompress() throws Exception {
849848
this.rs = this.stmt.executeQuery("SHOW VARIABLES LIKE 'max_allowed_packet'");
850849
this.rs.next();
851850
long defaultMaxAllowedPacket = this.rs.getInt(2);
852-
boolean changeMaxAllowedPacket = defaultMaxAllowedPacket < 4 + 1024 * 1024 * 32 - 1;
853-
854-
if (versionMeetsMinimum(5, 6, 20) && !versionMeetsMinimum(5, 7)) {
855-
/*
856-
* The 5.6.20 patch for Bug #16963396, Bug #19030353, Bug #69477 limits the size of redo log BLOB writes
857-
* to 10% of the redo log file size. The 5.7.5 patch addresses the bug without imposing a limitation.
858-
* As a result of the redo log BLOB write limit introduced for MySQL 5.6, innodb_log_file_size should be set to a value
859-
* greater than 10 times the largest BLOB data size found in the rows of your tables plus the length of other variable length
860-
* fields (VARCHAR, VARBINARY, and TEXT type fields).
861-
*/
862-
this.rs = this.stmt.executeQuery("SHOW VARIABLES LIKE 'innodb_log_file_size'");
863-
this.rs.next();
864-
assumeFalse(this.rs.getInt(2) < 1024 * 1024 * 32 * 10,
865-
"You need to increase innodb_log_file_size to at least " + 1024 * 1024 * 32 * 10 + " before running this test!");
866-
}
851+
852+
createTable("testUseCompress", "(pos INT PRIMARY KEY AUTO_INCREMENT, blobdata LONGBLOB)");
867853

868854
try {
869-
if (changeMaxAllowedPacket) {
870-
this.stmt.executeUpdate("SET GLOBAL max_allowed_packet=" + 1024 * 1024 * 33);
871-
}
855+
this.stmt.executeUpdate("SET GLOBAL max_allowed_packet=" + 1024 * 1024 * 33);
872856

873-
testCompressionWith("false", 1024 * 1024 * 16 - 2); // no split
874-
testCompressionWith("false", 1024 * 1024 * 16 - 1); // split with additional empty packet
875-
testCompressionWith("false", 1024 * 1024 * 32); // big payload
857+
testUseCompress("false", 1024 * 1024 * 16 - 2); // No split.
858+
testUseCompress("false", 1024 * 1024 * 16 - 1); // Split with additional empty packet.
859+
testUseCompress("false", 1024 * 1024 * 32); // Big payload.
876860

877-
testCompressionWith("true", 1024 * 1024 * 16 - 2 - 3); // no split, one compressed packet
878-
testCompressionWith("true", 1024 * 1024 * 16 - 2 - 2); // no split, two compressed packets
879-
testCompressionWith("true", 1024 * 1024 * 16 - 1); // split with additional empty packet, two compressed packets
880-
testCompressionWith("true", 1024 * 1024 * 32); // big payload
861+
testUseCompress("true", 1024 * 1024 * 16 - 2 - 3); // No split, one compressed packet.
862+
testUseCompress("true", 1024 * 1024 * 16 - 2 - 2); // No split, two compressed packets.
863+
testUseCompress("true", 1024 * 1024 * 16 - 1); // Split with additional empty packet, two compressed packets.
864+
testUseCompress("true", 1024 * 1024 * 32); // Big payload.
881865
} finally {
882-
if (changeMaxAllowedPacket) {
883-
this.stmt.executeUpdate("SET GLOBAL max_allowed_packet=" + defaultMaxAllowedPacket);
884-
}
866+
this.stmt.executeUpdate("SET GLOBAL max_allowed_packet=" + defaultMaxAllowedPacket);
885867
}
886868
}
887869

@@ -891,62 +873,59 @@ public void testUseCompress() throws Exception {
891873
*
892874
* @throws Exception
893875
*/
894-
private void testCompressionWith(String useCompression, int maxPayloadSize) throws Exception {
895-
String sqlToSend = "INSERT INTO BLOBTEST(blobdata) VALUES (?)";
896-
int requiredSize = maxPayloadSize - sqlToSend.length() - "_binary''".length();
876+
private void testUseCompress(String useCompression, int maxPayloadSize) throws Exception {
877+
this.stmt.executeUpdate("TRUNCATE TABLE testUseCompress");
897878

898-
File testBlobFile = File.createTempFile("cmj-testblob", ".dat");
899-
testBlobFile.deleteOnExit();
879+
String sqlToSend = "INSERT INTO testUseCompress (blobdata) VALUES (?)";
880+
int remainingSize = maxPayloadSize - sqlToSend.length() - "X''".length() - 2 /* parameter_count & parameter_set_count */;
881+
if (remainingSize % 2 != 0) {
882+
sqlToSend += " ";
883+
remainingSize--;
884+
}
885+
int requiredSize = remainingSize / 2; // HEX requires twice the size of the data.
900886

901-
// TODO: following cleanup doesn't work correctly during concurrent execution of testsuite
902-
// cleanupTempFiles(testBlobFile, "cmj-testblob");
887+
File testBlobFile = File.createTempFile("testUseCompress", ".dat");
888+
testBlobFile.deleteOnExit();
903889

904890
BufferedOutputStream bOut = new BufferedOutputStream(new FileOutputStream(testBlobFile));
905-
906-
// generate a random sequence of letters. this ensures that no escaped characters cause packet sizes that interfere with bounds tests
891+
// Generate a random sequence of letters.
907892
Random random = new Random();
908893
for (int i = 0; i < requiredSize; i++) {
909894
bOut.write((byte) (65 + random.nextInt(26)));
910895
}
911-
912896
bOut.flush();
913897
bOut.close();
914898

915899
Properties props = new Properties();
916900
props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name());
917901
props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true");
918902
props.setProperty(PropertyKey.useCompression.getKeyName(), useCompression);
919-
Connection conn1 = getConnectionWithProps(props);
920-
Statement stmt1 = conn1.createStatement();
903+
Connection testConn = getConnectionWithProps(props);
921904

922-
createTable("BLOBTEST", "(pos int PRIMARY KEY auto_increment, blobdata LONGBLOB)");
905+
PreparedStatement testPstmt = testConn.prepareStatement(sqlToSend);
923906
BufferedInputStream bIn = new BufferedInputStream(new FileInputStream(testBlobFile));
907+
testPstmt.setBinaryStream(1, bIn, (int) testBlobFile.length());
908+
testPstmt.execute();
909+
testPstmt.clearParameters();
910+
bIn.close();
924911

925-
this.pstmt = conn1.prepareStatement(sqlToSend);
926-
927-
this.pstmt.setBinaryStream(1, bIn, (int) testBlobFile.length());
928-
this.pstmt.execute();
929-
this.pstmt.clearParameters();
930-
931-
this.rs = stmt1.executeQuery("SELECT blobdata from BLOBTEST LIMIT 1");
912+
Statement testStmt = testConn.createStatement();
913+
this.rs = testStmt.executeQuery("SELECT blobdata FROM testUseCompress LIMIT 1");
932914
this.rs.next();
933915
InputStream is = this.rs.getBinaryStream(1);
934916

935-
bIn.close();
936917
bIn = new BufferedInputStream(new FileInputStream(testBlobFile));
937-
int blobbyte = 0;
938-
int count = 0;
939-
while ((blobbyte = is.read()) > -1) {
940-
int filebyte = bIn.read();
941-
assertFalse(filebyte < 0 || filebyte != blobbyte, "Blob is not identical to initial data.");
942-
count++;
943-
}
944-
assertEquals(requiredSize, count);
918+
int blobByte = 0;
919+
int blobSize = 0;
920+
while ((blobByte = is.read()) > -1) {
921+
int fileByte = bIn.read();
922+
assertFalse(fileByte < 0 || fileByte != blobByte, "Blob is not identical to initial data.");
923+
blobSize++;
924+
}
925+
assertEquals(requiredSize, blobSize);
926+
bIn.close();
945927

946928
is.close();
947-
if (bIn != null) {
948-
bIn.close();
949-
}
950929
}
951930

952931
/**

0 commit comments

Comments
 (0)