From f29883caca28719f6bf6b682d57cb80355772059 Mon Sep 17 00:00:00 2001 From: Chantal Loncle <82039410+bog-walk@users.noreply.github.com> Date: Tue, 16 Jul 2024 15:55:22 -0400 Subject: [PATCH] feat: EXPOSED-446 Support N-column inList equality comparisons IN operator is currently only accessible with up to 3 left-hand side expressions. This allows a variable n-columns to be used for equality comparison, as supported by the databases. SQL Server requires special use of the EXISTS keyword to get the same result. Additional tasks: - Rename existing inList tests - Remove SQLite exclusion from existing tests (IN keyword supported since 2018) - Update KDocs with samples --- exposed-core/api/exposed-core.api | 15 ++++ .../exposed/sql/SQLExpressionBuilder.kt | 64 +++++++++++--- .../jetbrains/exposed/sql/ops/InListOps.kt | 70 +++++++++++++++ .../sql/tests/shared/dml/SelectTests.kt | 87 ++++++++++++++----- 4 files changed, 204 insertions(+), 32 deletions(-) diff --git a/exposed-core/api/exposed-core.api b/exposed-core/api/exposed-core.api index 144a906ea8..dbeece08f4 100644 --- a/exposed-core/api/exposed-core.api +++ b/exposed-core/api/exposed-core.api @@ -1055,6 +1055,7 @@ public abstract interface class org/jetbrains/exposed/sql/ISqlExpressionBuilder public abstract fun greaterEqEntityID (Lorg/jetbrains/exposed/sql/Column;Ljava/lang/Comparable;)Lorg/jetbrains/exposed/sql/GreaterEqOp; public abstract fun hasFlag (Lorg/jetbrains/exposed/sql/ExpressionWithColumnType;Ljava/lang/Object;)Lorg/jetbrains/exposed/sql/EqOp; public abstract fun hasFlag (Lorg/jetbrains/exposed/sql/ExpressionWithColumnType;Lorg/jetbrains/exposed/sql/Expression;)Lorg/jetbrains/exposed/sql/EqOp; + public abstract fun inList (Ljava/util/List;Ljava/lang/Iterable;)Lorg/jetbrains/exposed/sql/ops/InListOrNotInListBaseOp; public abstract fun inList (Lkotlin/Pair;Ljava/lang/Iterable;)Lorg/jetbrains/exposed/sql/ops/InListOrNotInListBaseOp; public abstract fun inList (Lkotlin/Triple;Ljava/lang/Iterable;)Lorg/jetbrains/exposed/sql/ops/InListOrNotInListBaseOp; public abstract fun inList (Lorg/jetbrains/exposed/sql/ExpressionWithColumnType;Ljava/lang/Iterable;)Lorg/jetbrains/exposed/sql/ops/InListOrNotInListBaseOp; @@ -1108,6 +1109,7 @@ public abstract interface class org/jetbrains/exposed/sql/ISqlExpressionBuilder public abstract fun neq (Lorg/jetbrains/exposed/sql/ExpressionWithColumnType;Ljava/lang/Object;)Lorg/jetbrains/exposed/sql/Op; public abstract fun neq (Lorg/jetbrains/exposed/sql/ExpressionWithColumnType;Lorg/jetbrains/exposed/sql/Expression;)Lorg/jetbrains/exposed/sql/Op; public abstract fun notEqSubQuery (Lorg/jetbrains/exposed/sql/Expression;Lorg/jetbrains/exposed/sql/AbstractQuery;)Lorg/jetbrains/exposed/sql/NotEqSubQueryOp; + public abstract fun notInList (Ljava/util/List;Ljava/lang/Iterable;)Lorg/jetbrains/exposed/sql/ops/InListOrNotInListBaseOp; public abstract fun notInList (Lkotlin/Pair;Ljava/lang/Iterable;)Lorg/jetbrains/exposed/sql/ops/InListOrNotInListBaseOp; public abstract fun notInList (Lkotlin/Triple;Ljava/lang/Iterable;)Lorg/jetbrains/exposed/sql/ops/InListOrNotInListBaseOp; public abstract fun notInList (Lorg/jetbrains/exposed/sql/ExpressionWithColumnType;Ljava/lang/Iterable;)Lorg/jetbrains/exposed/sql/ops/InListOrNotInListBaseOp; @@ -1182,6 +1184,7 @@ public final class org/jetbrains/exposed/sql/ISqlExpressionBuilder$DefaultImpls public static fun greaterEqEntityID (Lorg/jetbrains/exposed/sql/ISqlExpressionBuilder;Lorg/jetbrains/exposed/sql/Column;Ljava/lang/Comparable;)Lorg/jetbrains/exposed/sql/GreaterEqOp; public static fun hasFlag (Lorg/jetbrains/exposed/sql/ISqlExpressionBuilder;Lorg/jetbrains/exposed/sql/ExpressionWithColumnType;Ljava/lang/Object;)Lorg/jetbrains/exposed/sql/EqOp; public static fun hasFlag (Lorg/jetbrains/exposed/sql/ISqlExpressionBuilder;Lorg/jetbrains/exposed/sql/ExpressionWithColumnType;Lorg/jetbrains/exposed/sql/Expression;)Lorg/jetbrains/exposed/sql/EqOp; + public static fun inList (Lorg/jetbrains/exposed/sql/ISqlExpressionBuilder;Ljava/util/List;Ljava/lang/Iterable;)Lorg/jetbrains/exposed/sql/ops/InListOrNotInListBaseOp; public static fun inList (Lorg/jetbrains/exposed/sql/ISqlExpressionBuilder;Lkotlin/Pair;Ljava/lang/Iterable;)Lorg/jetbrains/exposed/sql/ops/InListOrNotInListBaseOp; public static fun inList (Lorg/jetbrains/exposed/sql/ISqlExpressionBuilder;Lkotlin/Triple;Ljava/lang/Iterable;)Lorg/jetbrains/exposed/sql/ops/InListOrNotInListBaseOp; public static fun inList (Lorg/jetbrains/exposed/sql/ISqlExpressionBuilder;Lorg/jetbrains/exposed/sql/ExpressionWithColumnType;Ljava/lang/Iterable;)Lorg/jetbrains/exposed/sql/ops/InListOrNotInListBaseOp; @@ -1237,6 +1240,7 @@ public final class org/jetbrains/exposed/sql/ISqlExpressionBuilder$DefaultImpls public static fun neq (Lorg/jetbrains/exposed/sql/ISqlExpressionBuilder;Lorg/jetbrains/exposed/sql/ExpressionWithColumnType;Ljava/lang/Object;)Lorg/jetbrains/exposed/sql/Op; public static fun neq (Lorg/jetbrains/exposed/sql/ISqlExpressionBuilder;Lorg/jetbrains/exposed/sql/ExpressionWithColumnType;Lorg/jetbrains/exposed/sql/Expression;)Lorg/jetbrains/exposed/sql/Op; public static fun notEqSubQuery (Lorg/jetbrains/exposed/sql/ISqlExpressionBuilder;Lorg/jetbrains/exposed/sql/Expression;Lorg/jetbrains/exposed/sql/AbstractQuery;)Lorg/jetbrains/exposed/sql/NotEqSubQueryOp; + public static fun notInList (Lorg/jetbrains/exposed/sql/ISqlExpressionBuilder;Ljava/util/List;Ljava/lang/Iterable;)Lorg/jetbrains/exposed/sql/ops/InListOrNotInListBaseOp; public static fun notInList (Lorg/jetbrains/exposed/sql/ISqlExpressionBuilder;Lkotlin/Pair;Ljava/lang/Iterable;)Lorg/jetbrains/exposed/sql/ops/InListOrNotInListBaseOp; public static fun notInList (Lorg/jetbrains/exposed/sql/ISqlExpressionBuilder;Lkotlin/Triple;Ljava/lang/Iterable;)Lorg/jetbrains/exposed/sql/ops/InListOrNotInListBaseOp; public static fun notInList (Lorg/jetbrains/exposed/sql/ISqlExpressionBuilder;Lorg/jetbrains/exposed/sql/ExpressionWithColumnType;Ljava/lang/Iterable;)Lorg/jetbrains/exposed/sql/ops/InListOrNotInListBaseOp; @@ -2179,6 +2183,7 @@ public final class org/jetbrains/exposed/sql/SqlExpressionBuilder : org/jetbrain public fun greaterEqEntityID (Lorg/jetbrains/exposed/sql/Column;Ljava/lang/Comparable;)Lorg/jetbrains/exposed/sql/GreaterEqOp; public fun hasFlag (Lorg/jetbrains/exposed/sql/ExpressionWithColumnType;Ljava/lang/Object;)Lorg/jetbrains/exposed/sql/EqOp; public fun hasFlag (Lorg/jetbrains/exposed/sql/ExpressionWithColumnType;Lorg/jetbrains/exposed/sql/Expression;)Lorg/jetbrains/exposed/sql/EqOp; + public fun inList (Ljava/util/List;Ljava/lang/Iterable;)Lorg/jetbrains/exposed/sql/ops/InListOrNotInListBaseOp; public fun inList (Lkotlin/Pair;Ljava/lang/Iterable;)Lorg/jetbrains/exposed/sql/ops/InListOrNotInListBaseOp; public fun inList (Lkotlin/Triple;Ljava/lang/Iterable;)Lorg/jetbrains/exposed/sql/ops/InListOrNotInListBaseOp; public fun inList (Lorg/jetbrains/exposed/sql/ExpressionWithColumnType;Ljava/lang/Iterable;)Lorg/jetbrains/exposed/sql/ops/InListOrNotInListBaseOp; @@ -2232,6 +2237,7 @@ public final class org/jetbrains/exposed/sql/SqlExpressionBuilder : org/jetbrain public fun neq (Lorg/jetbrains/exposed/sql/ExpressionWithColumnType;Ljava/lang/Object;)Lorg/jetbrains/exposed/sql/Op; public fun neq (Lorg/jetbrains/exposed/sql/ExpressionWithColumnType;Lorg/jetbrains/exposed/sql/Expression;)Lorg/jetbrains/exposed/sql/Op; public fun notEqSubQuery (Lorg/jetbrains/exposed/sql/Expression;Lorg/jetbrains/exposed/sql/AbstractQuery;)Lorg/jetbrains/exposed/sql/NotEqSubQueryOp; + public fun notInList (Ljava/util/List;Ljava/lang/Iterable;)Lorg/jetbrains/exposed/sql/ops/InListOrNotInListBaseOp; public fun notInList (Lkotlin/Pair;Ljava/lang/Iterable;)Lorg/jetbrains/exposed/sql/ops/InListOrNotInListBaseOp; public fun notInList (Lkotlin/Triple;Ljava/lang/Iterable;)Lorg/jetbrains/exposed/sql/ops/InListOrNotInListBaseOp; public fun notInList (Lorg/jetbrains/exposed/sql/ExpressionWithColumnType;Ljava/lang/Iterable;)Lorg/jetbrains/exposed/sql/ops/InListOrNotInListBaseOp; @@ -2883,6 +2889,15 @@ public final class org/jetbrains/exposed/sql/ops/InTableOp : org/jetbrains/expos public fun toQueryBuilder (Lorg/jetbrains/exposed/sql/QueryBuilder;)V } +public final class org/jetbrains/exposed/sql/ops/MultipleInListOp : org/jetbrains/exposed/sql/ops/InListOrNotInListBaseOp { + public fun (Ljava/util/List;Ljava/lang/Iterable;Z)V + public synthetic fun (Ljava/util/List;Ljava/lang/Iterable;ZILkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun getExpr ()Ljava/lang/Object; + public fun getExpr ()Ljava/util/List; + public synthetic fun registerValues (Lorg/jetbrains/exposed/sql/QueryBuilder;Ljava/lang/Object;)V + public fun toQueryBuilder (Lorg/jetbrains/exposed/sql/QueryBuilder;)V +} + public final class org/jetbrains/exposed/sql/ops/PairInListOp : org/jetbrains/exposed/sql/ops/InListOrNotInListBaseOp { public fun (Lkotlin/Pair;Ljava/lang/Iterable;Z)V public synthetic fun (Lkotlin/Pair;Ljava/lang/Iterable;ZILkotlin/jvm/internal/DefaultConstructorMarker;)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 a231eff4d1..08febd863d 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 @@ -840,26 +840,46 @@ interface ISqlExpressionBuilder { // Array Comparisons - /** Checks if this expression is equal to any element from [list]. */ + /** + * Checks if this expression is equal to any element from [list]. + * + * @sample org.jetbrains.exposed.sql.tests.shared.dml.SelectTests.testInListWithSingleExpression01 + */ infix fun ExpressionWithColumnType.inList(list: Iterable): InListOrNotInListBaseOp = SingleValueInListOp(this, list, isInList = true) /** - * Checks if both expressions are equal to elements from [list]. - * This syntax is unsupported by SQLite and SQL Server + * Checks if expressions from this `Pair` are equal to elements from [list]. + * This syntax is unsupported by SQL Server. + * + * @sample org.jetbrains.exposed.sql.tests.shared.dml.SelectTests.testInListWithPairExpressions01 **/ infix fun Pair, ExpressionWithColumnType>.inList(list: Iterable>): InListOrNotInListBaseOp> = PairInListOp(this, list, isInList = true) /** - * Checks if expressions from triple are equal to elements from [list]. - * This syntax is unsupported by SQLite and SQL Server + * Checks if expressions from this `Triple` are equal to elements from [list]. + * This syntax is unsupported by SQL Server. + * + * @sample org.jetbrains.exposed.sql.tests.shared.dml.SelectTests.testInListWithTripleExpressions **/ infix fun Triple, ExpressionWithColumnType, ExpressionWithColumnType>.inList( list: Iterable> ): InListOrNotInListBaseOp> = TripleInListOp(this, list, isInList = true) - /** Checks if this expression is equals to any element from [list]. */ + /** + * Checks if all columns in this `List` are equal to any of the lists of values from [list]. + * + * @sample org.jetbrains.exposed.sql.tests.shared.dml.SelectTests.testInListWithMultipleColumns + **/ + infix fun List>.inList(list: Iterable>): InListOrNotInListBaseOp> = + MultipleInListOp(this, list, isInList = true) + + /** + * Checks if this [EntityID] column is equal to any element from [list]. + * + * @sample org.jetbrains.exposed.sql.tests.shared.dml.SelectTests.testInListWithEntityIDColumns + */ @Suppress("UNCHECKED_CAST") @JvmName("inListIds") infix fun , ID : EntityID?> Column.inList(list: Iterable): InListOrNotInListBaseOp?> { @@ -867,13 +887,19 @@ interface ISqlExpressionBuilder { return SingleValueInListOp(this, list.map { EntityIDFunctionProvider.createEntityID(it, idTable) }, isInList = true) } - /** Checks if this expression is not equals to any element from [list]. */ + /** + * Checks if this expression is not equal to any element from [list]. + * + * @sample org.jetbrains.exposed.sql.tests.shared.dml.SelectTests.testInListWithSingleExpression01 + */ infix fun ExpressionWithColumnType.notInList(list: Iterable): InListOrNotInListBaseOp = SingleValueInListOp(this, list, isInList = false) /** - * Checks if both expressions are not equal to elements from [list]. - * This syntax is unsupported by SQLite and SQL Server + * Checks if expressions from this `Pair` are not equal to elements from [list]. + * This syntax is unsupported by SQL Server. + * + * @sample org.jetbrains.exposed.sql.tests.shared.dml.SelectTests.testNotInListWithPairExpressionsAndEmptyList **/ infix fun Pair, ExpressionWithColumnType>.notInList( list: Iterable> @@ -881,15 +907,29 @@ interface ISqlExpressionBuilder { PairInListOp(this, list, isInList = false) /** - * Checks if expressions from triple are not equal to elements from [list]. - * This syntax is unsupported by SQLite and SQL Server + * Checks if expressions from this `Triple` are not equal to elements from [list]. + * This syntax is unsupported by SQL Server. + * + * @sample org.jetbrains.exposed.sql.tests.shared.dml.SelectTests.testInListWithTripleExpressions **/ infix fun Triple, ExpressionWithColumnType, ExpressionWithColumnType>.notInList( list: Iterable> ): InListOrNotInListBaseOp> = TripleInListOp(this, list, isInList = false) - /** Checks if this expression is not equals to any element from [list]. */ + /** + * Checks if all columns in this `List` are not equal to any of the lists of values from [list]. + * + * @sample org.jetbrains.exposed.sql.tests.shared.dml.SelectTests.testInListWithMultipleColumns + **/ + infix fun List>.notInList(list: Iterable>): InListOrNotInListBaseOp> = + MultipleInListOp(this, list, isInList = false) + + /** + * Checks if this [EntityID] column is not equal to any element from [list]. + * + * @sample org.jetbrains.exposed.sql.tests.shared.dml.SelectTests.testInListWithEntityIDColumns + */ @Suppress("UNCHECKED_CAST") @JvmName("notInListIds") infix fun , ID : EntityID?> Column.notInList(list: Iterable): InListOrNotInListBaseOp?> { diff --git a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/ops/InListOps.kt b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/ops/InListOps.kt index e0730c12e8..66d74f7460 100644 --- a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/ops/InListOps.kt +++ b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/ops/InListOps.kt @@ -1,10 +1,13 @@ package org.jetbrains.exposed.sql.ops +import org.jetbrains.exposed.sql.Column import org.jetbrains.exposed.sql.ComplexExpression import org.jetbrains.exposed.sql.ExpressionWithColumnType import org.jetbrains.exposed.sql.Op import org.jetbrains.exposed.sql.QueryBuilder +import org.jetbrains.exposed.sql.transactions.TransactionManager import org.jetbrains.exposed.sql.vendors.OracleDialect +import org.jetbrains.exposed.sql.vendors.SQLServerDialect import org.jetbrains.exposed.sql.vendors.currentDialectIfAvailable /** @@ -123,3 +126,70 @@ class TripleInListOp( append(")") } } + +/** + * Represents an SQL operator that checks if all columns of a `List` [expr] match any of the lists of + * values from [list]. + * + * To inverse the operator and check if the `List` of columns is **not** in [list], set [isInList] to `false`. + */ +class MultipleInListOp( + override val expr: List>, + list: Iterable>, + isInList: Boolean = true +) : InListOrNotInListBaseOp>(expr, list, isInList) { + override val columnTypes: List> = expr + + override fun QueryBuilder.registerValues(values: List<*>) { + append("(") + expr.forEachIndexed { i, expression -> + registerArgument(expression.columnType, values[i]) + if (i != values.lastIndex) append(", ") + } + append(")") + } + + override fun toQueryBuilder(queryBuilder: QueryBuilder) { + // SQL Server does not support IN operator with tuples (or any more than 1 expression on the left-hand side) + if (currentDialectIfAvailable !is SQLServerDialect) { + super.toQueryBuilder(queryBuilder) + } else { + queryBuilder { + val iterator = list.iterator() + if (!iterator.hasNext()) { + if (isInList) { + +FALSE + } else { + +TRUE + } + } else { + // Built-in exists(AbstractQuery) cannot be used because row value constructors are not supported + // Alternative: Compound AND + OR operators for each value list -> + // (col_1 = ? AND col_2 = ? AND col_3 = ?) OR (col_1 = ? AND col_2 = ? AND col_3 = ?) OR ... (...) + val transaction = TransactionManager.current() + val columnNames = columnTypes.map { transaction.identity(it) } + val firstValue = iterator.next() + + when { + isInList -> append("EXISTS (") + else -> append("NOT EXISTS (") + } + append("SELECT * FROM (VALUES ") + registerValues(firstValue) + iterator.forEach { value -> + append(", ") + registerValues(value) + } + append(") v") + columnNames.appendTo(prefix = "(", postfix = ")") { +it } + append(" WHERE ") + columnNames.withIndex().appendTo(separator = " AND ") { (i, column) -> + +"v.$column=" + +columnTypes[i] + } + append(")") + } + } + } + } +} diff --git a/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/dml/SelectTests.kt b/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/dml/SelectTests.kt index 90cf2f93a9..3c69c0a7e7 100644 --- a/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/dml/SelectTests.kt +++ b/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/dml/SelectTests.kt @@ -131,20 +131,24 @@ class SelectTests : DatabaseTestsBase() { } @Test - fun testInList01() { + fun testInListWithSingleExpression01() { withCitiesAndUsers { _, users, _ -> - val r = users.selectAll().where { + val r1 = users.selectAll().where { users.id inList listOf("andrey", "alex") }.orderBy(users.name).toList() - assertEquals(2, r.size) - assertEquals("Alex", r[0][users.name]) - assertEquals("Andrey", r[1][users.name]) + assertEquals(2, r1.size) + assertEquals("Alex", r1[0][users.name]) + assertEquals("Andrey", r1[1][users.name]) + + val r2 = users.selectAll().where { users.id notInList listOf("ABC", "DEF") }.toList() + + assertEquals(users.selectAll().count().toInt(), r2.size) } } @Test - fun testInList02() { + fun testInListWithSingleExpression02() { withCitiesAndUsers { cities, _, _ -> val cityIds = cities.selectAll().map { it[cities.id] }.take(2) val r = cities.selectAll().where { cities.id inList cityIds } @@ -154,8 +158,8 @@ class SelectTests : DatabaseTestsBase() { } @Test - fun testInList03() { - withCitiesAndUsers(listOf(TestDB.SQLITE, TestDB.SQLSERVER)) { _, users, _ -> + fun testInListWithPairExpressions01() { + withCitiesAndUsers(exclude = listOf(TestDB.SQLSERVER)) { _, users, _ -> val r = users.selectAll().where { users.id to users.name inList listOf("andrey" to "Andrey", "alex" to "Alex") }.orderBy(users.name).toList() @@ -167,8 +171,8 @@ class SelectTests : DatabaseTestsBase() { } @Test - fun testInList04() { - withCitiesAndUsers(listOf(TestDB.SQLITE, TestDB.SQLSERVER)) { _, users, _ -> + fun testInListWithPairExpressions02() { + withCitiesAndUsers(exclude = listOf(TestDB.SQLSERVER)) { _, users, _ -> val r = users.selectAll().where { users.id to users.name inList listOf("andrey" to "Andrey") }.toList() assertEquals(1, r.size) @@ -177,8 +181,8 @@ class SelectTests : DatabaseTestsBase() { } @Test - fun testInList05() { - withCitiesAndUsers(listOf(TestDB.SQLITE, TestDB.SQLSERVER)) { _, users, _ -> + fun testInListWithPairExpressionsAndEmptyList() { + withCitiesAndUsers(exclude = listOf(TestDB.SQLSERVER)) { _, users, _ -> val r = users.selectAll().where { users.id to users.name inList emptyList() }.toList() assertEquals(0, r.size) @@ -186,8 +190,8 @@ class SelectTests : DatabaseTestsBase() { } @Test - fun testInList06() { - withCitiesAndUsers(listOf(TestDB.SQLITE, TestDB.SQLSERVER)) { _, users, _ -> + fun testNotInListWithPairExpressionsAndEmptyList() { + withCitiesAndUsers(exclude = listOf(TestDB.SQLSERVER)) { _, users, _ -> val r = users.selectAll().where { users.id to users.name notInList emptyList() }.toList() assertEquals(users.selectAll().count().toInt(), r.size) @@ -195,18 +199,61 @@ class SelectTests : DatabaseTestsBase() { } @Test - fun testInList07() { - withCitiesAndUsers(listOf(TestDB.SQLITE, TestDB.SQLSERVER)) { _, users, _ -> - val r = users.selectAll().where { - Triple(users.id, users.name, users.cityId) notInList listOf(Triple("alex", "Alex", null)) + fun testInListWithTripleExpressions() { + withCitiesAndUsers(exclude = listOf(TestDB.SQLSERVER)) { _, users, _ -> + val userExpressions = Triple(users.id, users.name, users.cityId) + val r1 = users.selectAll().where { + userExpressions notInList listOf(Triple("alex", "Alex", null)) }.toList() - assertEquals(users.selectAll().count().toInt() - 1, r.size) + assertEquals(users.selectAll().count().toInt() - 1, r1.size) + + val r2 = users.selectAll().where { + userExpressions inList listOf(Triple("andrey", "Andrey", 1)) + }.toList() + + assertEquals(1, r2.size) + } + } + + @Test + fun testInListWithMultipleColumns() { + val tester = object : Table("tester") { + val num1 = integer("num_1") + val num2 = integer("num_2") + val num3 = integer("num_3") + val num4 = integer("num_4") + } + + withTables(tester) { + repeat(3) { n -> + tester.insert { + it[num1] = n + it[num2] = n + it[num3] = n + it[num4] = n + } + } + val expected = tester.selectAll().count().toInt() + + val allSameNumbers = List(3) { n -> List(4) { n } } + val result1 = tester.selectAll().where { tester.columns inList allSameNumbers }.toList() + assertEquals(expected, result1.size) + + val result2 = tester.selectAll().where { tester.columns inList listOf(allSameNumbers.first()) }.toList() + assertEquals(1, result2.size) + + val allDifferentNumbers = List(3) { n -> List(4) { n + it } } + val result3 = tester.selectAll().where { tester.columns notInList allDifferentNumbers }.toList() + assertEquals(expected, result3.size) + + val result4 = tester.selectAll().where { tester.columns notInList emptyList() }.toList() + assertEquals(expected, result4.size) } } @Test - fun testInList08() { + fun testInListWithEntityIDColumns() { withTables(EntityTests.Posts, EntityTests.Boards, EntityTests.Categories) { val board1 = EntityTests.Board.new { this.name = "Board1"