Andres
01/23/2025, 5:38 PMarray column but I am struggling to understand how I could do a containsAny or overlaps.
ex:
val listColumn: Column<List<String>> = array<String>("list_column")
.where {
Table.listColumn containsAny listOf("a', "b")
}
Am I missing something or is this not possible currently without an extension?Andres
01/23/2025, 6:13 PMimport org.jetbrains.exposed.sql.Column
import org.jetbrains.exposed.sql.Expression
import org.jetbrains.exposed.sql.ExpressionWithColumnType
import org.jetbrains.exposed.sql.Op
import org.jetbrains.exposed.sql.QueryBuilder
class ArrayContainsAnyOp<T>(
private val column: Expression<T>,
private val elements: Iterable<*>,
) : Op<Boolean>() {
override fun toQueryBuilder(queryBuilder: QueryBuilder) =
queryBuilder {
val columnName = (column as? Column<*>)?.name ?: column.toString().substringAfterLast('.')
append(elements.joinToString(" OR ") { "'$it' = ANY($columnName)" })
}
}
infix fun <T> ExpressionWithColumnType<T>.containsAny(iterable: Iterable<*>) = ArrayContainsAnyOp(this, iterable)
But still curious if there is a better way.Chantal Loncle
01/24/2025, 12:26 AMarray() column type does not support every single existing array operator or function, but one of the supported options is ANY() (and ALL()), via anyFrom() and allFrom(). They both accept multiple argument types (like subqueries, collections, columns, tables).
You can use the preceding comparison operator of your choice to build the clause, for example:
Table
.selectAll()
.where {
Table.listColumn eq anyFrom(listOf("a", "b"))
}
// generates SQL:
// ... WHERE list_column = ANY(ARRAY['a','b'])
More info about the array type and its supported functions can be found here in the docs.Andres
01/24/2025, 6:27 PMorg.postgresql.util.PSQLException: ERROR: operator does not exist: character varying[] = text\n Hint: No operator matches the given name and argument types. You might need to add explicit type casts.\n Position: 201Chantal Loncle
01/24/2025, 7:38 PMANY().
The SQL I shared previously would not be valid if the left-side stores a collection of data.
If that is the case, you could use anyFrom() with one of our compound operators, something like:
val stringsOverlap = listOf("C", "D").map { str ->
Op.build { stringParam(str) eq anyFrom(TestTable.strings) }
}.compoundOr()
TestTable
.selectAll()
.where { stringsOverlap }
// generates SQL like:
// ... WHERE ('C' = ANY (tester.strings)) OR ('D' = ANY (tester.strings))
If that doesn't get the results you need, is it possible that the operator you want is PostgreSQL's &&? It's not currently supported out-of-the-box, so here's the implementation in case:
class OverlapOp(
val target: Expression<*>,
val candidate: Expression<*>
) : Op<Boolean>(), ComplexExpression {
override fun toQueryBuilder(queryBuilder: QueryBuilder) = queryBuilder {
append(target, " && ", candidate)
}
}
inline infix fun <reified T : Any, L : List<T>> ExpressionWithColumnType<L>.overlap(
collection: L
) =
OverlapOp(this, arrayLiteral(collection))
TestTable
.selectAll()
.where {
TestTable.strings overlap listOf("C", "D")
}
// generates SQL like:
// ... WHERE tester.strings && ARRAY['C','D']Andres
01/24/2025, 7:42 PMlast_known_related_camera_serial_numbers character varying[]
SELECT * FROM monitorstatusdetails WHERE last_known_related_camera_serial_numbers = ANY(ARRAY['6d', 'o8'])
[2025-01-24 14:41:43] [42883] ERROR: operator does not exist: character varying[] = text
[2025-01-24 14:41:43] Hint: No operator matches the given name and argument types. You might need to add explicit type casts.
[2025-01-24 14:41:43] Position: 83Andres
01/24/2025, 7:43 PMALTER TABLE monitorstatusdetails ADD COLUMN last_known_related_camera_serial_numbers VARCHAR ARRAY;
Is it because I am using VARCHAR? I wont pretend I am any kind of database expertChantal Loncle
01/24/2025, 7:48 PManyFrom() with compoundOr(). That should work for you.Andres
01/24/2025, 7:53 PMpublic fun List<Op<Boolean>>.compoundOr(): Op<Boolean> defined in org.jetbrains.exposed.sqlChantal Loncle
01/24/2025, 7:57 PMval stringsOverlap = listOf("C", "D").map { str ->
Op.build { stringParam(str) eq anyFrom(TestTable.strings) }
}.compoundOr()
TestTable
.selectAll()
.where { stringsOverlap }
// generates SQL like:
// ... WHERE ('C' = ANY (tester.strings)) OR ('D' = ANY (tester.strings))Andres
01/24/2025, 8:04 PMAndres
01/28/2025, 7:16 PMfun <T> containsAny(
column: Column<List<T>>,
items: List<T>,
) = items.map { item ->
Op.build { stringParam(item.toString()) eq anyFrom(column) }
}.compoundOr()