From adce45898add6fb71c75243baa955df6a61f2149 Mon Sep 17 00:00:00 2001 From: Wolfgang Jung Date: Thu, 29 Jun 2023 16:03:21 +0200 Subject: [PATCH 1/4] Fix for EXPOSED-86, allowing blobs with streams of unknown size --- .../jdbc/JdbcPreparedStatementImpl.kt | 23 ++++++++++++++++++- .../exposed/sql/tests/shared/DDLTests.kt | 9 +++++++- 2 files changed, 30 insertions(+), 2 deletions(-) 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 d21bb694a9..05c51b5091 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 @@ -5,12 +5,15 @@ import org.jetbrains.exposed.sql.BlobColumnType import org.jetbrains.exposed.sql.IColumnType import org.jetbrains.exposed.sql.statements.StatementResult import org.jetbrains.exposed.sql.statements.api.PreparedStatementApi +import java.io.ByteArrayInputStream +import java.io.FileInputStream import org.jetbrains.exposed.sql.vendors.SQLiteDialect import org.jetbrains.exposed.sql.vendors.currentDialect import java.io.InputStream import java.sql.PreparedStatement import java.sql.ResultSet import java.sql.Statement +import java.sql.SQLFeatureNotSupportedException import java.sql.Types /** @@ -80,7 +83,25 @@ class JdbcPreparedStatementImpl( } override fun setInputStream(index: Int, inputStream: InputStream) { - statement.setBinaryStream(index, inputStream, inputStream.available()) + try { + when { + // streams with known length where available matches the actual length + inputStream is ByteArrayInputStream -> + statement.setBinaryStream(index, inputStream, inputStream.available()) + + // FileInputStream.available() returns returns Int.MAX_VALUE + // if the underlying file is larger than 2GB + inputStream is FileInputStream && inputStream.available() < Int.MAX_VALUE -> + statement.setBinaryStream(index, inputStream, inputStream.available()) + + // default handling for unknown length + else -> statement.setBinaryStream(index, inputStream) + + } + } catch (e: SQLFeatureNotSupportedException) { + // fallback to bytes + statement.setBytes(index, inputStream.readBytes()) + } } override fun setArray(index: Int, type: String, array: Array<*>) { diff --git a/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/DDLTests.kt b/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/DDLTests.kt index 89ddab0fcd..620a49e469 100644 --- a/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/DDLTests.kt +++ b/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/DDLTests.kt @@ -25,6 +25,8 @@ import org.jetbrains.exposed.sql.vendors.SQLiteDialect import org.junit.Assume import org.junit.Test import org.postgresql.util.PGobject +import java.io.ByteArrayInputStream +import java.io.SequenceInputStream import java.util.* import kotlin.random.Random import kotlin.test.assertNotNull @@ -612,7 +614,12 @@ class DDLTests : 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 = t.insert { it[t.b] = shortBlob From 4a8f99f42a6a97f4dd0c7732933cd69ee15c5808 Mon Sep 17 00:00:00 2001 From: Wolfgang Jung Date: Sun, 23 Jun 2024 18:34:24 +0200 Subject: [PATCH 2/4] Resolved conflicts and moved test from DDLTests into BlobColumnTypeTests --- .../jdbc/JdbcPreparedStatementImpl.kt | 28 ++++++++++++++++--- .../tests/shared/types/BlobColumnTypeTests.kt | 9 +++++- 2 files changed, 32 insertions(+), 5 deletions(-) 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 92a25a177d..0fa92a1ecb 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 @@ -5,15 +5,15 @@ import org.jetbrains.exposed.sql.BlobColumnType import org.jetbrains.exposed.sql.IColumnType import org.jetbrains.exposed.sql.statements.StatementResult import org.jetbrains.exposed.sql.statements.api.PreparedStatementApi -import java.io.ByteArrayInputStream -import java.io.FileInputStream 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.Statement import java.sql.SQLFeatureNotSupportedException +import java.sql.Statement import java.sql.Types /** @@ -80,11 +80,31 @@ 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 + } + 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 From ff92cd180cef676e41225af1e9ad74a6ec53cd60 Mon Sep 17 00:00:00 2001 From: Wolfgang Jung Date: Sun, 23 Jun 2024 18:34:24 +0200 Subject: [PATCH 3/4] Resolved conflicts and moved test from DDLTests into BlobColumnTypeTests --- .../kotlin/org/jetbrains/exposed/sql/tests/shared/DDLTests.kt | 3 --- 1 file changed, 3 deletions(-) diff --git a/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/DDLTests.kt b/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/DDLTests.kt index 649525ecfd..1d1bb354bf 100644 --- a/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/DDLTests.kt +++ b/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/DDLTests.kt @@ -23,9 +23,6 @@ import org.jetbrains.exposed.sql.vendors.SQLServerDialect import org.jetbrains.exposed.sql.vendors.SQLiteDialect import org.junit.Assume import org.junit.Test -import org.postgresql.util.PGobject -import java.io.ByteArrayInputStream -import java.io.SequenceInputStream import java.util.* import kotlin.test.assertNotNull import kotlin.test.expect From 590f98ee22c59f0304d808e92121964c73b15dfd Mon Sep 17 00:00:00 2001 From: Wolfgang Jung Date: Sun, 23 Jun 2024 18:34:24 +0200 Subject: [PATCH 4/4] Resolved conflicts and moved test from DDLTests into BlobColumnTypeTests --- .../exposed/sql/statements/jdbc/JdbcPreparedStatementImpl.kt | 1 + 1 file changed, 1 insertion(+) 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 0fa92a1ecb..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 @@ -91,6 +91,7 @@ class JdbcPreparedStatementImpl( else -> false } + @SuppressWarnings("SwallowedException") override fun setInputStream(index: Int, inputStream: InputStream, setAsBlobObject: Boolean) { if (setAsBlobObject) { statement.setBlob(index, inputStream)