Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Calling Blob#length() causes an InputStream obtained from Blob#getBinaryStream() to close #611

Closed
permagnushansson opened this issue Jan 26, 2018 · 4 comments

Comments

@permagnushansson
Copy link

Driver version or jar name

6.1.6 and later

SQL Server version

Microsoft SQL Server 2012

Client operating system

Mac OS, Windows, Linux

Java/JVM version

1.8.161

Table schema

create table mytable (
  mydata varbinary(max)
);

Problem description

In versions prior to 6.1.6 it was possible to:

  1. Obtain an InputStream from a BLOB with myBlob.getBinaryStream().
  2. Call myBlob.length() to get the size of the BLOB.
  3. Consume the InputStream.

In version 6.1.6 and later, the InputStream cannot be consumed, because it has been closed, and an exception is thrown:

java.io.IOException: The stream is closed.
	at com.microsoft.sqlserver.jdbc.BaseInputStream.checkClosed(SimpleInputStream.java:99)
	at com.microsoft.sqlserver.jdbc.PLPInputStream.read(PLPInputStream.java:221)
	at com.example.JdbcBlobStreamTester.main(JdbcBlobStreamTester.java:51)

The following works, of course:

  1. Call myBlob.length() to get the size of the BLOB.
  2. Obtain an InputStream from a BLOB with myBlob.getBinaryStream().
  3. Consume the InputStream.

I suspect that the change of behavior came about with the resolution #16.

Expected behavior and actual behavior

Expected: that the obtained InputStream survives the call to Blob#length().

On the other hand, the stream closing might be expected behavior: 66e395d.

Repro code

package com.example;

import java.io.InputStream;
import java.sql.Blob;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;

public class JdbcBlobStreamTester {
  
  private static final String URL = "jdbc:sqlserver://database.example.com:1433;DatabaseName=my_database";
  private static final String CLASS_NAME = "com.microsoft.sqlserver.jdbc.SQLServerDriver";
  private static final String USERNAME = "username";
  private static final String PASSWORD = "password";

  public static void main(String... args) {
    
    Connection con = null;
    Statement stmt = null;
    ResultSet rs = null;
    
    try {
      System.out.format("Loading class '%s'...", CLASS_NAME);
      final Class<?> cls = Class.forName(CLASS_NAME);
      final Driver driver = (Driver) cls.newInstance();
      System.out.println("OK!");
      System.out.format("Loaded driver version %d.%d\n", driver.getMajorVersion(), driver.getMinorVersion());
      
      System.out.format("Connecting using URL '%s'...", URL);
      con = DriverManager.getConnection(URL, USERNAME, PASSWORD);
      System.out.println("OK!");
      
      final String sql = " SELECT MYDATA FROM MYTABLE ";
      stmt = con.createStatement();
      
      System.out.print("Executing query...");
      rs = stmt.executeQuery(sql);
      System.out.println("OK!");
      
      if (!rs.next()) {
        throw new IllegalStateException("The ResultSet is empty");
      }

      final Blob blob = rs.getBlob(1);        
      final InputStream is = blob.getBinaryStream();

      // Changing the order of the following two lines gives a difference in behavior:
      System.out.format("BLOB length: %d bytes\n", blob.length());
      System.out.format("First byte of BLOB: %c\n", is.read());

      System.out.println("Done!");
    } catch (Exception e) {
      System.out.println("\n\nAn error occured:");
      e.printStackTrace(System.out);
    } finally {
      close(rs);
      close(stmt);
      close(con);
    }
  }
  
  private static void close(AutoCloseable obj) {
    if (obj != null) {
      try {
        obj.close();
      } catch (Exception e) {
        e.printStackTrace(System.out);
      }
    }
  }
}
rene-ye added a commit to rene-ye/mssql-jdbc that referenced this issue Jan 26, 2018
Fix to issue microsoft#611, where calling length() would close the stream.
@rene-ye
Copy link
Member

rene-ye commented Jan 26, 2018

Hi @permagnushansson, thank you for bringing the issue to our attention. The behavior described is unintended, and I have been able to reproduce it. A fix has been pushed and is currently under review. In the meantime, you can test the changes by forking this branch.

@permagnushansson
Copy link
Author

Hi @rene-ye , I cloned the forked branch and I can confirm that the fix works for me. Great!

A side note - I followed the instructions and built the driver with Java 9(.0.4) and Maven (mvn install -Pbuild42), but I wasn't able to use it with Java 8(u_121). I got the following exception:

Exception in thread "main" java.lang.NoSuchMethodError: java.nio.ByteBuffer.position(I)Ljava/nio/ByteBuffer;
	at com.microsoft.sqlserver.jdbc.TDSWriter.startMessage(IOBuffer.java:3191)

Looks like the ByteBuffer hasn't inherited the methods of Buffer. Not sure if I've managed to screw things up, or if this is a general Java 9/earlier compatibility problem.

@rene-ye
Copy link
Member

rene-ye commented Jan 30, 2018

Hi @permagnushansson, the fix for that error has already been resolved in the main branches. The blobStream branch hasn't been updated for awhile, and that's probably why you're seeing that error. I've updated the branch with the latest changes and that should fix the problem. Please try again and feel free update us on any issues you encounter.

@rene-ye
Copy link
Member

rene-ye commented Mar 6, 2018

PR #595 has been merged and can be expected in 6.5.0. Closing issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants