Skip to content

Commit

Permalink
refactor: Make call to registerArgument function type-safe in AllAn…
Browse files Browse the repository at this point in the history
…yFromArrayOp.kt

When calling the `registerArgument` function in the overriden `registerSubSearchArgument` function in `AllAnyFromArrayOp`, there is a type mismatch between `sqlType` and `argument`'s type because there is a compile-time guarantee that ArrayColumnType only receives a value of type `List` and not `Array`. This mismatch results in a compilation error when trying to guarantee type-safety with the refactor of IColumnType. Therefore, this is a necessary preparation step for the refactor of IColumnType to make it and its subclasses type-safe.

To avoid introducing a breaking change, the functions `anyFrom` and `allFrom` in SQLExpressionBuilder.kt retain the same signature, but convert the `Array` to a `List`. In addition, these two functions were overloaded with ones that accept `List` instead of `Array`.
  • Loading branch information
joc-a committed Feb 29, 2024
1 parent c2a43a4 commit c3620bd
Show file tree
Hide file tree
Showing 4 changed files with 99 additions and 5 deletions.
4 changes: 2 additions & 2 deletions exposed-core/api/exposed-core.api
Original file line number Diff line number Diff line change
Expand Up @@ -2696,9 +2696,9 @@ public final class org/jetbrains/exposed/sql/functions/math/TanFunction : org/je
}

public final class org/jetbrains/exposed/sql/ops/AllAnyFromArrayOp : org/jetbrains/exposed/sql/ops/AllAnyFromBaseOp {
public fun <init> (Z[Ljava/lang/Object;Lorg/jetbrains/exposed/sql/ColumnType;)V
public fun <init> (ZLjava/util/List;Lorg/jetbrains/exposed/sql/ColumnType;)V
public synthetic fun registerSubSearchArgument (Lorg/jetbrains/exposed/sql/QueryBuilder;Ljava/lang/Object;)V
public fun registerSubSearchArgument (Lorg/jetbrains/exposed/sql/QueryBuilder;[Ljava/lang/Object;)V
public fun registerSubSearchArgument (Lorg/jetbrains/exposed/sql/QueryBuilder;Ljava/util/List;)V
}

public abstract class org/jetbrains/exposed/sql/ops/AllAnyFromBaseOp : org/jetbrains/exposed/sql/Op {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,21 @@ inline fun <reified T : Any> anyFrom(array: Array<T>, delegateType: ColumnType?
// emptyArray() without type info generates ARRAY[]
@OptIn(InternalApi::class)
val columnType = delegateType ?: resolveColumnType(T::class, if (array.isEmpty()) TextColumnType() else null)
return AllAnyFromArrayOp(true, array.toList(), columnType)
}

/**
* Returns this list of data wrapped in the `ANY` operator. This function is only supported by PostgreSQL and H2 dialects.
*
* **Note** If [delegateType] is left `null`, the base column type associated with storing elements of type [T] will be
* resolved according to the internal mapping of the element's type in [resolveColumnType].
*
* @throws IllegalStateException If no column type mapping is found and a [delegateType] is not provided.
*/
inline fun <reified T : Any> anyFrom(array: List<T>, delegateType: ColumnType? = null): Op<T> {
// emptyList() without type info generates ARRAY[]
@OptIn(InternalApi::class)
val columnType = delegateType ?: resolveColumnType(T::class, if (array.isEmpty()) TextColumnType() else null)
return AllAnyFromArrayOp(true, array, columnType)
}

Expand All @@ -152,6 +167,21 @@ inline fun <reified T : Any> allFrom(array: Array<T>, delegateType: ColumnType?
// emptyArray() without type info generates ARRAY[]
@OptIn(InternalApi::class)
val columnType = delegateType ?: resolveColumnType(T::class, if (array.isEmpty()) TextColumnType() else null)
return AllAnyFromArrayOp(false, array.toList(), columnType)
}

/**
* Returns this list of data wrapped in the `ALL` operator. This function is only supported by PostgreSQL and H2 dialects.
*
* **Note** If [delegateType] is left `null`, the base column type associated with storing elements of type [T] will be
* resolved according to the internal mapping of the element's type in [resolveColumnType].
*
* @throws IllegalStateException If no column type mapping is found and a [delegateType] is not provided.
*/
inline fun <reified T : Any> allFrom(array: List<T>, delegateType: ColumnType? = null): Op<T> {
// emptyList() without type info generates ARRAY[]
@OptIn(InternalApi::class)
val columnType = delegateType ?: resolveColumnType(T::class, if (array.isEmpty()) TextColumnType() else null)
return AllAnyFromArrayOp(false, array, columnType)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,10 @@ class AllAnyFromSubQueryOp<T>(
*/
class AllAnyFromArrayOp<T : Any>(
isAny: Boolean,
array: Array<T>,
array: List<T>,
private val delegateType: ColumnType
) : AllAnyFromBaseOp<T, Array<T>>(isAny, array) {
override fun QueryBuilder.registerSubSearchArgument(subSearch: Array<T>) {
) : AllAnyFromBaseOp<T, List<T>>(isAny, array) {
override fun QueryBuilder.registerSubSearchArgument(subSearch: List<T>) {
registerArgument(ArrayColumnType(delegateType), subSearch)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,21 @@ class SelectTests : DatabaseTestsBase() {
}
}

@Test
fun testEqAnyFromList() {
withDb(testDBsSupportingAnyAndAllFromArrays) {
withCitiesAndUsers { _, users, _ ->
val r = users.selectAll().where {
users.id eq anyFrom(listOf("andrey", "alex"))
}.orderBy(users.name).toList()

assertEquals(2, r.size)
assertEquals("Alex", r[0][users.name])
assertEquals("Andrey", r[1][users.name])
}
}
}

@Test
fun testNeqAnyFromArray() {
withDb(testDBsSupportingAnyAndAllFromArrays) {
Expand All @@ -336,6 +351,18 @@ class SelectTests : DatabaseTestsBase() {
}
}

@Test
fun testNeqAnyFromList() {
withDb(testDBsSupportingAnyAndAllFromArrays) {
withCitiesAndUsers { _, users, _ ->
val r = users.selectAll().where {
users.id neq anyFrom(listOf("andrey"))
}.orderBy(users.name)
assertEquals(4, r.count())
}
}
}

@Test
fun testNeqAnyFromEmptyArray() {
withDb(testDBsSupportingAnyAndAllFromArrays) {
Expand All @@ -346,6 +373,16 @@ class SelectTests : DatabaseTestsBase() {
}
}

@Test
fun testNeqAnyFromEmptyList() {
withDb(testDBsSupportingAnyAndAllFromArrays) {
withCitiesAndUsers { _, users, _ ->
val r = users.selectAll().where { users.id neq anyFrom(emptyList()) }.orderBy(users.name)
assert(r.empty())
}
}
}

@Test
fun testGreaterEqAnyFromArray() {
withDb(testDBsSupportingAnyAndAllFromArrays) {
Expand All @@ -361,6 +398,21 @@ class SelectTests : DatabaseTestsBase() {
}
}

@Test
fun testGreaterEqAnyFromList() {
withDb(testDBsSupportingAnyAndAllFromArrays) {
withSales { _, sales ->
val amounts = listOf(100, 1000).map { it.toBigDecimal() }
val r = sales.selectAll().where { sales.amount greaterEq anyFrom(amounts) }
.orderBy(sales.amount)
.map { it[sales.product] }
assertEquals(6, r.size)
r.subList(0, 3).forEach { assertEquals("tea", it) }
r.subList(3, 6).forEach { assertEquals("coffee", it) }
}
}
}

@Test
fun testEqAnyFromTable() {
withDb(testDBsSupportingInAnyAllFromTables) {
Expand Down Expand Up @@ -408,6 +460,18 @@ class SelectTests : DatabaseTestsBase() {
}
}

@Test
fun testGreaterEqAllFromList() {
withDb(testDBsSupportingAnyAndAllFromArrays) {
withSales { _, sales ->
val amounts = listOf(100, 1000).map { it.toBigDecimal() }
val r = sales.selectAll().where { sales.amount greaterEq allFrom(amounts) }.toList()
assertEquals(3, r.size)
r.forEach { assertEquals("coffee", it[sales.product]) }
}
}
}

@Test
fun testGreaterEqAllFromTable() {
withDb(testDBsSupportingInAnyAllFromTables) {
Expand Down

0 comments on commit c3620bd

Please sign in to comment.