Skip to content

Commit 2aa3f6c

Browse files
committed
Add infix/shortcut functions for all built-in conditions
1 parent ceccc3a commit 2aa3f6c

File tree

7 files changed

+1804
-61
lines changed

7 files changed

+1804
-61
lines changed
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
* Copyright 2016-2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.mybatis.dynamic.sql.util.kotlin
17+
18+
/**
19+
* This exception is thrown when a where clause contains more than one criterion and there
20+
* is not an "and" or an "or" to connect them.
21+
*
22+
* @since 1.4.0
23+
*/
24+
class DuplicateInitialCriterionException : RuntimeException(
25+
"Setting more than one initial criterion is not allowed. " +
26+
"Additional criteria should be added with \"and\" or \"or\" expression")

src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/GroupingCriteriaCollector.kt

Lines changed: 169 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import org.mybatis.dynamic.sql.BindableColumn
1919
import org.mybatis.dynamic.sql.ColumnAndConditionCriterion
2020
import org.mybatis.dynamic.sql.CriteriaGroup
2121
import org.mybatis.dynamic.sql.AndOrCriteriaGroup
22+
import org.mybatis.dynamic.sql.BasicColumn
2223
import org.mybatis.dynamic.sql.ExistsCriterion
2324
import org.mybatis.dynamic.sql.NotCriterion
2425
import org.mybatis.dynamic.sql.SqlBuilder
@@ -40,9 +41,17 @@ typealias GroupingCriteriaReceiver = GroupingCriteriaCollector.() -> Unit
4041
* Only one of these initial criterion functions should be called within each scope. If you need more than one,
4142
* use a sub-criteria joined with "and" or "or"
4243
*/
44+
@Suppress("TooManyFunctions")
4345
@MyBatisDslMarker
4446
class GroupingCriteriaCollector {
4547
internal var initialCriterion: SqlCriterion? = null
48+
private set(value) {
49+
if (field != null) {
50+
throw DuplicateInitialCriterionException()
51+
}
52+
field = value
53+
}
54+
4655
internal val subCriteria = mutableListOf<AndOrCriteriaGroup>()
4756

4857
fun and(criteriaReceiver: GroupingCriteriaReceiver): Unit =
@@ -104,9 +113,168 @@ class GroupingCriteriaCollector {
104113
* Build an initial criterion for a where clause, or a nested and/or/not group.
105114
* You can use it like A (isEqualTo(3))
106115
*/
107-
infix operator fun <T : Any> BindableColumn<T>.invoke(condition: VisitableCondition<T>) {
116+
operator fun <T> BindableColumn<T>.invoke(condition: VisitableCondition<T>) {
108117
initialCriterion = ColumnAndConditionCriterion.withColumn(this)
109118
.withCondition(condition)
110119
.build()
111120
}
121+
122+
// infix functions...we may be able to rewrite these as extension functions once Kotlin solves the multiple
123+
// receivers problem (https://youtrack.jetbrains.com/issue/KT-42435)
124+
125+
// conditions for all data types
126+
fun <T> BindableColumn<T>.isNull() = invoke(SqlBuilder.isNull())
127+
128+
fun <T> BindableColumn<T>.isNotNull() = invoke(SqlBuilder.isNotNull())
129+
130+
infix fun <T : Any> BindableColumn<T>.isEqualTo(value: T) = invoke(SqlBuilder.isEqualTo(value))
131+
132+
infix fun <T> BindableColumn<T>.isEqualToSubQuery(subQuery: KotlinSubQueryBuilder.() -> Unit) =
133+
invoke(SqlBuilder.isEqualTo(KotlinSubQueryBuilder().apply(subQuery)))
134+
135+
infix fun <T> BindableColumn<T>.isEqualTo(column: BasicColumn) = invoke(SqlBuilder.isEqualTo(column))
136+
137+
infix fun <T : Any> BindableColumn<T>.isEqualToWhenPresent(value: T?) =
138+
invoke(SqlBuilder.isEqualToWhenPresent<T>(value))
139+
140+
infix fun <T : Any> BindableColumn<T>.isNotEqualTo(value: T) = invoke(SqlBuilder.isNotEqualTo(value))
141+
142+
infix fun <T> BindableColumn<T>.isNotEqualToSubQuery(subQuery: KotlinSubQueryBuilder.() -> Unit) =
143+
invoke(SqlBuilder.isNotEqualTo(KotlinSubQueryBuilder().apply(subQuery)))
144+
145+
infix fun <T> BindableColumn<T>.isNotEqualTo(column: BasicColumn) = invoke(SqlBuilder.isNotEqualTo(column))
146+
147+
infix fun <T : Any> BindableColumn<T>.isNotEqualToWhenPresent(value: T?) =
148+
invoke(SqlBuilder.isNotEqualToWhenPresent<T>(value))
149+
150+
infix fun <T : Any> BindableColumn<T>.isGreaterThan(value: T) = invoke(SqlBuilder.isGreaterThan(value))
151+
152+
infix fun <T> BindableColumn<T>.isGreaterThanSubQuery(subQuery: KotlinSubQueryBuilder.() -> Unit) =
153+
invoke(SqlBuilder.isGreaterThan(KotlinSubQueryBuilder().apply(subQuery)))
154+
155+
infix fun <T> BindableColumn<T>.isGreaterThan(column: BasicColumn) = invoke(SqlBuilder.isGreaterThan(column))
156+
157+
infix fun <T : Any> BindableColumn<T>.isGreaterThanWhenPresent(value: T?) =
158+
invoke(SqlBuilder.isGreaterThanWhenPresent<T>(value))
159+
160+
infix fun <T : Any> BindableColumn<T>.isGreaterThanOrEqualTo(value: T) =
161+
invoke(SqlBuilder.isGreaterThanOrEqualTo(value))
162+
163+
infix fun <T> BindableColumn<T>.isGreaterThanOrEqualToSubQuery(subQuery: KotlinSubQueryBuilder.() -> Unit) =
164+
invoke(SqlBuilder.isGreaterThanOrEqualTo(KotlinSubQueryBuilder().apply(subQuery)))
165+
166+
infix fun <T> BindableColumn<T>.isGreaterThanOrEqualTo(column: BasicColumn) =
167+
invoke(SqlBuilder.isGreaterThanOrEqualTo(column))
168+
169+
infix fun <T : Any> BindableColumn<T>.isGreaterThanOrEqualToWhenPresent(value: T?) =
170+
invoke(SqlBuilder.isGreaterThanOrEqualToWhenPresent<T>(value))
171+
172+
infix fun <T : Any> BindableColumn<T>.isLessThan(value: T) = invoke(SqlBuilder.isLessThan(value))
173+
174+
infix fun <T> BindableColumn<T>.isLessThanSubQuery(subQuery: KotlinSubQueryBuilder.() -> Unit) =
175+
invoke(SqlBuilder.isLessThan(KotlinSubQueryBuilder().apply(subQuery)))
176+
177+
infix fun <T> BindableColumn<T>.isLessThan(column: BasicColumn) = invoke(SqlBuilder.isLessThan(column))
178+
179+
infix fun <T : Any> BindableColumn<T>.isLessThanWhenPresent(value: T?) =
180+
invoke(SqlBuilder.isLessThanWhenPresent<T>(value))
181+
182+
infix fun <T : Any> BindableColumn<T>.isLessThanOrEqualTo(value: T) = invoke(SqlBuilder.isLessThanOrEqualTo(value))
183+
184+
infix fun <T> BindableColumn<T>.isLessThanOrEqualToSubQuery(subQuery: KotlinSubQueryBuilder.() -> Unit) =
185+
invoke(SqlBuilder.isLessThanOrEqualTo(KotlinSubQueryBuilder().apply(subQuery)))
186+
187+
infix fun <T> BindableColumn<T>.isLessThanOrEqualTo(column: BasicColumn) =
188+
invoke(SqlBuilder.isLessThanOrEqualTo(column))
189+
190+
infix fun <T : Any> BindableColumn<T>.isLessThanOrEqualToWhenPresent(value: T?) =
191+
invoke(SqlBuilder.isLessThanOrEqualToWhenPresent<T>(value))
192+
193+
fun <T : Any> BindableColumn<T>.isIn(vararg values: T) = isIn(values.asList())
194+
195+
infix fun <T : Any> BindableColumn<T>.isIn(values: Collection<T>) = invoke(SqlBuilder.isIn(values))
196+
197+
infix fun <T> BindableColumn<T>.isIn(subQuery: KotlinSubQueryBuilder.() -> Unit) =
198+
invoke(SqlBuilder.isIn(KotlinSubQueryBuilder().apply(subQuery)))
199+
200+
fun <T : Any> BindableColumn<T>.isInWhenPresent(vararg values: T?) = isInWhenPresent(values.asList())
201+
202+
infix fun <T : Any> BindableColumn<T>.isInWhenPresent(values: Collection<T?>?) =
203+
invoke(SqlBuilder.isInWhenPresent<T>(values))
204+
205+
fun <T : Any> BindableColumn<T>.isNotIn(vararg values: T) = isNotIn(values.asList())
206+
207+
infix fun <T : Any> BindableColumn<T>.isNotIn(values: Collection<T>) = invoke(SqlBuilder.isNotIn(values))
208+
209+
infix fun <T> BindableColumn<T>.isNotIn(subQuery: KotlinSubQueryBuilder.() -> Unit) =
210+
invoke(SqlBuilder.isNotIn(KotlinSubQueryBuilder().apply(subQuery)))
211+
212+
fun <T : Any> BindableColumn<T>.isNotInWhenPresent(vararg values: T?) = isNotInWhenPresent(values.asList())
213+
214+
infix fun <T : Any> BindableColumn<T>.isNotInWhenPresent(values: Collection<T?>?) =
215+
invoke(SqlBuilder.isNotInWhenPresent<T>(values))
216+
217+
infix fun <T : Any> BindableColumn<T>.isBetween(value1: T) =
218+
InfixBetweenBuilder(value1) { invoke(it) }
219+
220+
infix fun <T : Any> BindableColumn<T>.isBetweenWhenPresent(value1: T?) =
221+
InfixBetweenWhenPresentBuilder(value1) { invoke(it) }
222+
223+
infix fun <T : Any> BindableColumn<T>.isNotBetween(value1: T) =
224+
InfixNotBetweenBuilder(value1) { invoke(it) }
225+
226+
infix fun <T : Any> BindableColumn<T>.isNotBetweenWhenPresent(value1: T?) =
227+
InfixNotBetweenWhenPresentBuilder(value1) { invoke(it) }
228+
229+
// for string columns, but generic for columns with type handlers
230+
infix fun <T : Any> BindableColumn<T>.isLike(value: T) = invoke(SqlBuilder.isLike(value))
231+
232+
infix fun <T : Any> BindableColumn<T>.isLikeWhenPresent(value: T?) =
233+
invoke(SqlBuilder.isLikeWhenPresent<T>(value))
234+
235+
infix fun <T : Any> BindableColumn<T>.isNotLike(value: T) = invoke(SqlBuilder.isNotLike(value))
236+
237+
infix fun <T : Any> BindableColumn<T>.isNotLikeWhenPresent(value: T?) =
238+
invoke(SqlBuilder.isNotLikeWhenPresent<T>(value))
239+
240+
// shortcuts for booleans
241+
fun BindableColumn<Boolean>.isTrue() = isEqualTo(true)
242+
243+
fun BindableColumn<Boolean>.isFalse() = isEqualTo(false)
244+
245+
// conditions for strings only
246+
infix fun BindableColumn<String>.isLikeCaseInsensitive(value: String) =
247+
invoke(SqlBuilder.isLikeCaseInsensitive(value))
248+
249+
infix fun BindableColumn<String>.isLikeCaseInsensitiveWhenPresent(value: String?) =
250+
invoke(SqlBuilder.isLikeCaseInsensitiveWhenPresent(value))
251+
252+
infix fun BindableColumn<String>.isNotLikeCaseInsensitive(value: String) =
253+
invoke(SqlBuilder.isNotLikeCaseInsensitive(value))
254+
255+
infix fun BindableColumn<String>.isNotLikeCaseInsensitiveWhenPresent(value: String?) =
256+
invoke(SqlBuilder.isNotLikeCaseInsensitiveWhenPresent(value))
257+
258+
fun BindableColumn<String>.isInCaseInsensitive(vararg values: String) = isInCaseInsensitive(values.asList())
259+
260+
infix fun BindableColumn<String>.isInCaseInsensitive(values: Collection<String>) =
261+
invoke(SqlBuilder.isInCaseInsensitive(values))
262+
263+
fun BindableColumn<String>.isInCaseInsensitiveWhenPresent(vararg values: String?) =
264+
isInCaseInsensitiveWhenPresent(values.asList())
265+
266+
infix fun BindableColumn<String>.isInCaseInsensitiveWhenPresent(values: Collection<String?>?) =
267+
invoke(SqlBuilder.isInCaseInsensitiveWhenPresent(values))
268+
269+
fun BindableColumn<String>.isNotInCaseInsensitive(vararg values: String) =
270+
isNotInCaseInsensitive(values.asList())
271+
272+
infix fun BindableColumn<String>.isNotInCaseInsensitive(values: Collection<String>) =
273+
invoke(SqlBuilder.isNotInCaseInsensitive(values))
274+
275+
fun BindableColumn<String>.isNotInCaseInsensitiveWhenPresent(vararg values: String?) =
276+
isNotInCaseInsensitiveWhenPresent(values.asList())
277+
278+
infix fun BindableColumn<String>.isNotInCaseInsensitiveWhenPresent(values: Collection<String?>?) =
279+
invoke(SqlBuilder.isNotInCaseInsensitiveWhenPresent(values))
112280
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* Copyright 2016-2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.mybatis.dynamic.sql.util.kotlin
17+
18+
import org.mybatis.dynamic.sql.SqlBuilder
19+
import org.mybatis.dynamic.sql.VisitableCondition
20+
21+
class InfixBetweenBuilder<T> (private val value1: T, private val conditionConsumer: (VisitableCondition<T>) -> Unit) {
22+
infix fun and(value2: T) = conditionConsumer.invoke(SqlBuilder.isBetween(value1).and(value2))
23+
}
24+
25+
class InfixBetweenWhenPresentBuilder<T>(
26+
private val value1: T?,
27+
private val conditionConsumer: (VisitableCondition<T>) -> Unit
28+
) {
29+
infix fun and(value2: T?) = conditionConsumer.invoke(SqlBuilder.isBetweenWhenPresent<T>(value1).and(value2))
30+
}
31+
32+
class InfixNotBetweenBuilder<T>(
33+
private val value1: T,
34+
private val conditionConsumer: (VisitableCondition<T>) -> Unit
35+
) {
36+
infix fun and(value2: T) = conditionConsumer.invoke(SqlBuilder.isNotBetween(value1).and(value2))
37+
}
38+
39+
class InfixNotBetweenWhenPresentBuilder<T>(
40+
private val value1: T?,
41+
private val conditionConsumer: (VisitableCondition<T>) -> Unit
42+
) {
43+
infix fun and(value2: T?) = conditionConsumer.invoke(SqlBuilder.isNotBetweenWhenPresent<T>(value1).and(value2))
44+
}

src/test/kotlin/examples/kotlin/mybatis3/canonical/ReusableWhereTest.kt

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,6 @@ import org.assertj.core.api.Assertions.assertThat
3131
import org.junit.jupiter.api.Test
3232
import org.mybatis.dynamic.sql.util.kotlin.WhereApplier
3333
import org.mybatis.dynamic.sql.util.kotlin.andThen
34-
import org.mybatis.dynamic.sql.util.kotlin.elements.isEqualTo
35-
import org.mybatis.dynamic.sql.util.kotlin.elements.isLessThan
36-
import org.mybatis.dynamic.sql.util.kotlin.elements.isNotNull
37-
import org.mybatis.dynamic.sql.util.kotlin.elements.isNull
3834
import org.mybatis.dynamic.sql.util.kotlin.mybatis3.select
3935
import java.io.InputStreamReader
4036
import java.sql.DriverManager
@@ -114,12 +110,10 @@ class ReusableWhereTest {
114110

115111
@Test
116112
fun testComposition() {
117-
var whereApplier = commonWhere.andThen {
118-
and(birthDate, isNotNull())
119-
}
120-
121-
whereApplier = whereApplier.andThen {
122-
or(addressId, isLessThan(3))
113+
val whereApplier = commonWhere.andThen {
114+
and { birthDate.isNotNull() }
115+
}.andThen {
116+
or { addressId isLessThan 3 }
123117
}
124118

125119
val selectStatement = select(person.allColumns()) {
@@ -136,8 +130,8 @@ class ReusableWhereTest {
136130
}
137131

138132
private val commonWhere: WhereApplier = {
139-
where(id, isEqualTo(1))
140-
or(occupation, isNull<String>())
133+
where { id isEqualTo 1 }
134+
or { occupation.isNull() }
141135
}
142136

143137
companion object {

0 commit comments

Comments
 (0)