Skip to content

Commit 7fb7717

Browse files
committed
[Kotlin] Improve reusable where support
1 parent 5a90096 commit 7fb7717

File tree

4 files changed

+113
-63
lines changed

4 files changed

+113
-63
lines changed

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

Lines changed: 53 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -29,30 +29,13 @@ import org.mybatis.dynamic.sql.util.Messages
2929

3030
typealias GroupingCriteriaReceiver = GroupingCriteriaCollector.() -> Unit
3131

32-
/**
33-
* This class is used to gather criteria for a where clause. The class gathers two types of criteria:
34-
* an initial criterion, and sub-criteria connected by either an "and" or an "or".
35-
*
36-
* An initial criterion can be one of four types:
37-
* - A column and condition (called with the invoke operator on a column, or an infix function)
38-
* - An exists operator (called with the "exists" function)
39-
* - A criteria group which is essentially parenthesis within the where clause (called with the "group" function)
40-
* - A criteria group preceded with "not" (called with the "not" function)
41-
*
42-
* Only one of the initial criterion functions should be called within each scope. If you need more than one,
43-
* use a sub-criterion joined with "and" or "or"
44-
*/
45-
@Suppress("TooManyFunctions")
46-
@MyBatisDslMarker
47-
class GroupingCriteriaCollector {
48-
internal var initialCriterion: SqlCriterion? = null
49-
private set(value) {
50-
if (field != null) {
51-
throw KInvalidSQLException(Messages.getString("ERROR.21")) //$NON-NLS-1$
52-
}
53-
field = value
54-
}
32+
fun GroupingCriteriaReceiver.andThen(after: SubCriteriaCollector.() -> Unit): GroupingCriteriaReceiver = {
33+
invoke(this)
34+
after(this)
35+
}
5536

37+
@MyBatisDslMarker
38+
sealed class SubCriteriaCollector {
5639
internal val subCriteria = mutableListOf<AndOrCriteriaGroup>()
5740

5841
/**
@@ -64,11 +47,11 @@ class GroupingCriteriaCollector {
6447
* @param criteriaReceiver a function to create the contained criteria
6548
*/
6649
fun and(criteriaReceiver: GroupingCriteriaReceiver): Unit =
67-
with(GroupingCriteriaCollector().apply(criteriaReceiver)) {
68-
this@GroupingCriteriaCollector.subCriteria.add(
50+
GroupingCriteriaCollector().apply(criteriaReceiver).let {
51+
subCriteria.add(
6952
AndOrCriteriaGroup.Builder().withConnector("and") //$NON-NLS-1$
70-
.withInitialCriterion(initialCriterion)
71-
.withSubCriteria(subCriteria)
53+
.withInitialCriterion(it.initialCriterion)
54+
.withSubCriteria(it.subCriteria)
7255
.build()
7356
)
7457
}
@@ -85,7 +68,7 @@ class GroupingCriteriaCollector {
8568
*
8669
*/
8770
fun and(criteria: List<AndOrCriteriaGroup>) {
88-
this@GroupingCriteriaCollector.subCriteria.add(
71+
subCriteria.add(
8972
AndOrCriteriaGroup.Builder().withConnector("and") //$NON-NLS-1$
9073
.withSubCriteria(criteria)
9174
.build()
@@ -101,11 +84,11 @@ class GroupingCriteriaCollector {
10184
* @param criteriaReceiver a function to create the contained criteria
10285
*/
10386
fun or(criteriaReceiver: GroupingCriteriaReceiver): Unit =
104-
with(GroupingCriteriaCollector().apply(criteriaReceiver)) {
105-
this@GroupingCriteriaCollector.subCriteria.add(
87+
GroupingCriteriaCollector().apply(criteriaReceiver).let {
88+
subCriteria.add(
10689
AndOrCriteriaGroup.Builder().withConnector("or") //$NON-NLS-1$
107-
.withInitialCriterion(initialCriterion)
108-
.withSubCriteria(subCriteria)
90+
.withInitialCriterion(it.initialCriterion)
91+
.withSubCriteria(it.subCriteria)
10992
.build()
11093
)
11194
}
@@ -122,12 +105,37 @@ class GroupingCriteriaCollector {
122105
*
123106
*/
124107
fun or(criteria: List<AndOrCriteriaGroup>) {
125-
this@GroupingCriteriaCollector.subCriteria.add(
108+
subCriteria.add(
126109
AndOrCriteriaGroup.Builder().withConnector("or") //$NON-NLS-1$
127110
.withSubCriteria(criteria)
128111
.build()
129112
)
130113
}
114+
}
115+
116+
/**
117+
* This class is used to gather criteria for a where clause. The class gathers two types of criteria:
118+
* an initial criterion, and sub-criteria connected by either an "and" or an "or".
119+
*
120+
* An initial criterion can be one of four types:
121+
* - A column and condition (called with the invoke operator on a column, or an infix function)
122+
* - An exists operator (called with the "exists" function)
123+
* - A criteria group which is essentially parenthesis within the where clause (called with the "group" function)
124+
* - A criteria group preceded with "not" (called with the "not" function)
125+
*
126+
* Only one of the initial criterion functions should be called within each scope. If you need more than one,
127+
* use a sub-criterion joined with "and" or "or"
128+
*/
129+
@Suppress("TooManyFunctions")
130+
@MyBatisDslMarker
131+
class GroupingCriteriaCollector : SubCriteriaCollector() {
132+
internal var initialCriterion: SqlCriterion? = null
133+
private set(value) {
134+
if (field != null) {
135+
throw KInvalidSQLException(Messages.getString("ERROR.21")) //$NON-NLS-1$
136+
}
137+
field = value
138+
}
131139

132140
/**
133141
* Add an initial criterion preceded with "not" to the current context. If the receiver adds more than one
@@ -139,10 +147,10 @@ class GroupingCriteriaCollector {
139147
* @param criteriaReceiver a function to create the contained criteria
140148
*/
141149
fun not(criteriaReceiver: GroupingCriteriaReceiver): Unit =
142-
with(GroupingCriteriaCollector().apply(criteriaReceiver)) {
143-
this@GroupingCriteriaCollector.initialCriterion = NotCriterion.Builder()
144-
.withInitialCriterion(initialCriterion)
145-
.withSubCriteria(subCriteria)
150+
GroupingCriteriaCollector().apply(criteriaReceiver).let {
151+
initialCriterion = NotCriterion.Builder()
152+
.withInitialCriterion(it.initialCriterion)
153+
.withSubCriteria(it.subCriteria)
146154
.build()
147155
}
148156

@@ -159,9 +167,7 @@ class GroupingCriteriaCollector {
159167
*
160168
*/
161169
fun not(criteria: List<AndOrCriteriaGroup>) {
162-
this@GroupingCriteriaCollector.initialCriterion = NotCriterion.Builder()
163-
.withSubCriteria(criteria)
164-
.build()
170+
initialCriterion = NotCriterion.Builder().withSubCriteria(criteria).build()
165171
}
166172

167173
/**
@@ -173,9 +179,8 @@ class GroupingCriteriaCollector {
173179
* @param kotlinSubQueryBuilder a function to create a select statement
174180
*/
175181
fun exists(kotlinSubQueryBuilder: KotlinSubQueryBuilder.() -> Unit): Unit =
176-
with(KotlinSubQueryBuilder().apply(kotlinSubQueryBuilder)) {
177-
this@GroupingCriteriaCollector.initialCriterion =
178-
ExistsCriterion.Builder().withExistsPredicate(SqlBuilder.exists(this)).build()
182+
KotlinSubQueryBuilder().apply(kotlinSubQueryBuilder).let {
183+
initialCriterion = ExistsCriterion.Builder().withExistsPredicate(SqlBuilder.exists(it)).build()
179184
}
180185

181186
/**
@@ -192,10 +197,10 @@ class GroupingCriteriaCollector {
192197
* @param criteriaReceiver a function to create the contained criteria
193198
*/
194199
fun group(criteriaReceiver: GroupingCriteriaReceiver): Unit =
195-
with(GroupingCriteriaCollector().apply(criteriaReceiver)) {
196-
this@GroupingCriteriaCollector.initialCriterion = CriteriaGroup.Builder()
197-
.withInitialCriterion(initialCriterion)
198-
.withSubCriteria(subCriteria)
200+
GroupingCriteriaCollector().apply(criteriaReceiver).let {
201+
initialCriterion = CriteriaGroup.Builder()
202+
.withInitialCriterion(it.initialCriterion)
203+
.withSubCriteria(it.subCriteria)
199204
.build()
200205
}
201206

@@ -212,9 +217,7 @@ class GroupingCriteriaCollector {
212217
*
213218
*/
214219
fun group(criteria: List<AndOrCriteriaGroup>) {
215-
this@GroupingCriteriaCollector.initialCriterion = CriteriaGroup.Builder()
216-
.withSubCriteria(criteria)
217-
.build()
220+
initialCriterion = CriteriaGroup.Builder().withSubCriteria(criteria).build()
218221
}
219222

220223
/**

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

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,10 @@ import org.mybatis.dynamic.sql.where.AbstractWhereSupport
2525
@DslMarker
2626
annotation class MyBatisDslMarker
2727

28+
@Deprecated("Please use independentWhere")
2829
typealias WhereApplier = KotlinBaseBuilder<*>.() -> Unit
2930

31+
@Deprecated("Please use independentWhere")
3032
fun WhereApplier.andThen(after: WhereApplier): WhereApplier = {
3133
invoke(this)
3234
after(this)
@@ -41,32 +43,33 @@ abstract class KotlinBaseBuilder<D : AbstractWhereSupport<*,*>> {
4143
}
4244

4345
fun where(criteria: GroupingCriteriaReceiver): Unit =
44-
with(GroupingCriteriaCollector().apply(criteria)) {
45-
this@KotlinBaseBuilder.getDsl().where(initialCriterion, subCriteria)
46+
GroupingCriteriaCollector().apply(criteria).let {
47+
getDsl().where(it.initialCriterion, it.subCriteria)
4648
}
4749

4850
fun where(criteria: List<AndOrCriteriaGroup>) {
4951
getDsl().where(criteria)
5052
}
5153

5254
fun and(criteria: GroupingCriteriaReceiver): Unit =
53-
with(GroupingCriteriaCollector().apply(criteria)) {
54-
this@KotlinBaseBuilder.getDsl().where().and(initialCriterion, subCriteria)
55+
GroupingCriteriaCollector().apply(criteria).let {
56+
getDsl().where().and(it.initialCriterion, it.subCriteria)
5557
}
5658

5759
fun and(criteria: List<AndOrCriteriaGroup>) {
5860
getDsl().where().and(criteria)
5961
}
6062

6163
fun or(criteria: GroupingCriteriaReceiver): Unit =
62-
with(GroupingCriteriaCollector().apply(criteria)) {
63-
this@KotlinBaseBuilder.getDsl().where().or(initialCriterion, subCriteria)
64+
GroupingCriteriaCollector().apply(criteria).let {
65+
getDsl().where().or(it.initialCriterion, it.subCriteria)
6466
}
6567

6668
fun or(criteria: List<AndOrCriteriaGroup>) {
6769
getDsl().where().or(criteria)
6870
}
6971

72+
@Deprecated("Please use independentWhere to create a standalone where clause, then pass it to the where method")
7073
fun applyWhere(whereApplier: WhereApplier) = whereApplier.invoke(this)
7174

7275
/**

src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/elements/SqlElements.kt

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -76,19 +76,36 @@ import org.mybatis.dynamic.sql.where.condition.IsNotLikeCaseInsensitive
7676
import org.mybatis.dynamic.sql.where.condition.IsNotNull
7777
import org.mybatis.dynamic.sql.where.condition.IsNull
7878

79+
/**
80+
* No-Op function for code beautification. This allows creation of an independent where clause
81+
* that can be reused in different statements. For example:
82+
*
83+
* val independentWhereClause = independentWhere { id isEqualTo 3 }
84+
*
85+
* val rows = countFrom(foo) {
86+
* where(independentWhereClause)
87+
* }
88+
*
89+
* Use of this function is optional. You can also write code like this:
90+
*
91+
* val independentWhereClause: GroupingCriteriaReceiver = { id isEqualTo 3 }
92+
*
93+
*/
94+
fun independentWhere(receiver: GroupingCriteriaReceiver): GroupingCriteriaReceiver = receiver
95+
7996
// support for criteria without initial conditions
8097
fun and(receiver: GroupingCriteriaReceiver): AndOrCriteriaGroup =
8198
with(GroupingCriteriaCollector().apply(receiver)) {
82-
AndOrCriteriaGroup.Builder().withInitialCriterion(this.initialCriterion)
83-
.withSubCriteria(this.subCriteria)
99+
AndOrCriteriaGroup.Builder().withInitialCriterion(initialCriterion)
100+
.withSubCriteria(subCriteria)
84101
.withConnector("and")
85102
.build()
86103
}
87104

88105
fun or(receiver: GroupingCriteriaReceiver): AndOrCriteriaGroup =
89106
with(GroupingCriteriaCollector().apply(receiver)) {
90-
AndOrCriteriaGroup.Builder().withInitialCriterion(this.initialCriterion)
91-
.withSubCriteria(this.subCriteria)
107+
AndOrCriteriaGroup.Builder().withInitialCriterion(initialCriterion)
108+
.withSubCriteria(subCriteria)
92109
.withConnector("or")
93110
.build()
94111
}

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

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import org.junit.jupiter.api.Test
2828
import org.junit.jupiter.api.TestInstance
2929
import org.mybatis.dynamic.sql.util.kotlin.WhereApplier
3030
import org.mybatis.dynamic.sql.util.kotlin.andThen
31+
import org.mybatis.dynamic.sql.util.kotlin.elements.independentWhere
3132
import org.mybatis.dynamic.sql.util.kotlin.mybatis3.select
3233

3334
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@@ -98,16 +99,16 @@ class ReusableWhereTest {
9899
}
99100

100101
@Test
101-
fun testComposition() {
102-
val whereApplier = commonWhere.andThen {
102+
fun testDeprecatedComposition() {
103+
val composedWhereClause = commonWhere.andThen {
103104
and { birthDate.isNotNull() }
104105
}.andThen {
105106
or { addressId isLessThan 3 }
106107
}
107108

108109
val selectStatement = select(person.allColumns()) {
109110
from(person)
110-
applyWhere(whereApplier)
111+
applyWhere(composedWhereClause)
111112
}
112113

113114
assertThat(selectStatement.selectStatement).isEqualTo(
@@ -118,8 +119,34 @@ class ReusableWhereTest {
118119
)
119120
}
120121

122+
@Test
123+
fun testComposition() {
124+
val composedWhereClause = commonWhereClause.andThen {
125+
and { birthDate.isNotNull() }
126+
}.andThen {
127+
or { addressId isLessThan 3 }
128+
}
129+
130+
val selectStatement = select(person.allColumns()) {
131+
from(person)
132+
where(composedWhereClause)
133+
}
134+
135+
assertThat(selectStatement.selectStatement).isEqualTo(
136+
"select * from Person " +
137+
"where id = #{parameters.p1,jdbcType=INTEGER} or occupation is null " +
138+
"and birth_date is not null " +
139+
"or address_id < #{parameters.p2,jdbcType=INTEGER}"
140+
)
141+
}
142+
121143
private val commonWhere: WhereApplier = {
122144
where { id isEqualTo 1 }
123145
or { occupation.isNull() }
124146
}
147+
148+
private val commonWhereClause = independentWhere {
149+
id isEqualTo 1
150+
or { occupation.isNull() }
151+
}
125152
}

0 commit comments

Comments
 (0)