From 7da65fe4fcdf05afbc8b6ab38047e677b6d94659 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Wed, 28 Jan 2026 17:08:40 +0100 Subject: [PATCH] Support -1 for undetermined length in SqlBinaryValue/SqlCharacterValue Closes gh-36219 --- .../org/springframework/core/io/Resource.java | 2 + .../jdbc/core/support/SqlBinaryValue.java | 18 +++++++-- .../jdbc/core/support/SqlCharacterValue.java | 40 +++++++++++++++---- .../core/support/SqlBinaryValueTests.java | 36 +++++++++++++++++ .../core/support/SqlCharacterValueTests.java | 36 +++++++++++++++++ 5 files changed, 121 insertions(+), 11 deletions(-) diff --git a/spring-core/src/main/java/org/springframework/core/io/Resource.java b/spring-core/src/main/java/org/springframework/core/io/Resource.java index b7e39449407..54b7084a43e 100644 --- a/spring-core/src/main/java/org/springframework/core/io/Resource.java +++ b/spring-core/src/main/java/org/springframework/core/io/Resource.java @@ -183,6 +183,7 @@ public interface Resource extends InputStreamSource { /** * Determine the content length for this resource. + * @return the content length (or -1 if undetermined) * @throws IOException if the resource cannot be resolved * (in the file system or as some other known physical resource type) */ @@ -190,6 +191,7 @@ public interface Resource extends InputStreamSource { /** * Determine the last-modified timestamp for this resource. + * @return the last-modified timestamp (or 0 if not known) * @throws IOException if the resource cannot be resolved * (in the file system or as some other known physical resource type) */ diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/support/SqlBinaryValue.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/support/SqlBinaryValue.java index 884a55214d0..15d7490c116 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/support/SqlBinaryValue.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/support/SqlBinaryValue.java @@ -71,7 +71,7 @@ public class SqlBinaryValue implements SqlTypeValue { /** * Create a new {@code SqlBinaryValue} for the given content. * @param stream the content stream - * @param length the length of the content + * @param length the length of the content (or -1 if undetermined) */ public SqlBinaryValue(InputStream stream, long length) { this.content = stream; @@ -83,7 +83,7 @@ public class SqlBinaryValue implements SqlTypeValue { *

Consider specifying a {@link Resource} with content length support * when available: {@link SqlBinaryValue#SqlBinaryValue(Resource)}. * @param resource the resource to obtain a content stream from - * @param length the length of the content + * @param length the length of the content (or -1 if undetermined) */ public SqlBinaryValue(InputStreamSource resource, long length) { this.content = resource; @@ -147,10 +147,20 @@ public class SqlBinaryValue implements SqlTypeValue { throws SQLException { if (sqlType == Types.BLOB) { - ps.setBlob(paramIndex, is, length); + if (length >= 0) { + ps.setBlob(paramIndex, is, length); + } + else { + ps.setBlob(paramIndex, is); + } } else { - ps.setBinaryStream(paramIndex, is, length); + if (length >= 0) { + ps.setBinaryStream(paramIndex, is, length); + } + else { + ps.setBinaryStream(paramIndex, is); + } } } diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/support/SqlCharacterValue.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/support/SqlCharacterValue.java index bae27406d27..f0f3b2ad3c2 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/support/SqlCharacterValue.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/support/SqlCharacterValue.java @@ -78,7 +78,7 @@ public class SqlCharacterValue implements SqlTypeValue { /** * Create a new {@code SqlCharacterValue} for the given content. * @param reader the content reader - * @param length the length of the content + * @param length the length of the content (or -1 if undetermined) */ public SqlCharacterValue(Reader reader, long length) { this.content = reader; @@ -88,7 +88,7 @@ public class SqlCharacterValue implements SqlTypeValue { /** * Create a new {@code SqlCharacterValue} for the given content. * @param asciiStream the content as ASCII stream - * @param length the length of the content + * @param length the length of the content (or -1 if undetermined) */ public SqlCharacterValue(InputStream asciiStream, long length) { this.content = asciiStream; @@ -109,8 +109,8 @@ public class SqlCharacterValue implements SqlTypeValue { else if (this.content instanceof Reader reader) { setReader(ps, paramIndex, sqlType, reader, this.length); } - else if (this.content instanceof InputStream asciiStream) { - ps.setAsciiStream(paramIndex, asciiStream, this.length); + else if (this.content instanceof InputStream inputStream) { + setInputStream(ps, paramIndex, inputStream, this.length); } else { throw new IllegalArgumentException("Illegal content type: " + this.content.getClass().getName()); @@ -135,13 +135,39 @@ public class SqlCharacterValue implements SqlTypeValue { throws SQLException { if (sqlType == Types.CLOB) { - ps.setClob(paramIndex, reader, length); + if (length >= 0) { + ps.setClob(paramIndex, reader, length); + } + else { + ps.setClob(paramIndex, reader); + } } else if (sqlType == Types.NCLOB) { - ps.setNClob(paramIndex, reader, length); + if (length >= 0) { + ps.setNClob(paramIndex, reader, length); + } + else { + ps.setNClob(paramIndex, reader); + } } else { - ps.setCharacterStream(paramIndex, reader, length); + if (length >= 0) { + ps.setCharacterStream(paramIndex, reader, length); + } + else { + ps.setCharacterStream(paramIndex, reader); + } + } + } + + private void setInputStream(PreparedStatement ps, int paramIndex, InputStream is, long length) + throws SQLException { + + if (length >= 0) { + ps.setAsciiStream(paramIndex, is, length); + } + else { + ps.setAsciiStream(paramIndex, is); } } diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/core/support/SqlBinaryValueTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/core/support/SqlBinaryValueTests.java index 8ef63956986..323e9bbefdd 100644 --- a/spring-jdbc/src/test/java/org/springframework/jdbc/core/support/SqlBinaryValueTests.java +++ b/spring-jdbc/src/test/java/org/springframework/jdbc/core/support/SqlBinaryValueTests.java @@ -65,6 +65,15 @@ class SqlBinaryValueTests { verify(ps).setBinaryStream(1, content, 3L); } + @Test + void withInputStreamUndeterminedLength() throws SQLException { + InputStream content = new ByteArrayInputStream(new byte[] {0, 1, 2}); + SqlBinaryValue value = new SqlBinaryValue(content, -1); + PreparedStatement ps = mock(); + value.setTypeValue(ps, 1, JdbcUtils.TYPE_UNKNOWN, null); + verify(ps).setBinaryStream(1, content); + } + @Test void withInputStreamForBlob() throws SQLException { InputStream content = new ByteArrayInputStream(new byte[] {0, 1, 2}); @@ -74,6 +83,15 @@ class SqlBinaryValueTests { verify(ps).setBlob(1, content, 3L); } + @Test + void withInputStreamForBlobUndeterminedLength() throws SQLException { + InputStream content = new ByteArrayInputStream(new byte[] {0, 1, 2}); + SqlBinaryValue value = new SqlBinaryValue(content, -1); + PreparedStatement ps = mock(); + value.setTypeValue(ps, 1, Types.BLOB, null); + verify(ps).setBlob(1, content); + } + @Test void withInputStreamSource() throws SQLException { InputStream content = new ByteArrayInputStream(new byte[] {0, 1, 2}); @@ -83,6 +101,15 @@ class SqlBinaryValueTests { verify(ps).setBinaryStream(1, content, 3L); } + @Test + void withInputStreamSourceUndeterminedLength() throws SQLException { + InputStream content = new ByteArrayInputStream(new byte[] {0, 1, 2}); + SqlBinaryValue value = new SqlBinaryValue(() -> content, -1); + PreparedStatement ps = mock(); + value.setTypeValue(ps, 1, JdbcUtils.TYPE_UNKNOWN, null); + verify(ps).setBinaryStream(1, content); + } + @Test void withInputStreamSourceForBlob() throws SQLException { InputStream content = new ByteArrayInputStream(new byte[] {0, 1, 2}); @@ -92,6 +119,15 @@ class SqlBinaryValueTests { verify(ps).setBlob(1, content, 3L); } + @Test + void withInputStreamSourceForBlobUndeterminedLength() throws SQLException { + InputStream content = new ByteArrayInputStream(new byte[] {0, 1, 2}); + SqlBinaryValue value = new SqlBinaryValue(() -> content, -1); + PreparedStatement ps = mock(); + value.setTypeValue(ps, 1, Types.BLOB, null); + verify(ps).setBlob(1, content); + } + @Test void withResource() throws SQLException { byte[] content = new byte[] {0, 1, 2}; diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/core/support/SqlCharacterValueTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/core/support/SqlCharacterValueTests.java index b38732d4e14..ad39536f432 100644 --- a/spring-jdbc/src/test/java/org/springframework/jdbc/core/support/SqlCharacterValueTests.java +++ b/spring-jdbc/src/test/java/org/springframework/jdbc/core/support/SqlCharacterValueTests.java @@ -104,6 +104,15 @@ class SqlCharacterValueTests { verify(ps).setCharacterStream(1, content, 3L); } + @Test + void withReaderUndeterminedLength() throws SQLException { + Reader content = new StringReader("abc"); + SqlCharacterValue value = new SqlCharacterValue(content, -1); + PreparedStatement ps = mock(); + value.setTypeValue(ps, 1, JdbcUtils.TYPE_UNKNOWN, null); + verify(ps).setCharacterStream(1, content); + } + @Test void withReaderForClob() throws SQLException { Reader content = new StringReader("abc"); @@ -113,6 +122,15 @@ class SqlCharacterValueTests { verify(ps).setClob(1, content, 3L); } + @Test + void withReaderForClobUndeterminedLength() throws SQLException { + Reader content = new StringReader("abc"); + SqlCharacterValue value = new SqlCharacterValue(content, -1); + PreparedStatement ps = mock(); + value.setTypeValue(ps, 1, Types.CLOB, null); + verify(ps).setClob(1, content); + } + @Test void withReaderForNClob() throws SQLException { Reader content = new StringReader("abc"); @@ -122,6 +140,15 @@ class SqlCharacterValueTests { verify(ps).setNClob(1, content, 3L); } + @Test + void withReaderForNClobUndeterminedLength() throws SQLException { + Reader content = new StringReader("abc"); + SqlCharacterValue value = new SqlCharacterValue(content, -1); + PreparedStatement ps = mock(); + value.setTypeValue(ps, 1, Types.NCLOB, null); + verify(ps).setNClob(1, content); + } + @Test void withAsciiStream() throws SQLException { InputStream content = new ByteArrayInputStream("abc".getBytes(StandardCharsets.US_ASCII)); @@ -131,4 +158,13 @@ class SqlCharacterValueTests { verify(ps).setAsciiStream(1, content, 3L); } + @Test + void withAsciiStreamUndeterminedLength() throws SQLException { + InputStream content = new ByteArrayInputStream("abc".getBytes(StandardCharsets.US_ASCII)); + SqlCharacterValue value = new SqlCharacterValue(content, -1); + PreparedStatement ps = mock(); + value.setTypeValue(ps, 1, JdbcUtils.TYPE_UNKNOWN, null); + verify(ps).setAsciiStream(1, content); + } + }