diff --git a/exposed-jdbc/src/main/kotlin/org/jetbrains/exposed/sql/statements/jdbc/JdbcPreparedStatementImpl.kt b/exposed-jdbc/src/main/kotlin/org/jetbrains/exposed/sql/statements/jdbc/JdbcPreparedStatementImpl.kt index 61663276c7..e0b7a0c8eb 100644 --- a/exposed-jdbc/src/main/kotlin/org/jetbrains/exposed/sql/statements/jdbc/JdbcPreparedStatementImpl.kt +++ b/exposed-jdbc/src/main/kotlin/org/jetbrains/exposed/sql/statements/jdbc/JdbcPreparedStatementImpl.kt @@ -7,9 +7,12 @@ import org.jetbrains.exposed.sql.statements.StatementResult import org.jetbrains.exposed.sql.statements.api.PreparedStatementApi import org.jetbrains.exposed.sql.vendors.SQLiteDialect import org.jetbrains.exposed.sql.vendors.currentDialect +import java.io.ByteArrayInputStream +import java.io.FileInputStream import java.io.InputStream import java.sql.PreparedStatement import java.sql.ResultSet +import java.sql.SQLFeatureNotSupportedException import java.sql.Statement import java.sql.Types @@ -77,11 +80,32 @@ class JdbcPreparedStatementImpl( } } + private fun streamHasKnownLength(inputStream: InputStream) = + when { + // streams with known length where available matches the actual length + inputStream is ByteArrayInputStream -> true + // this will not work for filesystem sockets or fifos, but it's the best we can do + // unless java introduces a new method in InputStream for checking the number of bytes + // available (not only the number of bytes that can be read without blocking). + inputStream is FileInputStream && inputStream.available() < Int.MAX_VALUE -> true + else -> false + } + + @SuppressWarnings("SwallowedException") override fun setInputStream(index: Int, inputStream: InputStream, setAsBlobObject: Boolean) { if (setAsBlobObject) { statement.setBlob(index, inputStream) } else { - statement.setBinaryStream(index, inputStream, inputStream.available()) + try { + if (streamHasKnownLength(inputStream)) { + statement.setBinaryStream(index, inputStream, inputStream.available()) + } else { + statement.setBinaryStream(index, inputStream) + } + } catch (e: SQLFeatureNotSupportedException) { + // not all drivers support setBinaryStream, fallback to byte-array + statement.setBytes(index, inputStream.readBytes()) + } } } diff --git a/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/types/BlobColumnTypeTests.kt b/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/types/BlobColumnTypeTests.kt index 48b5ba6c64..6adbd86404 100644 --- a/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/types/BlobColumnTypeTests.kt +++ b/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/types/BlobColumnTypeTests.kt @@ -17,6 +17,8 @@ import org.jetbrains.exposed.sql.tests.shared.assertTrue import org.jetbrains.exposed.sql.tests.shared.expectException import org.jetbrains.exposed.sql.vendors.PostgreSQLDialect import org.junit.Test +import java.io.ByteArrayInputStream +import java.io.SequenceInputStream import java.nio.charset.Charset import kotlin.random.Random import kotlin.test.assertContentEquals @@ -46,7 +48,12 @@ class BlobColumnTypeTests : DatabaseTestsBase() { val shortBytes = "Hello there!".toByteArray() val longBytes = Random.nextBytes(1024) val shortBlob = ExposedBlob(shortBytes) - val longBlob = ExposedBlob(longBytes) + val longBlob = ExposedBlob( + inputStream = SequenceInputStream( + ByteArrayInputStream(longBytes, 0, 512), + ByteArrayInputStream(longBytes, 512, 512) + ) + ) val id1 = BlobTable.insert { it[content] = shortBlob