Skip to content

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

@permagnushansson

Description

@permagnushansson

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);
      }
    }
  }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions