diff --git a/exposed-json/src/main/kotlin/org/jetbrains/exposed/v1/json/JsonColumnType.kt b/exposed-json/src/main/kotlin/org/jetbrains/exposed/v1/json/JsonColumnType.kt index 66fbaf28da..9321abe613 100644 --- a/exposed-json/src/main/kotlin/org/jetbrains/exposed/v1/json/JsonColumnType.kt +++ b/exposed-json/src/main/kotlin/org/jetbrains/exposed/v1/json/JsonColumnType.kt @@ -12,6 +12,7 @@ import org.jetbrains.exposed.v1.core.statements.api.RowApi import org.jetbrains.exposed.v1.core.vendors.H2Dialect import org.jetbrains.exposed.v1.core.vendors.PostgreSQLDialect import org.jetbrains.exposed.v1.core.vendors.currentDialect +import java.sql.Clob /** * Column for storing JSON data, either in non-binary text format or the vendor's default JSON type format. @@ -73,14 +74,18 @@ open class JsonColumnType( } override fun readObject(rs: RowApi, index: Int): Any? { - return if (currentDialect is PostgreSQLDialect) { - rs.getString(index) - } else { - super.readObject(rs, index) + if (currentDialect is PostgreSQLDialect) return rs.getString(index) + + return when (val value = super.readObject(rs, index)) { + is Clob -> value.readText() + else -> value } } } +/** Reads this large object completely, releasing the reader it was streamed through. */ +private fun Clob.readText(): String = characterStream.use { it.readText() } + /** * Creates a column, with the specified [name], for storing JSON data. * diff --git a/exposed-json/src/test/kotlin/org/jetbrains/exposed/v1/json/JsonColumnTests.kt b/exposed-json/src/test/kotlin/org/jetbrains/exposed/v1/json/JsonColumnTests.kt index 412da01941..ed5c8fc5b4 100644 --- a/exposed-json/src/test/kotlin/org/jetbrains/exposed/v1/json/JsonColumnTests.kt +++ b/exposed-json/src/test/kotlin/org/jetbrains/exposed/v1/json/JsonColumnTests.kt @@ -13,6 +13,7 @@ import org.jetbrains.exposed.v1.core.vendors.PostgreSQLDialect import org.jetbrains.exposed.v1.core.vendors.SQLServerDialect import org.jetbrains.exposed.v1.exceptions.UnsupportedByDialectException import org.jetbrains.exposed.v1.jdbc.* +import org.jetbrains.exposed.v1.jdbc.transactions.inTopLevelTransaction import org.jetbrains.exposed.v1.tests.DatabaseTestsBase import org.jetbrains.exposed.v1.tests.MISSING_R2DBC_TEST import org.jetbrains.exposed.v1.tests.TestDB @@ -492,4 +493,49 @@ class JsonColumnTests : DatabaseTestsBase() { assertEquals(newUser, result[infoAsJson]) } } + + private val textIsNotClob = TestDB.ALL - TestDB.ALL_ORACLE_LIKE + + @Test + fun testJsonStoredAsClob() { + val textTester = object : IntIdTable("json_large_text_tester") { + val payload = text("payload") + } + val jsonTester = object : IntIdTable("json_large_text_tester") { + val payload = json("payload", Json.Default) + } + + val newUser = User("Pro", "Alpha") + var writtenAsText: EntityID? = null + var writtenAsJson: EntityID? = null + + withTables(excludeSettings = textIsNotClob, textTester) { + val columnDdl = textTester.payload.descriptionDdl() + kotlin.test.assertTrue( + columnDdl.contains("CLOB"), + "The column must be created as a CLOB for this test to exercise anything, but was: $columnDdl" + ) + + inTopLevelTransaction { + maxAttempts = 1 + val textId = textTester.insertAndGetId { it[payload] = Json.encodeToString(newUser) } + val jsonId = jsonTester.insertAndGetId { it[payload] = newUser } + writtenAsText = textId + writtenAsJson = jsonId + + for (id in listOf(textId, jsonId)) { + val row = jsonTester.selectAll().where { jsonTester.id eq id }.single() + assertEquals(newUser, row[jsonTester.payload]) + } + } + + inTopLevelTransaction { + maxAttempts = 1 + for (id in listOf(assertNotNull(writtenAsText), assertNotNull(writtenAsJson))) { + val row = jsonTester.selectAll().where { jsonTester.id eq id }.single() + assertEquals(newUser, row[jsonTester.payload]) + } + } + } + } } diff --git a/exposed-r2dbc-tests/src/test/kotlin/org/jetbrains/exposed/v1/r2dbc/sql/tests/json/JsonColumnTests.kt b/exposed-r2dbc-tests/src/test/kotlin/org/jetbrains/exposed/v1/r2dbc/sql/tests/json/JsonColumnTests.kt index 80b23b31d7..9cb45a86bc 100644 --- a/exposed-r2dbc-tests/src/test/kotlin/org/jetbrains/exposed/v1/r2dbc/sql/tests/json/JsonColumnTests.kt +++ b/exposed-r2dbc-tests/src/test/kotlin/org/jetbrains/exposed/v1/r2dbc/sql/tests/json/JsonColumnTests.kt @@ -30,6 +30,7 @@ import org.jetbrains.exposed.v1.r2dbc.tests.shared.assertEqualLists import org.jetbrains.exposed.v1.r2dbc.tests.shared.assertEquals import org.jetbrains.exposed.v1.r2dbc.tests.shared.assertTrue import org.jetbrains.exposed.v1.r2dbc.tests.shared.expectException +import org.jetbrains.exposed.v1.r2dbc.transactions.inTopLevelSuspendTransaction import org.junit.jupiter.api.Test import kotlin.test.assertContentEquals import kotlin.test.assertNotNull @@ -464,4 +465,47 @@ class JsonColumnTests : R2dbcDatabaseTestsBase() { assertEquals(newUser, result[infoAsJson]) } } + + @Test + fun testJsonStoredAsClob() { + val textTester = object : IntIdTable("json_large_text_tester") { + val payload = text("payload") + } + val jsonTester = object : IntIdTable("json_large_text_tester") { + val payload = json("payload", Json.Default) + } + + val newUser = User("Pro", "Alpha") + var writtenAsText: EntityID? = null + var writtenAsJson: EntityID? = null + + withTables(excludeSettings = TestDB.ALL - TestDB.ALL_ORACLE_LIKE, textTester) { + val columnDdl = textTester.payload.descriptionDdl() + kotlin.test.assertTrue( + columnDdl.contains("CLOB"), + "The column must be created as a CLOB for this test to exercise anything, but was: $columnDdl" + ) + + inTopLevelSuspendTransaction { + maxAttempts = 1 + val textId = textTester.insertAndGetId { it[payload] = Json.encodeToString(newUser) } + val jsonId = jsonTester.insertAndGetId { it[payload] = newUser } + writtenAsText = textId + writtenAsJson = jsonId + + for (id in listOf(textId, jsonId)) { + val row = jsonTester.selectAll().where { jsonTester.id eq id }.single() + assertEquals(newUser, row[jsonTester.payload]) + } + } + + inTopLevelSuspendTransaction { + maxAttempts = 1 + for (id in listOf(assertNotNull(writtenAsText), assertNotNull(writtenAsJson))) { + val row = jsonTester.selectAll().where { jsonTester.id eq id }.single() + assertEquals(newUser, row[jsonTester.payload]) + } + } + } + } }