Skip to content

Commit 2d71ec2

Browse files
committed
Fix for Bug#17881458, BEHAVIOR OF SETBINARYSTREAM() METHOD IS DIFFERENT WHEN USESERVERPREPSTMTS=TRUE.
Change-Id: Ic33f8fc7dfd43253f9091f128a4c57e494dea86e
1 parent bf0ecd1 commit 2d71ec2

File tree

8 files changed

+327
-147
lines changed

8 files changed

+327
-147
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#17881458, BEHAVIOR OF SETBINARYSTREAM() METHOD IS DIFFERENT WHEN USESERVERPREPSTMTS=TRUE.
7+
68
- Fix for Bug#45554 (Bug#11754018), Connector/J does not encode binary data if useServerPrepStatements=false.
79

810
- Fix for Bug#114974 (Bug#36614381), the SQL in batch will not clear after statement close.

src/main/core-api/java/com/mysql/cj/util/Util.java

Lines changed: 77 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -432,8 +432,7 @@ public static boolean isRunningOnWindows() {
432432
}
433433

434434
/**
435-
* Reads length bytes from reader into buf. Blocks until enough input is
436-
* available
435+
* Reads length bytes from reader into buf. Blocks until enough input is available.
437436
*
438437
* @param reader
439438
* {@link Reader}
@@ -459,24 +458,90 @@ public static int readFully(Reader reader, char[] buf, int length) throws IOExce
459458
return numCharsRead;
460459
}
461460

462-
public static final int readBlock(InputStream i, byte[] b, ExceptionInterceptor exceptionInterceptor) {
461+
/**
462+
* Reads data from the provided {@link InputStream} into the specified buffer.
463+
*
464+
* @param inStream
465+
* The {@link InputStream} to read from.
466+
* @param buffer
467+
* Buffer into which bytes are read.
468+
* @param exceptionInterceptor
469+
* The {@link ExceptionInterceptor} to handle new exceptions thrown.
470+
* @return
471+
* The number of bytes read, or -1 if the end of the stream is reached
472+
*/
473+
public static final int readBlock(InputStream inStream, byte[] buffer, ExceptionInterceptor exceptionInterceptor) {
463474
try {
464-
return i.read(b);
465-
} catch (Throwable ex) {
475+
return inStream.read(buffer);
476+
} catch (Exception ex) {
466477
throw ExceptionFactory.createException(Messages.getString("Util.5") + ex.getClass().getName(), exceptionInterceptor);
467478
}
468479
}
469480

470-
public static final int readBlock(InputStream i, byte[] b, int length, ExceptionInterceptor exceptionInterceptor) {
481+
/**
482+
* Reads data from the provided {@link InputStream} into the specified buffer, up to the specified length or the size of the buffer, whichever is smaller.
483+
*
484+
* @param inStream
485+
* The {@link InputStream} to read from.
486+
* @param buffer
487+
* Buffer into which bytes are read.
488+
* @param length
489+
* The maximum amount of bytes to read from the input stream.
490+
* @param exceptionInterceptor
491+
* The {@link ExceptionInterceptor} to handle new exceptions thrown.
492+
* @return
493+
* The number of bytes read, or -1 if the end of the stream is reached
494+
*/
495+
public static final int readBlock(InputStream inStream, byte[] buffer, long length, ExceptionInterceptor exceptionInterceptor) {
471496
try {
472-
int lengthToRead = length;
473-
if (lengthToRead > b.length) {
474-
lengthToRead = b.length;
475-
}
476-
return i.read(b, 0, lengthToRead);
477-
} catch (Throwable ex) {
497+
int lengthToRead = length > buffer.length ? buffer.length : (int) length;
498+
return inStream.read(buffer, 0, lengthToRead);
499+
} catch (Exception ex) {
478500
throw ExceptionFactory.createException(Messages.getString("Util.5") + ex.getClass().getName(), exceptionInterceptor);
479501
}
480502
}
481503

504+
/**
505+
* Reads characters from the provided {@link Reader} into the specified buffer.
506+
*
507+
* @param reader
508+
* The {@link Reader} to read from.
509+
* @param buffer
510+
* Buffer into which the characters are read.
511+
* @param exceptionInterceptor
512+
* The {@link ExceptionInterceptor} to handle new exceptions thrown.
513+
* @return
514+
* The number of characters read, or -1 if the end of the stream is reached
515+
*/
516+
public static final int readBlock(Reader reader, char[] buffer, ExceptionInterceptor exceptionInterceptor) {
517+
try {
518+
return reader.read(buffer);
519+
} catch (Exception e) {
520+
throw ExceptionFactory.createException(Messages.getString("Util.6") + e.getClass().getName(), exceptionInterceptor);
521+
}
522+
}
523+
524+
/**
525+
* Reads characters from the provided {@link Reader} into the specified buffer, up to the specified length or the size of the buffer, whichever is smaller.
526+
*
527+
* @param reader
528+
* The {@link Reader} to read from.
529+
* @param buffer
530+
* Buffer into which the characters are read.
531+
* @param length
532+
* The maximum amount of characters to read from the stream.
533+
* @param exceptionInterceptor
534+
* The {@link ExceptionInterceptor} to handle new exceptions thrown.
535+
* @return
536+
* The number of characters read, or -1 if the end of the stream is reached
537+
*/
538+
public static final int readBlock(Reader reader, char[] buffer, long length, ExceptionInterceptor exceptionInterceptor) {
539+
try {
540+
int lengthToRead = length > buffer.length ? buffer.length : (int) length;
541+
return reader.read(buffer, 0, lengthToRead);
542+
} catch (Exception ex) {
543+
throw ExceptionFactory.createException(Messages.getString("Util.6") + ex.getClass().getName(), exceptionInterceptor);
544+
}
545+
}
546+
482547
}

src/main/core-impl/java/com/mysql/cj/ServerPreparedQuery.java

Lines changed: 107 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020

2121
package com.mysql.cj;
2222

23-
import java.io.Closeable;
2423
import java.io.IOException;
2524
import java.io.InputStream;
2625
import java.io.Reader;
@@ -50,14 +49,15 @@
5049
import com.mysql.cj.telemetry.TelemetrySpan;
5150
import com.mysql.cj.telemetry.TelemetrySpanName;
5251
import com.mysql.cj.util.StringUtils;
52+
import com.mysql.cj.util.Util;
5353

5454
// TODO should not be protocol-specific
5555

5656
public class ServerPreparedQuery extends ClientPreparedQuery {
5757

58-
public static final int BLOB_STREAM_READ_BUF_SIZE = 8192;
59-
public static final byte OPEN_CURSOR_FLAG = 0x01;
60-
public static final byte PARAMETER_COUNT_AVAILABLE = 0x08;
58+
private static final int READ_BUFFER_SIZE = 8192;
59+
private static final byte OPEN_CURSOR_FLAG = 0x01;
60+
private static final byte PARAMETER_COUNT_AVAILABLE = 0x08;
6161

6262
/** The ID that the server uses to identify this PreparedStatement */
6363
private long serverStatementId;
@@ -409,24 +409,24 @@ private void serverLongData(int parameterIndex, BindValue binding) {
409409
this.session.getProtocol()
410410
.sendCommand(this.commandBuilder.buildComStmtSendLongData(packet, this.serverStatementId, parameterIndex, (byte[]) value), true, 0);
411411
} else if (value instanceof InputStream) {
412-
storeStreamOrReader(parameterIndex, packet, (InputStream) value);
412+
storeStream(parameterIndex, packet, (InputStream) value, binding.getScaleOrLength());
413413
} else if (value instanceof java.sql.Blob) {
414414
try {
415-
storeStreamOrReader(parameterIndex, packet, ((java.sql.Blob) value).getBinaryStream());
415+
storeStream(parameterIndex, packet, ((java.sql.Blob) value).getBinaryStream(), binding.getScaleOrLength());
416416
} catch (Throwable t) {
417417
throw ExceptionFactory.createException(t.getMessage(), this.session.getExceptionInterceptor());
418418
}
419419
} else if (value instanceof Reader) {
420420
if (binding.isNational() && !this.charEncoding.equalsIgnoreCase("UTF-8") && !this.charEncoding.equalsIgnoreCase("utf8")) {
421421
throw ExceptionFactory.createException(Messages.getString("ServerPreparedStatement.31"), this.session.getExceptionInterceptor());
422422
}
423-
storeStreamOrReader(parameterIndex, packet, (Reader) value);
423+
storeReader(parameterIndex, packet, (Reader) value, binding.getScaleOrLength());
424424
} else if (value instanceof Clob) {
425425
if (binding.isNational() && !this.charEncoding.equalsIgnoreCase("UTF-8") && !this.charEncoding.equalsIgnoreCase("utf8")) {
426426
throw ExceptionFactory.createException(Messages.getString("ServerPreparedStatement.31"), this.session.getExceptionInterceptor());
427427
}
428428
try {
429-
storeStreamOrReader(parameterIndex, packet, ((Clob) value).getCharacterStream());
429+
storeReader(parameterIndex, packet, ((Clob) value).getCharacterStream(), binding.getScaleOrLength());
430430
} catch (Throwable t) {
431431
throw ExceptionFactory.createException(t.getMessage(), t);
432432
}
@@ -477,88 +477,126 @@ public void setResultFields(ColumnDefinition resultFields) {
477477
this.resultFields = resultFields;
478478
}
479479

480-
private void storeStreamOrReader(int parameterIndex, NativePacketPayload packet, Closeable streamOrReader) {
481-
// TODO consider to use more precise type than just Closable
480+
private void storeStream(int parameterIndex, NativePacketPayload packet, InputStream inStream, long length) {
482481
this.session.checkClosed();
483-
boolean isStream = InputStream.class.isAssignableFrom(streamOrReader.getClass());
484-
byte[] bBuf = null;
485-
char[] cBuf = null;
486-
String clobEncoding = null;
487482

488483
this.session.getSessionLock().lock();
489484
try {
490-
if (isStream) {
491-
bBuf = new byte[BLOB_STREAM_READ_BUF_SIZE];
492-
} else {
493-
clobEncoding = this.session.getPropertySet().getStringProperty(PropertyKey.clobCharacterEncoding).getStringValue();
494-
if (clobEncoding == null) {
495-
clobEncoding = this.session.getPropertySet().getStringProperty(PropertyKey.characterEncoding).getValue();
485+
inStream.mark(Integer.MAX_VALUE); // This same stream may have to be read several times, so it must be reset at the end.
486+
487+
boolean limitLength = length == -1 ? false : this.session.getPropertySet().getBooleanProperty(PropertyKey.useStreamLengthsInPrepStmts).getValue();
488+
int sendThreshold = this.session.getPropertySet().getMemorySizeProperty(PropertyKey.blobSendChunkSize).getValue();
489+
490+
byte[] buffer = new byte[READ_BUFFER_SIZE];
491+
boolean dataSent = false;
492+
int bytesRead = 0;
493+
int bytesInPacket = 0;
494+
long lengthLeft = length;
495+
496+
do {
497+
if (bytesInPacket == 0) {
498+
packet.setPosition(0);
499+
this.commandBuilder.buildComStmtSendLongDataHeader(packet, this.serverStatementId, parameterIndex);
500+
}
501+
502+
if (limitLength) {
503+
bytesRead = Util.readBlock(inStream, buffer, lengthLeft, this.session.getExceptionInterceptor());
504+
lengthLeft -= bytesRead;
505+
} else {
506+
bytesRead = Util.readBlock(inStream, buffer, this.session.getExceptionInterceptor());
507+
}
508+
if (bytesRead > 0) {
509+
packet.writeBytes(StringLengthDataType.STRING_FIXED, buffer, 0, bytesRead);
510+
bytesInPacket += bytesRead;
511+
}
512+
513+
if (bytesInPacket >= sendThreshold || bytesRead <= 0 && (!dataSent || bytesInPacket > 0)) {
514+
this.session.getProtocol().sendCommand(packet, true, 0);
515+
dataSent = true;
516+
bytesInPacket = 0;
496517
}
497-
int maxBytesChar = 2;
498-
if (clobEncoding != null) {
499-
maxBytesChar = this.session.getServerSession().getCharsetSettings().getMaxBytesPerChar(clobEncoding);
500-
if (maxBytesChar == 1) {
501-
maxBytesChar = 2; // for safety
518+
} while (bytesRead > 0);
519+
} finally {
520+
try {
521+
inStream.reset();
522+
} catch (IOException e) {
523+
}
524+
525+
if (this.autoClosePStmtStreams.getValue()) {
526+
if (inStream != null) {
527+
try {
528+
inStream.close();
529+
} catch (IOException e) {
502530
}
503531
}
504-
cBuf = new char[ServerPreparedQuery.BLOB_STREAM_READ_BUF_SIZE / maxBytesChar];
505532
}
506533

507-
boolean readAny = false;
508-
int bytesInPacket = 0;
509-
int totalBytesRead = 0;
510-
int bytesReadAtLastSend = 0;
511-
int packetIsFullAt = this.session.getPropertySet().getMemorySizeProperty(PropertyKey.blobSendChunkSize).getValue();
512-
int numRead = 0;
534+
this.session.getSessionLock().unlock();
535+
}
536+
}
513537

538+
private void storeReader(int parameterIndex, NativePacketPayload packet, Reader reader, long length) {
539+
this.session.checkClosed();
540+
541+
this.session.getSessionLock().lock();
542+
try {
514543
try {
515-
packet.setPosition(0);
516-
this.commandBuilder.buildComStmtSendLongDataHeader(packet, this.serverStatementId, parameterIndex);
517-
518-
while ((numRead = isStream ? ((InputStream) streamOrReader).read(bBuf) : ((Reader) streamOrReader).read(cBuf)) != -1) {
519-
readAny = true;
520-
521-
if (isStream) {
522-
packet.writeBytes(StringLengthDataType.STRING_FIXED, bBuf, 0, numRead);
523-
bytesInPacket += numRead;
524-
totalBytesRead += numRead;
525-
} else {
526-
byte[] valueAsBytes = StringUtils.getBytes(cBuf, 0, numRead, clobEncoding);
527-
packet.writeBytes(StringSelfDataType.STRING_EOF, valueAsBytes);
528-
bytesInPacket += valueAsBytes.length;
529-
totalBytesRead += valueAsBytes.length;
530-
}
544+
reader.mark(Integer.MAX_VALUE); // This same stream may have to be read several times, so it must be reset at the end.
545+
} catch (IOException e) {
546+
}
531547

532-
if (bytesInPacket >= packetIsFullAt) {
533-
bytesReadAtLastSend = totalBytesRead;
548+
String clobEncoding = this.session.getPropertySet().getStringProperty(PropertyKey.clobCharacterEncoding).getStringValue();
549+
if (clobEncoding == null) {
550+
clobEncoding = this.session.getPropertySet().getStringProperty(PropertyKey.characterEncoding).getValue();
551+
}
552+
boolean limitLength = length == -1 ? false : this.session.getPropertySet().getBooleanProperty(PropertyKey.useStreamLengthsInPrepStmts).getValue();
553+
int sendThreshold = this.session.getPropertySet().getMemorySizeProperty(PropertyKey.blobSendChunkSize).getValue();
534554

535-
this.session.getProtocol().sendCommand(packet, true, 0);
555+
char[] buffer = new char[READ_BUFFER_SIZE];
556+
boolean dataSent = false;
557+
int charsRead = 0;
558+
int bytesInPacket = 0;
559+
long lengthLeft = length;
536560

537-
bytesInPacket = 0;
538-
packet.setPosition(0);
539-
this.commandBuilder.buildComStmtSendLongDataHeader(packet, this.serverStatementId, parameterIndex);
540-
}
561+
do {
562+
if (bytesInPacket == 0) {
563+
packet.setPosition(0);
564+
this.commandBuilder.buildComStmtSendLongDataHeader(packet, this.serverStatementId, parameterIndex);
565+
}
566+
567+
if (limitLength) {
568+
charsRead = Util.readBlock(reader, buffer, lengthLeft, this.session.getExceptionInterceptor());
569+
lengthLeft -= charsRead;
570+
} else {
571+
charsRead = Util.readBlock(reader, buffer, this.session.getExceptionInterceptor());
572+
}
573+
if (charsRead > 0) {
574+
byte[] charsAsBytes = StringUtils.getBytes(buffer, 0, charsRead, clobEncoding);
575+
packet.writeBytes(StringSelfDataType.STRING_EOF, charsAsBytes);
576+
bytesInPacket += charsAsBytes.length;
541577
}
542578

543-
if (!readAny || totalBytesRead != bytesReadAtLastSend) {
579+
if (bytesInPacket >= sendThreshold || charsRead <= 0 && (!dataSent || bytesInPacket > 0)) {
544580
this.session.getProtocol().sendCommand(packet, true, 0);
581+
dataSent = true;
582+
bytesInPacket = 0;
545583
}
546-
} catch (IOException ioEx) {
547-
throw ExceptionFactory.createException(
548-
(isStream ? Messages.getString("ServerPreparedStatement.24") : Messages.getString("ServerPreparedStatement.25")) + ioEx.toString(),
549-
ioEx, this.session.getExceptionInterceptor());
550-
} finally {
551-
if (this.autoClosePStmtStreams.getValue()) {
552-
if (streamOrReader != null) {
553-
try {
554-
streamOrReader.close();
555-
} catch (IOException ioEx) {
556-
// ignore
557-
}
584+
} while (charsRead > 0);
585+
} finally {
586+
try {
587+
reader.reset();
588+
} catch (IOException e) {
589+
}
590+
591+
if (this.autoClosePStmtStreams.getValue()) {
592+
if (reader != null) {
593+
try {
594+
reader.close();
595+
} catch (IOException ioEx) {
558596
}
559597
}
560598
}
561-
} finally {
599+
562600
this.session.getSessionLock().unlock();
563601
}
564602
}

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,14 +61,14 @@ 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); // This same stream may have to be read several times, so it must be reset at the end.
6565
try {
6666
if (this.streamConvertBuf == null) {
6767
this.streamConvertBuf = new byte[4096];
6868
}
69-
int bcnt = useLength ? Util.readBlock(in, this.streamConvertBuf, (int) length, this.exceptionInterceptor)
69+
int bcnt = useLength ? Util.readBlock(in, this.streamConvertBuf, length, this.exceptionInterceptor)
7070
: Util.readBlock(in, this.streamConvertBuf, this.exceptionInterceptor);
71-
int lengthLeftToRead = (int) (length - bcnt);
71+
long lengthLeftToRead = length - bcnt;
7272

7373
ByteArrayOutputStream bytesOut = null;
7474
if (packet == null) {

0 commit comments

Comments
 (0)