Skip to content

Commit

Permalink
fix: EXPOSED-130 Logger throws ClassCastException with JSON and ListS…
Browse files Browse the repository at this point in the history
…erializer (#1835)

* fix: EXPOSED-130 Logger throws ClassCastException with JSON and ListSerializer

When a json/jsonb column is created that takes a List or ListSerializer, it correctly
deserializes/serializes the json array to and from the database. However, attempting
to log the statements involved throws ClassCastException, whereas there is no
issue if the column is created to take an Array or ArraySerializer instead.

The former extend Iterable, which is being handled separately in ColumnType.valueToString().
Overriding this branch to directly use the result of nonNullValueToString() fixes
the logger issue.
  • Loading branch information
bog-walk authored Aug 17, 2023
1 parent 8fe5c1c commit fb94e6a
Show file tree
Hide file tree
Showing 4 changed files with 90 additions and 0 deletions.
1 change: 1 addition & 0 deletions exposed-json/api/exposed-json.api
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ public class org/jetbrains/exposed/sql/json/JsonColumnType : org/jetbrains/expos
public fun setParameter (Lorg/jetbrains/exposed/sql/statements/api/PreparedStatementApi;ILjava/lang/Object;)V
public fun sqlType ()Ljava/lang/String;
public fun valueFromDB (Ljava/lang/Object;)Ljava/lang/Object;
public fun valueToString (Ljava/lang/Object;)Ljava/lang/String;
}

public final class org/jetbrains/exposed/sql/json/JsonColumnTypeKt {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ open class JsonColumnType<T : Any>(
@Suppress("UNCHECKED_CAST")
override fun notNullValueToDB(value: Any) = serialize(value as T)

override fun valueToString(value: Any?): String = when (value) {
is Iterable<*> -> nonNullValueToString(value)
else -> super.valueToString(value)
}

override fun nonNullValueToString(value: Any): String {
return when (currentDialect) {
is H2Dialect -> "JSON '${notNullValueToDB(value)}'"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package org.jetbrains.exposed.sql.json

import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.builtins.ArraySerializer
import kotlinx.serialization.builtins.ListSerializer
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import org.jetbrains.exposed.exceptions.UnsupportedByDialectException
Expand All @@ -15,6 +18,7 @@ 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 kotlin.test.assertContentEquals

class JsonBColumnTests : DatabaseTestsBase() {
private val binaryJsonNotSupportedDB = listOf(TestDB.SQLITE, TestDB.SQLSERVER) + TestDB.ORACLE
Expand Down Expand Up @@ -264,4 +268,42 @@ class JsonBColumnTests : DatabaseTestsBase() {
}
}
}

@OptIn(ExperimentalSerializationApi::class)
@Test
fun testLoggerWithJsonBCollections() {
val iterables = object : Table("iterables_tester") {
val userList = jsonb("user_list", Json.Default, ListSerializer(User.serializer()))
val intList = jsonb<List<Int>>("int_list", Json.Default)
val userArray = jsonb("user_array", Json.Default, ArraySerializer(User.serializer()))
val intArray = jsonb<IntArray>("int_array", Json.Default)
}

withDb(excludeSettings = binaryJsonNotSupportedDB) { testDb ->
excludingH2Version1(testDb) {
// the logger is left in to test that it does not throw ClassCastException on insertion of iterables
addLogger(StdOutSqlLogger)
SchemaUtils.create(iterables)

val user1 = User("A", "Team A")
val user2 = User("B", "Team B")
val integerList = listOf(1, 2, 3)
val integerArray = intArrayOf(1, 2, 3)
iterables.insert {
it[userList] = listOf(user1, user2)
it[intList] = integerList
it[userArray] = arrayOf(user1, user2)
it[intArray] = integerArray
}

val result = iterables.selectAll().single()
assertEqualCollections(listOf(user1, user2), result[iterables.userList])
assertEqualCollections(integerList, result[iterables.intList])
assertContentEquals(arrayOf(user1, user2), result[iterables.userArray])
assertContentEquals(integerArray, result[iterables.intArray])

SchemaUtils.drop(iterables)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package org.jetbrains.exposed.sql.json

import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.SerializationException
import kotlinx.serialization.builtins.ArraySerializer
import kotlinx.serialization.builtins.ListSerializer
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import org.jetbrains.exposed.exceptions.UnsupportedByDialectException
Expand All @@ -18,6 +21,7 @@ import org.jetbrains.exposed.sql.vendors.OracleDialect
import org.jetbrains.exposed.sql.vendors.PostgreSQLDialect
import org.jetbrains.exposed.sql.vendors.SQLServerDialect
import org.junit.Test
import kotlin.test.assertContentEquals

class JsonColumnTests : DatabaseTestsBase() {
@Test
Expand Down Expand Up @@ -299,4 +303,42 @@ class JsonColumnTests : DatabaseTestsBase() {
}
}
}

@OptIn(ExperimentalSerializationApi::class)
@Test
fun testLoggerWithJsonCollections() {
val iterables = object : Table("iterables_tester") {
val userList = json("user_list", Json.Default, ListSerializer(User.serializer()))
val intList = json<List<Int>>("int_list", Json.Default)
val userArray = json("user_array", Json.Default, ArraySerializer(User.serializer()))
val intArray = json<IntArray>("int_array", Json.Default)
}

withDb { testDb ->
excludingH2Version1(testDb) {
// the logger is left in to test that it does not throw ClassCastException on insertion of iterables
addLogger(StdOutSqlLogger)
SchemaUtils.create(iterables)

val user1 = User("A", "Team A")
val user2 = User("B", "Team B")
val integerList = listOf(1, 2, 3)
val integerArray = intArrayOf(1, 2, 3)
iterables.insert {
it[userList] = listOf(user1, user2)
it[intList] = integerList
it[userArray] = arrayOf(user1, user2)
it[intArray] = integerArray
}

val result = iterables.selectAll().single()
assertEqualCollections(listOf(user1, user2), result[iterables.userList])
assertEqualCollections(integerList, result[iterables.intList])
assertContentEquals(arrayOf(user1, user2), result[iterables.userArray])
assertContentEquals(integerArray, result[iterables.intArray])

SchemaUtils.drop(iterables)
}
}
}
}

0 comments on commit fb94e6a

Please sign in to comment.