Skip to content

Commit

Permalink
fix: EXPOSED-252 Json contains() throws with iterable as argument (#1963
Browse files Browse the repository at this point in the history
)

If a json column is declared as storing a list/set/array of values and one of
these iterables is used with the function contains(), a syntax error exception
is thrown.

The function now properly wraps the serialized json list with quotes when the
SQL is generated.
  • Loading branch information
bog-walk committed Jan 9, 2024
1 parent 04a6c92 commit 412a98f
Show file tree
Hide file tree
Showing 2 changed files with 41 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import org.jetbrains.exposed.sql.IColumnType
import org.jetbrains.exposed.sql.Op
import org.jetbrains.exposed.sql.QueryBuilder
import org.jetbrains.exposed.sql.SqlExpressionBuilder.asLiteral
import org.jetbrains.exposed.sql.stringLiteral
import org.jetbrains.exposed.sql.vendors.currentDialect

// Operator Classes
Expand Down Expand Up @@ -66,8 +67,10 @@ fun ExpressionWithColumnType<*>.contains(candidate: Expression<*>, path: String?
* **Note:** Optional [path] argument is not supported by all vendors; please check the documentation.
* @sample org.jetbrains.exposed.sql.json.JsonColumnTests.testJsonContains
*/
fun <T> ExpressionWithColumnType<*>.contains(candidate: T, path: String? = null): Contains =
Contains(this, asLiteral(candidate), path, columnType)
fun <T> ExpressionWithColumnType<*>.contains(candidate: T, path: String? = null): Contains = when (candidate) {
is Iterable<*>, is Array<*> -> Contains(this, stringLiteral(asLiteral(candidate).toString()), path, columnType)
else -> Contains(this, asLiteral(candidate), path, columnType)
}

/**
* Checks whether data exists within [this] JSON expression at the specified [path].
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import kotlinx.serialization.builtins.ArraySerializer
import kotlinx.serialization.builtins.ListSerializer
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import org.jetbrains.exposed.dao.id.EntityID
import org.jetbrains.exposed.dao.id.IntIdTable
import org.jetbrains.exposed.exceptions.UnsupportedByDialectException
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
Expand All @@ -14,6 +16,7 @@ import org.jetbrains.exposed.sql.tests.DatabaseTestsBase
import org.jetbrains.exposed.sql.tests.TestDB
import org.jetbrains.exposed.sql.tests.currentDialectTest
import org.jetbrains.exposed.sql.tests.shared.assertEqualCollections
import org.jetbrains.exposed.sql.tests.shared.assertEqualLists
import org.jetbrains.exposed.sql.tests.shared.assertEquals
import org.jetbrains.exposed.sql.tests.shared.assertTrue
import org.jetbrains.exposed.sql.tests.shared.expectException
Expand Down Expand Up @@ -267,6 +270,39 @@ class JsonColumnTests : DatabaseTestsBase() {
}
}

@Test
fun testJsonContainsWithIterables() {
val iterables = object : IntIdTable("iterables") {
val userList = json<List<User>>("user_list", Json.Default)
val userSet = json<Set<User>>("user_set", Json.Default)
val userArray = json<Array<User>>("user_array", Json.Default)
}

fun selectIdWhere(condition: SqlExpressionBuilder.() -> Op<Boolean>): List<EntityID<Int>> {
val query = iterables.select(iterables.id).where(SqlExpressionBuilder.condition())
return query.map { it[iterables.id] }
}

withTables(excludeSettings = jsonContainsNotSupported, iterables) {
val user1 = User("A", "Team A")
val user2 = User("B", "Team B")
val id1 = iterables.insertAndGetId {
it[userList] = listOf(user1, user2)
it[userSet] = setOf(user1)
it[userArray] = arrayOf(user1, user2)
}
val id2 = iterables.insertAndGetId {
it[userList] = listOf(user2)
it[userSet] = setOf(user2)
it[userArray] = arrayOf(user1, user2)
}

assertEqualLists(listOf(id1), selectIdWhere { iterables.userList.contains(listOf(user1)) })
assertEqualLists(listOf(id2), selectIdWhere { iterables.userSet.contains(setOf(user2)) })
assertEqualLists(listOf(id1, id2), selectIdWhere { iterables.userArray.contains(arrayOf(user1, user2)) })
}
}

@Test
fun testJsonWithDefaults() {
val defaultUser = User("UNKNOWN", "UNASSIGNED")
Expand Down

0 comments on commit 412a98f

Please sign in to comment.