From 6ccb9967ed6888687b7b2fa4d301e69ae1bb3509 Mon Sep 17 00:00:00 2001 From: bog-walk <82039410+bog-walk@users.noreply.github.com> Date: Tue, 13 Feb 2024 07:50:40 -0500 Subject: [PATCH] feat: EXPOSED-290 Support ANY and ALL operators using array column expressions (#1988) Support providing an array column or expression as an argument for existing anyFrom() and allFrom() functions. --- exposed-core/api/exposed-core.api | 8 ++++++ .../exposed/sql/SQLExpressionBuilder.kt | 6 +++++ .../jetbrains/exposed/sql/ops/AllAnyOps.kt | 16 ++++++++++++ .../shared/types/ArrayColumnTypeTests.kt | 26 +++++++++++++++++++ 4 files changed, 56 insertions(+) diff --git a/exposed-core/api/exposed-core.api b/exposed-core/api/exposed-core.api index 54d3326c89..cf37cb4e82 100644 --- a/exposed-core/api/exposed-core.api +++ b/exposed-core/api/exposed-core.api @@ -1790,9 +1790,11 @@ public final class org/jetbrains/exposed/sql/SQLExpressionBuilderKt { public static final fun CustomLongFunction (Ljava/lang/String;[Lorg/jetbrains/exposed/sql/Expression;)Lorg/jetbrains/exposed/sql/CustomFunction; public static final fun CustomStringFunction (Ljava/lang/String;[Lorg/jetbrains/exposed/sql/Expression;)Lorg/jetbrains/exposed/sql/CustomFunction; public static final fun allFrom (Lorg/jetbrains/exposed/sql/AbstractQuery;)Lorg/jetbrains/exposed/sql/Op; + public static final fun allFrom (Lorg/jetbrains/exposed/sql/Expression;)Lorg/jetbrains/exposed/sql/Op; public static final fun allFrom (Lorg/jetbrains/exposed/sql/Table;)Lorg/jetbrains/exposed/sql/Op; public static final fun allFrom ([Ljava/lang/Object;)Lorg/jetbrains/exposed/sql/Op; public static final fun anyFrom (Lorg/jetbrains/exposed/sql/AbstractQuery;)Lorg/jetbrains/exposed/sql/Op; + public static final fun anyFrom (Lorg/jetbrains/exposed/sql/Expression;)Lorg/jetbrains/exposed/sql/Op; public static final fun anyFrom (Lorg/jetbrains/exposed/sql/Table;)Lorg/jetbrains/exposed/sql/Op; public static final fun anyFrom ([Ljava/lang/Object;)Lorg/jetbrains/exposed/sql/Op; public static final fun avg (Lorg/jetbrains/exposed/sql/ExpressionWithColumnType;I)Lorg/jetbrains/exposed/sql/Avg; @@ -2691,6 +2693,12 @@ public abstract class org/jetbrains/exposed/sql/ops/AllAnyFromBaseOp : org/jetbr public fun toQueryBuilder (Lorg/jetbrains/exposed/sql/QueryBuilder;)V } +public final class org/jetbrains/exposed/sql/ops/AllAnyFromExpressionOp : org/jetbrains/exposed/sql/ops/AllAnyFromBaseOp { + public fun (ZLorg/jetbrains/exposed/sql/Expression;)V + public synthetic fun registerSubSearchArgument (Lorg/jetbrains/exposed/sql/QueryBuilder;Ljava/lang/Object;)V + public fun registerSubSearchArgument (Lorg/jetbrains/exposed/sql/QueryBuilder;Lorg/jetbrains/exposed/sql/Expression;)V +} + public final class org/jetbrains/exposed/sql/ops/AllAnyFromSubQueryOp : org/jetbrains/exposed/sql/ops/AllAnyFromBaseOp { public fun (ZLorg/jetbrains/exposed/sql/AbstractQuery;)V public synthetic fun registerSubSearchArgument (Lorg/jetbrains/exposed/sql/QueryBuilder;Ljava/lang/Object;)V diff --git a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/SQLExpressionBuilder.kt b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/SQLExpressionBuilder.kt index a8045168bf..79d41cd982 100644 --- a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/SQLExpressionBuilder.kt +++ b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/SQLExpressionBuilder.kt @@ -122,6 +122,9 @@ fun anyFrom(array: Array): Op = AllAnyFromArrayOp(true, array) /** Returns this table wrapped in the `ANY` operator. This function is only supported by MySQL, PostgreSQL, and H2 dialects. */ fun anyFrom(table: Table): Op = AllAnyFromTableOp(true, table) +/** Returns this expression wrapped in the `ANY` operator. This function is only supported by PostgreSQL and H2 dialects. */ +fun ?> anyFrom(expression: Expression): Op = AllAnyFromExpressionOp(true, expression) + /** Returns this subquery wrapped in the `ALL` operator. This function is not supported by the SQLite dialect. */ fun allFrom(subQuery: AbstractQuery<*>): Op = AllAnyFromSubQueryOp(false, subQuery) @@ -131,6 +134,9 @@ fun allFrom(array: Array): Op = AllAnyFromArrayOp(false, array) /** Returns this table wrapped in the `ALL` operator. This function is only supported by MySQL, PostgreSQL, and H2 dialects. */ fun allFrom(table: Table): Op = AllAnyFromTableOp(false, table) +/** Returns this expression wrapped in the `ALL` operator. This function is only supported by PostgreSQL and H2 dialects. */ +fun ?> allFrom(expression: Expression): Op = AllAnyFromExpressionOp(false, expression) + /** * Returns the array element stored at the one-based [index] position, or `null` if the stored array itself is null. * diff --git a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/ops/AllAnyOps.kt b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/ops/AllAnyOps.kt index e011e17b7d..deef4d2592 100644 --- a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/ops/AllAnyOps.kt +++ b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/ops/AllAnyOps.kt @@ -1,6 +1,7 @@ package org.jetbrains.exposed.sql.ops import org.jetbrains.exposed.sql.AbstractQuery +import org.jetbrains.exposed.sql.Expression import org.jetbrains.exposed.sql.Op import org.jetbrains.exposed.sql.QueryBuilder import org.jetbrains.exposed.sql.Table @@ -63,3 +64,18 @@ class AllAnyFromTableOp(isAny: Boolean, table: Table) : AllAnyFromBaseOp?>( + isAny: Boolean, + expression: Expression +) : AllAnyFromBaseOp>(isAny, expression) { + override fun QueryBuilder.registerSubSearchArgument(subSearch: Expression) { + append(subSearch) + } +} diff --git a/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/types/ArrayColumnTypeTests.kt b/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/types/ArrayColumnTypeTests.kt index 39a1573fa8..04420cb687 100644 --- a/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/types/ArrayColumnTypeTests.kt +++ b/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/types/ArrayColumnTypeTests.kt @@ -258,4 +258,30 @@ class ArrayColumnTypeTests : DatabaseTestsBase() { assertContentEquals(doublesInput, ArrayTestDao.all().single().doubles) } } + + @Test + fun testArrayColumnWithAllAnyOps() { + withTables(excludeSettings = arrayTypeUnsupportedDb, ArrayTestTable) { + val numInput = listOf(1, 2, 3) + val id1 = ArrayTestTable.insertAndGetId { + it[numbers] = numInput + it[doubles] = null + } + + val result1 = ArrayTestTable.select(ArrayTestTable.id).where { + ArrayTestTable.id eq anyFrom(ArrayTestTable.numbers) + } + assertEquals(id1, result1.single()[ArrayTestTable.id]) + + val result2 = ArrayTestTable.select(ArrayTestTable.id).where { + ArrayTestTable.id eq anyFrom(ArrayTestTable.numbers.slice(2, 3)) + } + assertTrue(result2.toList().isEmpty()) + + val result3 = ArrayTestTable.select(ArrayTestTable.id).where { + ArrayTestTable.id lessEq allFrom(ArrayTestTable.numbers) + } + assertEquals(id1, result3.single()[ArrayTestTable.id]) + } + } }