From 7211d4275784b77d68b18cf1867d2f6f2ec361ad Mon Sep 17 00:00:00 2001 From: bog-walk <82039410+bog-walk@users.noreply.github.com> Date: Mon, 7 Aug 2023 10:14:10 -0400 Subject: [PATCH] fix: EXPOSED-123 ExposedBlob.getBytes() fails on Oracle with IOException Mark invalid (#1824) The following tests fail when run using Oracle: DDLTests/testBlob() DDLTests/testBlobDefault() EntityTests/testBlobField() They all fail with the same error: Caused by: java.io.IOException: Mark invalid or stream not marked. at oracle.jdbc.driver.OracleBlobInputStream.reset(OracleBlobInputStream.java:308) at org.jetbrains.exposed.sql.statements.api.ExposedBlob.getBytes(ExposedBlob.kt:13) The exception is thrown after a BLOB value is retrieved and read from the DB because the type retrieved is oracle.jdbc.driver.OracleBlobInputStream, which either does not set markedByte to not be default by calling mark() or invalidates mark. This makes reset() throw. This is unlike an ExposedBlob instantiated using the secondary constructor that creates a ByteArrayInputStream from a ByteArray, which overrides reset() to not throw. This exception is handled inside getBytes() to ensure that the retrieved BLOB value is still read as a stream. --- .../exposed/sql/statements/api/ExposedBlob.kt | 22 ++++++++++++++----- .../exposed/sql/tests/shared/DDLTests.kt | 20 +++++++---------- .../sql/tests/shared/entities/EntityTests.kt | 3 ++- 3 files changed, 26 insertions(+), 19 deletions(-) diff --git a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/statements/api/ExposedBlob.kt b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/statements/api/ExposedBlob.kt index f218581d1c..1b05939d60 100644 --- a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/statements/api/ExposedBlob.kt +++ b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/statements/api/ExposedBlob.kt @@ -1,5 +1,8 @@ package org.jetbrains.exposed.sql.statements.api +import org.jetbrains.exposed.sql.vendors.OracleDialect +import org.jetbrains.exposed.sql.vendors.currentDialectIfAvailable +import java.io.IOException import java.io.InputStream class ExposedBlob(inputStream: InputStream) { @@ -8,13 +11,20 @@ class ExposedBlob(inputStream: InputStream) { var inputStream = inputStream private set - val bytes get() = inputStream.readBytes().also { - if (inputStream.markSupported()) { - inputStream.reset() - } else { - inputStream = it.inputStream() + val bytes: ByteArray + get() = inputStream.readBytes().also { + if (inputStream.markSupported()) { + try { + inputStream.reset() + } catch (_: IOException) { + if (currentDialectIfAvailable is OracleDialect) { + inputStream = it.inputStream() + } + } + } else { + inputStream = it.inputStream() + } } - } override fun equals(other: Any?): Boolean { if (this === other) return true 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 b61cda8fbd..5aa1ed30f2 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 @@ -442,7 +442,8 @@ class DDLTests : DatabaseTestsBase() { } } - @Test fun testBlob() { + @Test + fun testBlob() { val t = object : Table("t1") { val id = integer("id").autoIncrement() val b = blob("blob") @@ -455,11 +456,6 @@ class DDLTests : DatabaseTestsBase() { val longBytes = Random.nextBytes(1024) val shortBlob = ExposedBlob(shortBytes) val longBlob = ExposedBlob(longBytes) -// if (currentDialectTest.dataTypeProvider.blobAsStream) { -// SerialBlob(bytes) -// } else connection.createBlob().apply { -// setBytes(1, bytes) -// } val id1 = t.insert { it[t.b] = shortBlob @@ -497,7 +493,7 @@ class DDLTests : DatabaseTestsBase() { val defaultBlobStr = "test" val defaultBlob = ExposedBlob(defaultBlobStr.encodeToByteArray()) - val TestTable = object : Table("TestTable") { + val testTable = object : Table("TestTable") { val number = integer("number") val blobWithDefault = blob("blobWithDefault").default(defaultBlob) } @@ -506,18 +502,18 @@ class DDLTests : DatabaseTestsBase() { when (testDb) { TestDB.MYSQL -> { expectException { - SchemaUtils.create(TestTable) + SchemaUtils.create(testTable) } } else -> { - SchemaUtils.create(TestTable) + SchemaUtils.create(testTable) - TestTable.insert { + testTable.insert { it[number] = 1 } - assertEquals(defaultBlobStr, String(TestTable.selectAll().first()[TestTable.blobWithDefault].bytes)) + assertEquals(defaultBlobStr, String(testTable.selectAll().first()[testTable.blobWithDefault].bytes)) - SchemaUtils.drop(TestTable) + SchemaUtils.drop(testTable) } } } diff --git a/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/entities/EntityTests.kt b/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/entities/EntityTests.kt index d5676439e3..bc9c567f0b 100644 --- a/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/entities/EntityTests.kt +++ b/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/entities/EntityTests.kt @@ -111,7 +111,8 @@ class EntityTests : DatabaseTestsBase() { } } - @Test fun testBlobField() { + @Test + fun testBlobField() { withTables(EntityTestsData.YTable) { val y1 = EntityTestsData.YEntity.new { x = false