Skip to content

Commit

Permalink
Fixed a bug where calling length() after obtaining a stream would clo…
Browse files Browse the repository at this point in the history
…se the stream for Clobs/NClobs (#799)

Addresses #788. Also addressed an issue where varchar(max)/Clob objects were always being encoded to UTF-16LE instead of using the Collation specified in the Clob object.

List of Changes (the following applies to NClob as well):

1. calling Clob.length() no longer attempts to load the stream into a string. This fixes a null pointer issue as well as length() closing user streams.
2. added streaming capabilities for Clob.getAsciiStream(). NClobs will always have a non-streaming implementation for getAsciiStream().
3. Clobs no longer default to UTF-16LE. Clobs now respect the collation from the SQL Server. NClobs remain unchanged, will always be UTF-16LE encoding.
  • Loading branch information
rene-ye authored Sep 25, 2018
1 parent e19c524 commit 037a11d
Show file tree
Hide file tree
Showing 5 changed files with 322 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,6 @@ class PLPInputStream extends BaseInputStream {
static final int PLP_TERMINATOR = 0x00000000;
private final static byte[] EMPTY_PLP_BYTES = new byte[0];

// Stated length of the PLP stream payload; -1 if unknown length.
int payloadLength;

private static final int PLP_EOS = -1;
private int currentChunkRemain;

Expand Down
50 changes: 35 additions & 15 deletions src/main/java/com/microsoft/sqlserver/jdbc/SQLServerClob.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,9 @@

package com.microsoft.sqlserver.jdbc;

import static java.nio.charset.StandardCharsets.US_ASCII;
import static java.nio.charset.StandardCharsets.UTF_16LE;

import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
Expand All @@ -20,6 +18,7 @@
import java.io.StringReader;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.nio.charset.Charset;
import java.sql.Clob;
import java.sql.SQLException;
import java.text.MessageFormat;
Expand Down Expand Up @@ -152,7 +151,7 @@ abstract class SQLServerClobBase extends SQLServerLob implements Serializable {

// The value of the CLOB that this Clob object represents.
// This value is never null unless/until the free() method is called.
private String value;
protected String value;

private final SQLCollation sqlCollation;

Expand Down Expand Up @@ -181,8 +180,9 @@ final public String toString() {
// Unique id generator for each instance (used for logging).
static private final AtomicInteger baseID = new AtomicInteger(0);

private Charset defaultCharset = null;

// Returns unique id for each instance.

private static int nextInstanceID() {
return baseID.incrementAndGet();
}
Expand Down Expand Up @@ -281,9 +281,19 @@ public InputStream getAsciiStream() throws SQLException {
if (null != sqlCollation && !sqlCollation.supportsAsciiConversion())
DataTypes.throwConversionError(getDisplayClassName(), "AsciiStream");

getStringFromStream();
InputStream getterStream = new BufferedInputStream(
new ReaderInputStream(new StringReader(value), US_ASCII, value.length()));
// Need to use a BufferedInputStream since the stream returned by this method is assumed to support mark/reset
InputStream getterStream = null;
if (null == value && !activeStreams.isEmpty()) {
InputStream inputStream = (InputStream) activeStreams.get(0);
try {
inputStream.reset();
} catch (IOException e) {
SQLServerException.makeFromDriverError(con, null, e.getMessage(), null, false);
}
getterStream = new BufferedInputStream(inputStream);
} else {
getterStream = new ByteArrayInputStream(value.getBytes(java.nio.charset.StandardCharsets.US_ASCII));
}
activeStreams.add(getterStream);
return getterStream;
}
Expand All @@ -301,11 +311,17 @@ public Reader getCharacterStream() throws SQLException {
Reader getterStream = null;
if (null == value && !activeStreams.isEmpty()) {
InputStream inputStream = (InputStream) activeStreams.get(0);
getterStream = new BufferedReader(new InputStreamReader(inputStream, UTF_16LE));
try {
inputStream.reset();
} catch (IOException e) {
SQLServerException.makeFromDriverError(con, null, e.getMessage(), null, false);
}
Charset cs = (defaultCharset == null) ? typeInfo.getCharset() : defaultCharset;
getterStream = new BufferedReader(new InputStreamReader(inputStream, cs));
} else {
getterStream = new StringReader(value);
activeStreams.add(getterStream);
}
activeStreams.add(getterStream);
return getterStream;
}

Expand Down Expand Up @@ -381,9 +397,8 @@ public String getSubString(long pos, int length) throws SQLException {
*/
public long length() throws SQLException {
checkClosed();

if (value == null && activeStreams.get(0) instanceof PLPInputStream) {
return (long) ((PLPInputStream) activeStreams.get(0)).payloadLength / 2;
if (null == value && activeStreams.get(0) instanceof BaseInputStream) {
return (long) ((BaseInputStream) activeStreams.get(0)).payloadLength;
}
return value.length();
}
Expand All @@ -410,9 +425,10 @@ private void getStringFromStream() throws SQLServerException {
try {
stream.reset();
} catch (IOException e) {
throw new SQLServerException(e.getMessage(), null, 0, e);
SQLServerException.makeFromDriverError(con, null, e.getMessage(), null, false);
}
value = new String((stream).getBytes(), typeInfo.getCharset());
Charset cs = (defaultCharset == null) ? typeInfo.getCharset() : defaultCharset;
value = new String(stream.getBytes(), cs);
}
}

Expand Down Expand Up @@ -661,6 +677,10 @@ public int setString(long pos, String str, int offset, int len) throws SQLExcept

return len;
}

protected void setDefaultCharset(Charset c) {
this.defaultCharset = c;
}
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,12 @@ public final class SQLServerNClob extends SQLServerClobBase implements NClob {

SQLServerNClob(SQLServerConnection connection) {
super(connection, "", connection.getDatabaseCollation(), logger, null);
this.setDefaultCharset(java.nio.charset.StandardCharsets.UTF_16LE);
}

SQLServerNClob(BaseInputStream stream, TypeInfo typeInfo) throws SQLServerException, UnsupportedEncodingException {
super(null, stream, typeInfo.getSQLCollation(), logger, typeInfo);
this.setDefaultCharset(java.nio.charset.StandardCharsets.UTF_16LE);
}

@Override
Expand All @@ -45,6 +47,9 @@ public void free() throws SQLException {

@Override
public InputStream getAsciiStream() throws SQLException {
// NClobs are mapped to Nvarchar(max), and are always UTF-16 encoded. This API expects a US_ASCII stream.
// It's not possible to modify the stream without loading it into memory. Users should use getCharacterStream.
this.fillFromStream();
return super.getAsciiStream();
}

Expand All @@ -65,7 +70,9 @@ public String getSubString(long pos, int length) throws SQLException {

@Override
public long length() throws SQLException {
return super.length();
// If streaming, every 2 bytes represents 1 character. If not, length() just returns string length
long length = super.length();
return (null == value) ? length / 2 : length;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ abstract class BaseInputStream extends InputStream {

// Flag indicating whether the stream consumes and discards data as it reads it
final boolean isStreaming;

// Stated length of the payload
int payloadLength;

/** Generate the logging ID */
private String parentLoggingInfo = "";
Expand Down Expand Up @@ -131,9 +134,6 @@ void resetHelper() throws IOException {

final class SimpleInputStream extends BaseInputStream {

// Stated length of the payload
private final int payloadLength;

/**
* Initializes the input stream.
*/
Expand Down
Loading

0 comments on commit 037a11d

Please sign in to comment.