Skip to content

fix: preserve SQL null bindings for null streams - #1486

Merged
gotson merged 3 commits into
xerial:masterfrom
PHJ2000:fix-null-stream-bindings
Oct 27, 2026
Merged

gotson merged 3 commits into
xerial:masterfrom
PHJ2000:fix-null-stream-bindings

Conversation

@PHJ2000

@PHJ2000 PHJ2000 commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

Null streams are not consistently bound as SQL NULL by the int-length stream setters. With length zero, the null binding is overwritten with an empty BLOB or string; with a positive length, readBytes dereferences the null stream.

Return immediately after binding SQL NULL for a null stream with a non-negative length. Negative lengths retain their existing exception behavior. This fixes setBinaryStream and setUnicodeStream, and setAsciiStream through its existing delegation. The supported long-length binary/ASCII and setBlob overloads delegate to these methods and are also covered by the regression.

The parameterized in-memory regression checks SQL NULL at lengths 0, 1 and 16 across all six entry points. Controls verify that negative lengths are rejected for both null and non-null streams and that non-null zero-length streams still bind empty non-null values. The overloads without a length argument are outside this change.

Validation on JDK 11:

  • The zero-length regression fails on the original implementation; nonzero null-stream failures were also reproduced before their fix.
  • The negative-length/null control catches the temporary behavior change in the previous PR head and passes after restoring validation.
  • mvn -B -ntp spotless:apply clean verify passes: 474 tests, 8 existing skips, no failures.
  • git diff --check passes.

Signed-off-by: Jaehong Park <105151063+PHJ2000@users.noreply.github.com>
@gotson

gotson commented Oct 23, 2026

Copy link
Copy Markdown
Collaborator

i don't quite understand the description you gave, can you provide details on what is the current problem, how it is observed, and what is the fix ?

@PHJ2000

PHJ2000 commented Oct 23, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for asking — here is a minimal example of the observable problem:

try (Connection c = DriverManager.getConnection("jdbc:sqlite::memory:");
     PreparedStatement p = c.prepareStatement("select ?")) {
    p.setBinaryStream(1, null, 0);
    try (ResultSet rs = p.executeQuery()) {
        rs.next();
        Object value = rs.getObject(1);
        boolean wasNull = rs.wasNull();
        // Before this fix: value is an empty byte[], wasNull is false.
        // After this fix: value is null, wasNull is true.
    }
}

The existing null branch in setBinaryStream calls setBytes(pos, null), but execution then falls through to setBytes(pos, readBytes(istream, length)). For length zero, readBytes returns an empty byte array, overwriting the NULL binding with an empty BLOB.

setUnicodeStream has the same fall-through: it first binds null, then replaces it with an empty string. setAsciiStream delegates to that method, so it is affected too.

The production change is two early return statements after those existing null-binding branches. The new tests check both sides: null streams with length zero remain SQL NULL, while non-null streams with length zero still produce empty, non-null values. The null regression fails before the fix (expected: null, but was: []); the full Java 11 clean verify run passes afterward (470 tests, 8 skips).

@@ -271,6 +271,7 @@ private byte[] readBytes(InputStream istream, int length) throws SQLException {
public void setBinaryStream(int pos, InputStream istream, int length) throws SQLException {
if (istream == null && length == 0) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it seems to me that we should handle null stream of non-zero length, no ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right. The original change only preserved the existing zero-length null branch; a null stream with a positive length still caused a NullPointerException in readBytes.

I've removed the length condition from both null checks, so null binary, ASCII and Unicode streams now bind SQL NULL without reading or allocating a buffer. The parameterized regression covers lengths -1, 0, 1 and 16. A separate control confirms that negative lengths are still rejected for non-null streams, and the existing zero-length/non-null tests still verify empty values.

The expanded null-stream test fails for three nonzero lengths before this follow-up and passes after it. Full JDK 11 mvn -B -ntp spotless:apply clean verify passes: 474 tests, 8 skipped, no failures.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A follow-up from checking the related overloads: my previous commit also made a null stream with a negative int length succeed, while the existing long-length overloads still rejected negative lengths. That was an unnecessary behavior change.

In 340f95f, null streams bind SQL NULL for non-negative lengths, while negative lengths retain their existing exception behavior. The null regression now covers the three int-length setters, both long-length binary/ASCII setters, and the delegating setBlob overload at lengths 0, 1 and 16. Negative-length controls cover both null and non-null streams across these paths.

The new negative-length/null control fails on the previous PR head and passes with this correction. Full JDK 11 mvn -B -ntp spotless:apply clean verify passes: 474 tests, 8 skipped, no failures. This PR remains scoped to length-bearing stream setters; the separate overloads without a length argument are unchanged.

@PHJ2000 PHJ2000 changed the title fix: preserve null bindings for zero-length streams fix: preserve SQL null bindings for null streams Oct 23, 2026
@PHJ2000

PHJ2000 commented Oct 23, 2026

Copy link
Copy Markdown
Contributor Author

The only failed job for 340f95fb7b86c9c5ad18798f3460abc70769a241 is Alpine aarch64 / JDK 25. It stops while resolving the Maven project, before compilation/tests: Maven Central returns HTTP 429 for org.junit:junit-bom:5.12.2, and the build extension cannot resolve either. The other 30 check/test jobs passed.

This failure is a dependency-download/environment failure, not a failing regression assertion. I tried rerunning just that job, but GitHub rejected it because this account lacks repository admin rights. Could a maintainer rerun the failed job when convenient? No code change is needed for this log.

AI disclosure: this log investigation and comment were prepared with OpenAI Codex.

@gotson
gotson merged commit 201218f into xerial:master Oct 27, 2026
31 of 32 checks passed
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

Successfully merging this pull request may close these issues.

2 participants