From efc4727879a206828f51b302404bbf67d85ecc2d Mon Sep 17 00:00:00 2001 From: Jeff Butler Date: Mon, 22 Apr 2024 18:24:27 -0400 Subject: [PATCH 1/3] Add Array based "in" function in Kotlin These functions allow a more nature use of an Array as an input for an "in" condition. They also allow easy reuse of a vararg argument in a function. --- .../util/kotlin/GroupingCriteriaCollector.kt | 32 +++ .../sql/util/kotlin/elements/SqlElements.kt | 27 ++ .../spring/canonical/InfixElementsTest.kt | 262 +++++++++++++++++- .../nullability/test/InWhenPresentTest.kt | 2 +- .../nullability/test/NotInWhenPresentTest.kt | 2 +- 5 files changed, 316 insertions(+), 9 deletions(-) diff --git a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/GroupingCriteriaCollector.kt b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/GroupingCriteriaCollector.kt index 731848fb6..4fe8d3091 100644 --- a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/GroupingCriteriaCollector.kt +++ b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/GroupingCriteriaCollector.kt @@ -317,6 +317,10 @@ open class GroupingCriteriaCollector : SubCriteriaCollector() { fun BindableColumn.isIn(vararg values: T & Any) = isIn(values.asList()) + @JvmName("isInArray") + infix fun BindableColumn.isIn(values: Array) = + invoke(org.mybatis.dynamic.sql.util.kotlin.elements.isIn(values)) + infix fun BindableColumn.isIn(values: Collection) = invoke(org.mybatis.dynamic.sql.util.kotlin.elements.isIn(values)) @@ -325,11 +329,19 @@ open class GroupingCriteriaCollector : SubCriteriaCollector() { fun BindableColumn.isInWhenPresent(vararg values: T?) = isInWhenPresent(values.asList()) + @JvmName("isInArrayWhenPresent") + infix fun BindableColumn.isInWhenPresent(values: Array?) = + invoke(org.mybatis.dynamic.sql.util.kotlin.elements.isInWhenPresent(values)) + infix fun BindableColumn.isInWhenPresent(values: Collection?) = invoke(org.mybatis.dynamic.sql.util.kotlin.elements.isInWhenPresent(values)) fun BindableColumn.isNotIn(vararg values: T & Any) = isNotIn(values.asList()) + @JvmName("isNotInArray") + infix fun BindableColumn.isNotIn(values: Array) = + invoke(org.mybatis.dynamic.sql.util.kotlin.elements.isNotIn(values)) + infix fun BindableColumn.isNotIn(values: Collection) = invoke(org.mybatis.dynamic.sql.util.kotlin.elements.isNotIn(values)) @@ -338,6 +350,10 @@ open class GroupingCriteriaCollector : SubCriteriaCollector() { fun BindableColumn.isNotInWhenPresent(vararg values: T?) = isNotInWhenPresent(values.asList()) + @JvmName("isNotInArrayWhenPresent") + infix fun BindableColumn.isNotInWhenPresent(values: Array?) = + invoke(org.mybatis.dynamic.sql.util.kotlin.elements.isNotInWhenPresent(values)) + infix fun BindableColumn.isNotInWhenPresent(values: Collection?) = invoke(org.mybatis.dynamic.sql.util.kotlin.elements.isNotInWhenPresent(values)) @@ -394,24 +410,40 @@ open class GroupingCriteriaCollector : SubCriteriaCollector() { fun BindableColumn.isInCaseInsensitive(vararg values: String) = isInCaseInsensitive(values.asList()) + @JvmName("isInArrayCaseInsensitive") + infix fun BindableColumn.isInCaseInsensitive(values: Array) = + invoke(org.mybatis.dynamic.sql.util.kotlin.elements.isInCaseInsensitive(values)) + infix fun BindableColumn.isInCaseInsensitive(values: Collection) = invoke(org.mybatis.dynamic.sql.util.kotlin.elements.isInCaseInsensitive(values)) fun BindableColumn.isInCaseInsensitiveWhenPresent(vararg values: String?) = isInCaseInsensitiveWhenPresent(values.asList()) + @JvmName("isInArrayCaseInsensitiveWhenPresent") + infix fun BindableColumn.isInCaseInsensitiveWhenPresent(values: Array?) = + invoke(org.mybatis.dynamic.sql.util.kotlin.elements.isInCaseInsensitiveWhenPresent(values)) + infix fun BindableColumn.isInCaseInsensitiveWhenPresent(values: Collection?) = invoke(org.mybatis.dynamic.sql.util.kotlin.elements.isInCaseInsensitiveWhenPresent(values)) fun BindableColumn.isNotInCaseInsensitive(vararg values: String) = isNotInCaseInsensitive(values.asList()) + @JvmName("isNotInArrayCaseInsensitive") + infix fun BindableColumn.isNotInCaseInsensitive(values: Array) = + invoke(org.mybatis.dynamic.sql.util.kotlin.elements.isNotInCaseInsensitive(values)) + infix fun BindableColumn.isNotInCaseInsensitive(values: Collection) = invoke(org.mybatis.dynamic.sql.util.kotlin.elements.isNotInCaseInsensitive(values)) fun BindableColumn.isNotInCaseInsensitiveWhenPresent(vararg values: String?) = isNotInCaseInsensitiveWhenPresent(values.asList()) + @JvmName("isNotInArrayCaseInsensitiveWhenPresent") + infix fun BindableColumn.isNotInCaseInsensitiveWhenPresent(values: Array?) = + invoke(org.mybatis.dynamic.sql.util.kotlin.elements.isNotInCaseInsensitiveWhenPresent(values)) + infix fun BindableColumn.isNotInCaseInsensitiveWhenPresent(values: Collection?) = invoke(org.mybatis.dynamic.sql.util.kotlin.elements.isNotInCaseInsensitiveWhenPresent(values)) diff --git a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/elements/SqlElements.kt b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/elements/SqlElements.kt index 367ad0712..11082ae1d 100644 --- a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/elements/SqlElements.kt +++ b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/elements/SqlElements.kt @@ -262,6 +262,9 @@ fun isLessThanOrEqualToWhenPresent(value: T?): IsLessThanOrEqualTo = fun isIn(vararg values: T & Any): IsIn = isIn(values.asList()) +@JvmName("isInArray") +fun isIn(values: Array): IsIn = SqlBuilder.isIn(values.asList()) + fun isIn(values: Collection): IsIn = SqlBuilder.isIn(values) fun isIn(subQuery: KotlinSubQueryBuilder.() -> Unit): IsInWithSubselect = @@ -269,10 +272,16 @@ fun isIn(subQuery: KotlinSubQueryBuilder.() -> Unit): IsInWithSubselect = fun isInWhenPresent(vararg values: T?): IsIn = isInWhenPresent(values.asList()) +@JvmName("isInArrayWhenPresent") +fun isInWhenPresent(values: Array?): IsIn = SqlBuilder.isInWhenPresent(values?.asList()) + fun isInWhenPresent(values: Collection?): IsIn = SqlBuilder.isInWhenPresent(values) fun isNotIn(vararg values: T & Any): IsNotIn = isNotIn(values.asList()) +@JvmName("isNotInArray") +fun isNotIn(values: Array): IsNotIn = SqlBuilder.isNotIn(values.asList()) + fun isNotIn(values: Collection): IsNotIn = SqlBuilder.isNotIn(values) fun isNotIn(subQuery: KotlinSubQueryBuilder.() -> Unit): IsNotInWithSubselect = @@ -280,6 +289,9 @@ fun isNotIn(subQuery: KotlinSubQueryBuilder.() -> Unit): IsNotInWithSubselec fun isNotInWhenPresent(vararg values: T?): IsNotIn = isNotInWhenPresent(values.asList()) +@JvmName("isNotInArrayWhenPresent") +fun isNotInWhenPresent(values: Array?): IsNotIn = SqlBuilder.isNotInWhenPresent(values?.asList()) + fun isNotInWhenPresent(values: Collection?): IsNotIn = SqlBuilder.isNotInWhenPresent(values) fun isBetween(value1: T & Any): BetweenBuilder = BetweenBuilder(value1) @@ -318,22 +330,37 @@ fun isNotLikeCaseInsensitiveWhenPresent(value: String?): IsNotLikeCaseInsensitiv fun isInCaseInsensitive(vararg values: String): IsInCaseInsensitive = isInCaseInsensitive(values.asList()) +@JvmName("isInArrayCaseInsensitive") +fun isInCaseInsensitive(values: Array): IsInCaseInsensitive = SqlBuilder.isInCaseInsensitive(values.asList()) + fun isInCaseInsensitive(values: Collection): IsInCaseInsensitive = SqlBuilder.isInCaseInsensitive(values) fun isInCaseInsensitiveWhenPresent(vararg values: String?): IsInCaseInsensitive = isInCaseInsensitiveWhenPresent(values.asList()) +@JvmName("isInArrayCaseInsensitiveWhenPresent") +fun isInCaseInsensitiveWhenPresent(values: Array?): IsInCaseInsensitive = + SqlBuilder.isInCaseInsensitiveWhenPresent(values?.asList()) + fun isInCaseInsensitiveWhenPresent(values: Collection?): IsInCaseInsensitive = SqlBuilder.isInCaseInsensitiveWhenPresent(values) fun isNotInCaseInsensitive(vararg values: String): IsNotInCaseInsensitive = isNotInCaseInsensitive(values.asList()) +@JvmName("isNotInArrayCaseInsensitive") +fun isNotInCaseInsensitive(values: Array): IsNotInCaseInsensitive = + SqlBuilder.isNotInCaseInsensitive(values.asList()) + fun isNotInCaseInsensitive(values: Collection): IsNotInCaseInsensitive = SqlBuilder.isNotInCaseInsensitive(values) fun isNotInCaseInsensitiveWhenPresent(vararg values: String?): IsNotInCaseInsensitive = isNotInCaseInsensitiveWhenPresent(values.asList()) +@JvmName("isNotInArrayCaseInsensitiveWhenPresent") +fun isNotInCaseInsensitiveWhenPresent(values: Array?): IsNotInCaseInsensitive = + SqlBuilder.isNotInCaseInsensitiveWhenPresent(values?.asList()) + fun isNotInCaseInsensitiveWhenPresent(values: Collection?): IsNotInCaseInsensitive = SqlBuilder.isNotInCaseInsensitiveWhenPresent(values) diff --git a/src/test/kotlin/examples/kotlin/spring/canonical/InfixElementsTest.kt b/src/test/kotlin/examples/kotlin/spring/canonical/InfixElementsTest.kt index 8dce70567..e5fb13e4f 100644 --- a/src/test/kotlin/examples/kotlin/spring/canonical/InfixElementsTest.kt +++ b/src/test/kotlin/examples/kotlin/spring/canonical/InfixElementsTest.kt @@ -677,7 +677,7 @@ open class InfixElementsTest { fun testIsNotLike() { val selectStatement = select(firstName) { from(person) - where { firstName isNotLike "F%" } + where { firstName isNotLike "F%" } orderBy(id) } @@ -695,7 +695,7 @@ open class InfixElementsTest { fun testIsNotLikeWhenPresent() { val selectStatement = select(firstName) { from(person) - where { firstName isNotLikeWhenPresent "F%" } + where { firstName isNotLikeWhenPresent "F%" } orderBy(id) } @@ -955,10 +955,10 @@ open class InfixElementsTest { val selectStatement = select(firstName) { from(person) where { - upper(firstName) ( - isLike(fn).filter(String::isNotBlank) - .map(String::uppercase) - .map { "%$it%" }) + upper(firstName)( + isLike(fn).filter(String::isNotBlank) + .map(String::uppercase) + .map { "%$it%" }) } orderBy(id) configureStatement { isNonRenderingWhereClauseAllowed = true } @@ -981,7 +981,7 @@ open class InfixElementsTest { val selectStatement = select(firstName) { from(person) where { - upper(firstName) (isLike(fn).filter(String::isNotBlank) + upper(firstName)(isLike(fn).filter(String::isNotBlank) .map(String::uppercase) .map { "%$it%" }) } @@ -1264,4 +1264,252 @@ open class InfixElementsTest { assertThat(selectStatement.selectStatement).isEqualTo("select id from Person") } + + @Test + fun testIsInArray() { + fun search(vararg names: String) { + val selectStatement = select(firstName) { + from(person) + where { firstName isIn names } + orderBy(id) + } + + assertThat(selectStatement.selectStatement) + .isEqualTo("select first_name from Person where first_name in (:p1,:p2) order by id") + + val rows = template.selectList(selectStatement, String::class) + + assertThat(rows).hasSize(2) + assertThat(rows[0]).isEqualTo("Fred") + } + + search("Fred", "Wilma") + } + + @Test + fun testIsInArrayWhenPresent() { + fun search(vararg names: String?) { + val selectStatement = select(firstName) { + from(person) + where { firstName isInWhenPresent names } + orderBy(id) + } + + assertThat(selectStatement.selectStatement) + .isEqualTo("select first_name from Person where first_name in (:p1,:p2) order by id") + + val rows = template.selectList(selectStatement, String::class) + + assertThat(rows).hasSize(2) + assertThat(rows[0]).isEqualTo("Fred") + } + + search("Fred", null, "Wilma") + } + + @Test + fun testIsInArrayWhenPresentNullArray() { + val selectStatement = select(firstName) { + from(person) + where { + id isLessThan 10 + and { firstName isInWhenPresent null as Array? } + } + orderBy(id) + } + + assertThat(selectStatement.selectStatement) + .isEqualTo("select first_name from Person where id < :p1 order by id") + + val rows = template.selectList(selectStatement, String::class) + + assertThat(rows).hasSize(6) + assertThat(rows[0]).isEqualTo("Fred") + } + + @Test + fun testIsNotInArray() { + fun search(vararg names: String) { + val selectStatement = select(firstName) { + from(person) + where { firstName isNotIn names } + orderBy(id) + } + + assertThat(selectStatement.selectStatement) + .isEqualTo("select first_name from Person where first_name not in (:p1,:p2) order by id") + + val rows = template.selectList(selectStatement, String::class) + + assertThat(rows).hasSize(4) + assertThat(rows[0]).isEqualTo("Pebbles") + } + + search("Fred", "Wilma") + } + + @Test + fun testIsNotInArrayWhenPresent() { + fun search(vararg names: String?) { + val selectStatement = select(firstName) { + from(person) + where { firstName isNotInWhenPresent names } + orderBy(id) + } + + assertThat(selectStatement.selectStatement) + .isEqualTo("select first_name from Person where first_name not in (:p1,:p2) order by id") + + val rows = template.selectList(selectStatement, String::class) + + assertThat(rows).hasSize(4) + assertThat(rows[0]).isEqualTo("Pebbles") + } + + search("Fred", null, "Wilma") + } + + @Test + fun testIsNotInArrayWhenPresentNullArray() { + val selectStatement = select(firstName) { + from(person) + where { + id isLessThan 10 + and { firstName isNotInWhenPresent null as Array? } + } + orderBy(id) + } + + assertThat(selectStatement.selectStatement) + .isEqualTo("select first_name from Person where id < :p1 order by id") + + val rows = template.selectList(selectStatement, String::class) + + assertThat(rows).hasSize(6) + assertThat(rows[0]).isEqualTo("Fred") + } + + @Test + fun testIsInArrayCaseInsensitive() { + fun search(vararg names: String) { + val selectStatement = select(firstName) { + from(person) + where { firstName isInCaseInsensitive names } + orderBy(id) + } + + assertThat(selectStatement.selectStatement) + .isEqualTo("select first_name from Person where upper(first_name) in (:p1,:p2) order by id") + + val rows = template.selectList(selectStatement, String::class) + + assertThat(rows).hasSize(2) + assertThat(rows[0]).isEqualTo("Fred") + } + + search("Fred", "Wilma") + } + + @Test + fun testIsInArrayCaseInsensitiveWhenPresent() { + fun search(vararg names: String?) { + val selectStatement = select(firstName) { + from(person) + where { firstName isInCaseInsensitiveWhenPresent names } + orderBy(id) + } + + assertThat(selectStatement.selectStatement) + .isEqualTo("select first_name from Person where upper(first_name) in (:p1,:p2) order by id") + + val rows = template.selectList(selectStatement, String::class) + + assertThat(rows).hasSize(2) + assertThat(rows[0]).isEqualTo("Fred") + } + + search("Fred", null, "Wilma") + } + + @Test + fun testIsInArrayCaseInsensitiveWhenPresentNullArray() { + val selectStatement = select(firstName) { + from(person) + where { + id isLessThan 10 + and { firstName isInCaseInsensitiveWhenPresent null as Array? } + } + orderBy(id) + } + + assertThat(selectStatement.selectStatement) + .isEqualTo("select first_name from Person where id < :p1 order by id") + + val rows = template.selectList(selectStatement, String::class) + + assertThat(rows).hasSize(6) + assertThat(rows[0]).isEqualTo("Fred") + } + + @Test + fun testIsNotInArrayCaseInsensitive() { + fun search(vararg names: String) { + val selectStatement = select(firstName) { + from(person) + where { firstName isNotInCaseInsensitive names } + orderBy(id) + } + + assertThat(selectStatement.selectStatement) + .isEqualTo("select first_name from Person where upper(first_name) not in (:p1,:p2) order by id") + + val rows = template.selectList(selectStatement, String::class) + + assertThat(rows).hasSize(4) + assertThat(rows[0]).isEqualTo("Pebbles") + } + + search("Fred", "Wilma") + } + + @Test + fun testIsNotInArrayCaseInsensitiveWhenPresent() { + fun search(vararg names: String?) { + val selectStatement = select(firstName) { + from(person) + where { firstName isNotInCaseInsensitiveWhenPresent names } + orderBy(id) + } + + assertThat(selectStatement.selectStatement) + .isEqualTo("select first_name from Person where upper(first_name) not in (:p1,:p2) order by id") + + val rows = template.selectList(selectStatement, String::class) + + assertThat(rows).hasSize(4) + assertThat(rows[0]).isEqualTo("Pebbles") + } + + search("Fred", null, "Wilma") + } + + @Test + fun testIsNotInArrayCaseInsensitiveWhenPresentNullArray() { + val selectStatement = select(firstName) { + from(person) + where { + id isLessThan 10 + and { firstName isNotInCaseInsensitiveWhenPresent null as Array? } + } + orderBy(id) + } + + assertThat(selectStatement.selectStatement) + .isEqualTo("select first_name from Person where id < :p1 order by id") + + val rows = template.selectList(selectStatement, String::class) + + assertThat(rows).hasSize(6) + assertThat(rows[0]).isEqualTo("Fred") + } } diff --git a/src/test/kotlin/nullability/test/InWhenPresentTest.kt b/src/test/kotlin/nullability/test/InWhenPresentTest.kt index 3a922f812..2d3be77b5 100644 --- a/src/test/kotlin/nullability/test/InWhenPresentTest.kt +++ b/src/test/kotlin/nullability/test/InWhenPresentTest.kt @@ -176,7 +176,7 @@ class InWhenPresentTest { fun testFunction() { countFrom(person) { - where { id (isInWhenPresent(null)) } + where { id (isInWhenPresent(null as List?) ) } } } """ diff --git a/src/test/kotlin/nullability/test/NotInWhenPresentTest.kt b/src/test/kotlin/nullability/test/NotInWhenPresentTest.kt index d5e2fb716..5304b2a0b 100644 --- a/src/test/kotlin/nullability/test/NotInWhenPresentTest.kt +++ b/src/test/kotlin/nullability/test/NotInWhenPresentTest.kt @@ -176,7 +176,7 @@ class NotInWhenPresentTest { fun testFunction() { countFrom(person) { - where { id (isNotInWhenPresent(null)) } + where { id (isNotInWhenPresent(null as List?) ) } } } """ From 053f6afddf709ffbc81a3737688193b921e1239d Mon Sep 17 00:00:00 2001 From: Jeff Butler Date: Tue, 23 Apr 2024 10:41:19 -0400 Subject: [PATCH 2/3] Documentation --- CHANGELOG.md | 3 +++ src/site/markdown/docs/kotlinWhereClauses.md | 13 +++++++------ 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ee12f424..258faf05e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -65,6 +65,9 @@ types - which is a rare usage. Please let us know if this causes an undo hardshi as rendering empty lists). This change should be transparent to users unless they have implemented custom conditions. 4. Added a configuration setting to allow empty list conditions to render. This could generate invalid SQL, but might be a good safety measure in some cases. +5. Added Array based functions for the "in" and "not in" conditions in the Kotlin DSL. These functions allow a more + natural use of an Array as an input for an "in" condition. They also allow easy reuse of a vararg argument in a + function. ## Release 1.5.0 - April 21, 2023 diff --git a/src/site/markdown/docs/kotlinWhereClauses.md b/src/site/markdown/docs/kotlinWhereClauses.md index 23ab8801d..acb20521e 100644 --- a/src/site/markdown/docs/kotlinWhereClauses.md +++ b/src/site/markdown/docs/kotlinWhereClauses.md @@ -85,11 +85,10 @@ where clauses with and/or/not phrases. See the following example of a complex wh select(foo) { from(bar) where { - id isEqualTo 3 - and { id isEqualTo 4 } + id isEqualTo 3 + or { id isEqualTo 4 } + and { not { id isEqualTo 6 } } } - or { id isEqualTo 4 } - and { not { id isEqualTo 6 } } } ``` @@ -215,8 +214,10 @@ These criteria should be updated by moving the column and condition into a lambd ```kotlin select(foo) { from(bar) - where { id isEqualTo 3 } - or { id isEqualTo 4 } + where { + id isEqualTo 3 + or { id isEqualTo 4 } + } } ``` From 2a8bda03b4983e3e1c3ccae9f5246b10e5a1e0ca Mon Sep 17 00:00:00 2001 From: Jeff Butler Date: Tue, 23 Apr 2024 10:51:03 -0400 Subject: [PATCH 3/3] Add PR to CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 258faf05e..66d3a6cc9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -67,7 +67,7 @@ types - which is a rare usage. Please let us know if this causes an undo hardshi a good safety measure in some cases. 5. Added Array based functions for the "in" and "not in" conditions in the Kotlin DSL. These functions allow a more natural use of an Array as an input for an "in" condition. They also allow easy reuse of a vararg argument in a - function. + function. ([#781](https://github.com/mybatis/mybatis-dynamic-sql/pull/781)) ## Release 1.5.0 - April 21, 2023