com.microsoft.sqlserver.jdbc.PLPInputStream Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of mssql-jdbc Show documentation
Show all versions of mssql-jdbc Show documentation
Microsoft JDBC Driver for SQL Server.
/*
* Microsoft JDBC Driver for SQL Server Copyright(c) Microsoft Corporation All rights reserved. This program is made
* available under the terms of the MIT License. See the LICENSE file in the project root for more information.
*/
package com.microsoft.sqlserver.jdbc;
import java.io.ByteArrayInputStream;
import java.io.IOException;
/**
* PLPInputStream is an InputStream implementation that reads from a TDS PLP stream.
*
* Note PLP stands for Partially Length-prefixed Bytes. TDS 7.2 introduced this new streaming format for streaming of
* large types such as varchar(max), nvarchar(max), varbinary(max) and XML.
*
* See TDS specification, 6.3.3 Datatype Dependent Data Streams: Partially Length-prefixed Bytes for more details on the
* PLP format.
*/
class PLPInputStream extends BaseInputStream {
static final long PLP_NULL = 0xFFFFFFFFFFFFFFFFL;
static final long UNKNOWN_PLP_LEN = 0xFFFFFFFFFFFFFFFEL;
private static final byte[] EMPTY_PLP_BYTES = new byte[0];
private static final int PLP_EOS = -1;
private int currentChunkRemain;
private int markedChunkRemain;
private int leftOverReadLimit = 0;
private byte[] oneByteArray = new byte[1];
/**
* Non-destructive method for checking whether a PLP value at the current TDSReader location is null.
*/
static final boolean isNull(TDSReader tdsReader) throws SQLServerException {
TDSReaderMark mark = tdsReader.mark();
// Temporary stream cannot get closes, since it closes the main stream.
try {
return null == PLPInputStream.makeTempStream(tdsReader, false, null);
} finally {
tdsReader.reset(mark);
}
}
/**
* Create a new input stream.
*
* @param tdsReader
* TDS reader pointing at the start of the PLP data
* @param discardValue
* boolean to represent if base input stream is adaptive and is streaming
* @param dtv
* DTV implementation for values set from the TDS response stream.
* @return PLPInputStream that is created
* @throws SQLServerException
* when an error occurs
*/
static final PLPInputStream makeTempStream(TDSReader tdsReader, boolean discardValue,
ServerDTVImpl dtv) throws SQLServerException {
return makeStream(tdsReader, discardValue, discardValue, dtv);
}
static final PLPInputStream makeStream(TDSReader tdsReader, InputStreamGetterArgs getterArgs,
ServerDTVImpl dtv) throws SQLServerException {
PLPInputStream is = makeStream(tdsReader, getterArgs.isAdaptive, getterArgs.isStreaming, dtv);
if (null != is)
is.setLoggingInfo(getterArgs.logContext);
return is;
}
private static PLPInputStream makeStream(TDSReader tdsReader, boolean isAdaptive, boolean isStreaming,
ServerDTVImpl dtv) throws SQLServerException {
// Read total length of PLP stream.
long payloadLength = tdsReader.readLong();
// If length is PLP_NULL, then return a null PLP value.
if (PLP_NULL == payloadLength)
return null;
return new PLPInputStream(tdsReader, payloadLength, isAdaptive, isStreaming, dtv);
}
/**
* Initializes the input stream.
*/
PLPInputStream(TDSReader tdsReader, long statedPayloadLength, boolean isAdaptive, boolean isStreaming,
ServerDTVImpl dtv) {
super(tdsReader, isAdaptive, isStreaming, dtv);
this.payloadLength = (UNKNOWN_PLP_LEN != statedPayloadLength) ? ((int) statedPayloadLength) : -1;
this.currentChunkRemain = this.markedChunkRemain = 0;
}
/**
* Helper function to convert the entire PLP stream into a contiguous byte array. This call is inefficient (in terms
* of memory usage and run time) for very large PLPs. Use it only if a contiguous byte array is required.
*/
byte[] getBytes() throws SQLServerException {
byte[] value;
// The following 0-byte read just ensures that the number of bytes
// remaining in the current chunk is known.
readBytesInternal(null, 0, 0);
if (PLP_EOS == currentChunkRemain) {
value = EMPTY_PLP_BYTES;
} else {
// If the PLP payload length is known, allocate the final byte array now.
// Otherwise, start with the size of the first chunk. Additional chunks
// will cause the array to be reallocated & copied.
value = new byte[(-1 != payloadLength) ? payloadLength : currentChunkRemain];
int bytesRead = 0;
while (PLP_EOS != currentChunkRemain) {
// If the current byte array isn't large enough to hold
// the contents of the current chunk, then make it larger.
if (value.length == bytesRead) {
byte[] newValue = new byte[bytesRead + currentChunkRemain];
System.arraycopy(value, 0, newValue, 0, bytesRead);
value = newValue;
}
bytesRead += readBytesInternal(value, bytesRead, currentChunkRemain);
}
}
// Always close the stream after retrieving it
try {
close();
} catch (IOException e) {
SQLServerException.makeFromDriverError(null, null, e.getMessage(), null, true);
}
return value;
}
/**
* Skips over and discards n bytes of data from this input stream.
*
* @param n
* the number of bytes to be skipped.
* @return the actual number of bytes skipped.
* @exception IOException
* if an I/O error occurs.
*/
@Override
public long skip(long n) throws IOException {
checkClosed();
if (n < 0)
return 0L;
if (n > Integer.MAX_VALUE)
n = Integer.MAX_VALUE;
long bytesread = readBytes(null, 0, (int) n);
if (-1 == bytesread)
return 0;
else
return bytesread;
}
/**
* Returns the number of bytes that can be read (or skipped over) from this input stream without blocking by the
* next caller of a method for this input stream.
*
* @return the actual number of bytes available.
* @exception IOException
* if an I/O error occurs.
*/
@Override
public int available() throws IOException {
checkClosed();
try {
// The following 0-byte read just ensures that the number of bytes
// remaining in the current chunk is known.
if (0 == currentChunkRemain)
readBytesInternal(null, 0, 0);
if (PLP_EOS == currentChunkRemain)
return 0;
// Return the lesser of the number of bytes available for reading
// from the underlying TDSReader and the number of bytes left in
// the current chunk.
int available = tdsReader.available();
if (available > currentChunkRemain)
available = currentChunkRemain;
return available;
} catch (SQLServerException e) {
throw new IOException(e.getMessage());
}
}
/**
* Reads the next byte of data from the input stream.
*
* @return the byte read or -1 meaning no more bytes.
* @exception IOException
* if an I/O error occurs.
*/
@Override
public int read() throws IOException {
checkClosed();
if (-1 != readBytes(oneByteArray, 0, 1))
return oneByteArray[0] & 0xFF;
return -1;
}
/**
* Reads available data into supplied byte array.
*
* @param b
* array of bytes to fill.
* @return the number of bytes read or 0 meaning no bytes read.
* @exception IOException
* if an I/O error occurs.
*/
@Override
public int read(byte[] b) throws IOException {
// If b is null, a NullPointerException is thrown.
if (null == b)
throw new NullPointerException();
checkClosed();
return readBytes(b, 0, b.length);
}
/**
* Reads available data into supplied byte array.
*
* @param b
* array of bytes to fill.
* @param offset
* the offset into array b where to start writing.
* @param maxBytes
* the max number of bytes to write into b.
* @return the number of bytes read or 0 meaning no bytes read.
* @exception IOException
* if an I/O error occurs.
*/
@Override
public int read(byte[] b, int offset, int maxBytes) throws IOException {
// If b is null, a NullPointerException is thrown.
if (null == b)
throw new NullPointerException();
// Verify offset and maxBytes against target buffer if we're reading (as opposed to skipping).
// If offset is negative, or maxBytes is negative, or offset+maxBytes
// is greater than the length of the array b, then an IndexOutOfBoundsException is thrown.
if (offset < 0 || maxBytes < 0 || offset + maxBytes > b.length)
throw new IndexOutOfBoundsException();
checkClosed();
return readBytes(b, offset, maxBytes);
}
/**
* Reads available data into supplied byte array b.
*
* @param b
* array of bytes to fill. If b is null, method will skip over data.
* @param offset
* the offset into array b where to start writing.
* @param maxBytes
* the max number of bytes to write into b.
* @return the number of bytes read or 0 meaning no bytes read or -1 meaning EOS.
* @exception IOException
* if an I/O error occurs.
*/
int readBytes(byte[] b, int offset, int maxBytes) throws IOException {
// If maxBytes is zero, then no bytes are read and 0 is returned
// This must be done here rather than in readBytesInternal since a 0-byte read
// there may return -1 at EOS.
if (0 == maxBytes)
return 0;
try {
return readBytesInternal(b, offset, maxBytes);
} catch (SQLServerException e) {
throw new IOException(e.getMessage());
}
}
private int readBytesInternal(byte[] b, int offset, int maxBytes) throws SQLServerException {
/*
* If we're at EOS, say so. Note: For back compat, this special case needs to always be handled before checking
* user-supplied arguments below.
*/
if (PLP_EOS == currentChunkRemain)
return -1;
// Save off the current TDSReader position, wherever it is, and start reading
// from where we left off last time.
int bytesRead = 0;
while (true) {
/*
* Check that we have bytes left to read from the current chunk. If not then figure out the size of the next
* chunk or determine that we have reached the end of the stream.
*/
if (0 == currentChunkRemain) {
currentChunkRemain = (int) tdsReader.readUnsignedInt();
assert currentChunkRemain >= 0;
if (0 == currentChunkRemain) {
currentChunkRemain = PLP_EOS;
break;
}
}
if (bytesRead == maxBytes)
break;
/*
* Now we know there are bytes to be read in the current chunk. Further limit the max number of bytes we can
* read to whatever remains in the current chunk.
*/
int bytesToRead = maxBytes - bytesRead;
if (bytesToRead > currentChunkRemain)
bytesToRead = currentChunkRemain;
// Skip/Read as many bytes as we can, given the constraints.
if (null == b)
tdsReader.skip(bytesToRead);
else
tdsReader.readBytes(b, offset + bytesRead, bytesToRead);
bytesRead += bytesToRead;
currentChunkRemain -= bytesToRead;
}
if (bytesRead > 0) {
if (isReadLimitSet && leftOverReadLimit > 0) {
leftOverReadLimit = leftOverReadLimit - bytesRead;
if (leftOverReadLimit < 0)
clearCurrentMark();
}
return bytesRead;
}
if (PLP_EOS == currentChunkRemain)
return -1;
return 0;
}
/**
* Marks the current position in this input stream.
*
* @param readLimit
* the number of bytes to hold (this implementation ignores this).
*/
@Override
public void mark(int readLimit) {
// Save off current position and how much of the current chunk remains
// cant throw if the tdsreader is null
if (null != tdsReader && readLimit > 0) {
currentMark = tdsReader.mark();
markedChunkRemain = currentChunkRemain;
leftOverReadLimit = readLimit;
setReadLimit(readLimit);
}
}
/**
* Closes the stream releasing all resources held.
*
* @exception IOException
* if an I/O error occurs.
*/
@Override
public void close() throws IOException {
if (null == tdsReader)
return;
while (skip(tdsReader.getConnection().getTDSPacketSize()) != 0);
// Release ref to tdsReader and parentRS here, shut down stream state.
closeHelper();
}
/**
* Resets stream to saved mark position.
*
* @exception IOException
* if an I/O error occurs.
*/
@Override
public void reset() throws IOException {
resetHelper();
leftOverReadLimit = readLimit;
currentChunkRemain = markedChunkRemain;
}
}
/**
* Implements an XML binary stream with BOM header.
*
* Class extends a normal PLPInputStream class and prepends the XML BOM (0xFFFE) token then steps out of the way and
* forwards the rest of the InputStream calls to the super class PLPInputStream.
*/
final class PLPXMLInputStream extends PLPInputStream {
// XML BOM header (the first two header bytes sent to caller).
private static final byte[] xmlBOM = {(byte) 0xFF, (byte) 0xFE};
private final ByteArrayInputStream bomStream = new ByteArrayInputStream(xmlBOM);
static final PLPXMLInputStream makeXMLStream(TDSReader tdsReader, InputStreamGetterArgs getterArgs,
ServerDTVImpl dtv) throws SQLServerException {
// Read total length of PLP stream.
long payloadLength = tdsReader.readLong();
// If length is PLP_NULL, then return a null PLP value.
if (PLP_NULL == payloadLength)
return null;
PLPXMLInputStream is = new PLPXMLInputStream(tdsReader, payloadLength, getterArgs, dtv);
is.setLoggingInfo(getterArgs.logContext);
return is;
}
PLPXMLInputStream(TDSReader tdsReader, long statedPayloadLength, InputStreamGetterArgs getterArgs,
ServerDTVImpl dtv) {
super(tdsReader, statedPayloadLength, getterArgs.isAdaptive, getterArgs.isStreaming, dtv);
}
@Override
int readBytes(byte[] b, int offset, int maxBytes) throws IOException {
assert offset >= 0;
assert maxBytes >= 0;
// If maxBytes is zero, then no bytes are read and 0 is returned.
if (0 == maxBytes)
return 0;
int bytesRead = 0;
int xmlBytesRead = 0;
// Read/Skip BOM bytes first. When all BOM bytes have been consumed ...
if (null == b) {
for (int bomBytesSkipped;
bytesRead < maxBytes
&& 0 != (bomBytesSkipped = (int) bomStream.skip(((long) maxBytes) - ((long) bytesRead)));
bytesRead += bomBytesSkipped);
} else {
for (int bomBytesRead;
bytesRead < maxBytes
&& -1 != (bomBytesRead = bomStream.read(b, offset + bytesRead, maxBytes - bytesRead));
bytesRead += bomBytesRead);
}
// ... then read/skip bytes from the underlying PLPInputStream
for (; bytesRead < maxBytes
&& -1 != (xmlBytesRead = super.readBytes(b, offset + bytesRead, maxBytes - bytesRead));
bytesRead += xmlBytesRead);
if (bytesRead > 0)
return bytesRead;
// No bytes read - should have been EOF since 0-byte reads are handled above
assert -1 == xmlBytesRead;
return -1;
}
@Override
public void mark(int readLimit) {
bomStream.mark(xmlBOM.length);
super.mark(readLimit);
}
@Override
public void reset() throws IOException {
bomStream.reset();
super.reset();
}
/**
* Helper function to convert the entire PLP stream into a contiguous byte array. This call is inefficient (in terms
* of memory usage and run time) for very large PLPs. Use it only if a contiguous byte array is required.
*/
@Override
byte[] getBytes() throws SQLServerException {
// Look to see if the BOM has been read
byte[] bom = new byte[2];
byte[] bytesToReturn = null;
try {
int bytesread = bomStream.read(bom);
byte[] valueWithoutBOM = super.getBytes();
if (bytesread > 0) {
assert 2 == bytesread;
byte[] valueWithBOM = new byte[valueWithoutBOM.length + bytesread];
System.arraycopy(bom, 0, valueWithBOM, 0, bytesread);
System.arraycopy(valueWithoutBOM, 0, valueWithBOM, bytesread, valueWithoutBOM.length);
bytesToReturn = valueWithBOM;
} else
bytesToReturn = valueWithoutBOM;
} catch (IOException e) {
SQLServerException.makeFromDriverError(null, null, e.getMessage(), null, true);
}
return bytesToReturn;
}
}