Skip to content

Commit faa811d

Browse files
committed
Kotlin support for case expressions
1 parent 0abc3fa commit faa811d

File tree

9 files changed

+629
-19
lines changed

9 files changed

+629
-19
lines changed

src/main/java/org/mybatis/dynamic/sql/select/SearchedCaseDSL.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ private WhenDSL initialize(SqlCriterion sqlCriterion) {
6464
return new WhenDSL(sqlCriterion);
6565
}
6666

67-
public SearchedCaseEnder elseConstant(String elseValue) {
67+
public SearchedCaseEnder else_(String elseValue) {
6868
this.elseValue = elseValue;
6969
return new SearchedCaseEnder();
7070
}
@@ -81,7 +81,7 @@ private WhenDSL(SqlCriterion sqlCriterion) {
8181
setInitialCriterion(sqlCriterion);
8282
}
8383

84-
public SearchedCaseDSL thenConstant(String value) {
84+
public SearchedCaseDSL then(String value) {
8585
whenConditions.add(new SearchedCaseModel.SearchedWhenCondition(getInitialCriterion(), subCriteria, value));
8686
return SearchedCaseDSL.this;
8787
}

src/main/java/org/mybatis/dynamic/sql/select/SimpleCaseDSL.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ public WhenFinisher when(VisitableCondition<T> condition,
4444
return new WhenFinisher(condition, subsequentConditions);
4545
}
4646

47-
public SimpleCaseEnder elseConstant(String value) {
47+
public SimpleCaseEnder else_(String value) {
4848
elseValue = value;
4949
return new SimpleCaseEnder();
5050
}
@@ -65,7 +65,7 @@ private WhenFinisher(VisitableCondition<T> condition, List<VisitableCondition<T>
6565
conditions.addAll(subsequentConditions);
6666
}
6767

68-
public SimpleCaseDSL<T> thenConstant(String value) {
68+
public SimpleCaseDSL<T> then(String value) {
6969
whenConditions.add(new SimpleCaseModel.SimpleWhenCondition<>(conditions, value));
7070
return SimpleCaseDSL.this;
7171
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ sealed class SubCriteriaCollector {
127127
*/
128128
@Suppress("TooManyFunctions")
129129
@MyBatisDslMarker
130-
class GroupingCriteriaCollector : SubCriteriaCollector() {
130+
open class GroupingCriteriaCollector : SubCriteriaCollector() {
131131
internal var initialCriterion: SqlCriterion? = null
132132
private set(value) {
133133
assertNull(field, "ERROR.21") //$NON-NLS-1$
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/*
2+
* Copyright 2016-2024 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+
* https://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.elements
17+
18+
import org.mybatis.dynamic.sql.VisitableCondition
19+
import org.mybatis.dynamic.sql.select.SearchedCaseModel.SearchedWhenCondition
20+
import org.mybatis.dynamic.sql.select.SimpleCaseModel.SimpleWhenCondition
21+
import org.mybatis.dynamic.sql.util.kotlin.GroupingCriteriaCollector
22+
import org.mybatis.dynamic.sql.util.kotlin.assertNull
23+
24+
class KSearchedCaseDSL {
25+
internal var elseValue: String? = null
26+
private set(value) {
27+
assertNull(field, "ERROR.42") //$NON-NLS-1$
28+
field = value
29+
}
30+
internal val whenConditions = mutableListOf<SearchedWhenCondition>()
31+
32+
fun `when`(dslCompleter: SearchedCaseCriteriaCollector.() -> Unit) {
33+
val dsl = SearchedCaseCriteriaCollector().apply(dslCompleter)
34+
whenConditions.add(SearchedWhenCondition(dsl.initialCriterion, dsl.subCriteria, dsl.thenValue))
35+
}
36+
37+
fun `else`(value: String) {
38+
this.elseValue = value
39+
}
40+
}
41+
42+
class SearchedCaseCriteriaCollector : GroupingCriteriaCollector() {
43+
internal var thenValue: String? = null
44+
private set(value) {
45+
assertNull(field, "ERROR.41") //$NON-NLS-1$
46+
field = value
47+
}
48+
49+
fun then(value: String) {
50+
this.thenValue = value
51+
}
52+
}
53+
54+
class KSimpleCaseDSL<T : Any> {
55+
internal var elseValue: String? = null
56+
private set(value) {
57+
assertNull(field, "ERROR.42") //$NON-NLS-1$
58+
field = value
59+
}
60+
internal val whenConditions = mutableListOf<SimpleWhenCondition<T>>()
61+
62+
fun `when`(condition: VisitableCondition<T>, vararg conditions: VisitableCondition<T>) =
63+
SimpleCaseThenGatherer(condition, conditions.asList())
64+
65+
fun `else`(value: String) {
66+
this.elseValue = value
67+
}
68+
69+
inner class SimpleCaseThenGatherer(val condition: VisitableCondition<T>,
70+
val conditions: List<VisitableCondition<T>>) {
71+
fun then(value: String) {
72+
val allConditions = buildList {
73+
add(condition)
74+
addAll(conditions)
75+
}
76+
77+
whenConditions.add(SimpleWhenCondition(allConditions, value))
78+
}
79+
}
80+
}

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ import org.mybatis.dynamic.sql.SqlBuilder
2626
import org.mybatis.dynamic.sql.SqlColumn
2727
import org.mybatis.dynamic.sql.StringConstant
2828
import org.mybatis.dynamic.sql.VisitableCondition
29+
import org.mybatis.dynamic.sql.select.SearchedCaseModel
30+
import org.mybatis.dynamic.sql.select.SimpleCaseModel
2931
import org.mybatis.dynamic.sql.select.aggregate.Avg
3032
import org.mybatis.dynamic.sql.select.aggregate.Count
3133
import org.mybatis.dynamic.sql.select.aggregate.CountAll
@@ -96,6 +98,24 @@ fun or(receiver: GroupingCriteriaReceiver): AndOrCriteriaGroup =
9698
.build()
9799
}
98100

101+
// case expressions
102+
fun case(dslCompleter: KSearchedCaseDSL.() -> Unit): BasicColumn =
103+
KSearchedCaseDSL().apply(dslCompleter).run {
104+
SearchedCaseModel.Builder()
105+
.withWhenConditions(whenConditions)
106+
.withElseValue(elseValue)
107+
.build()
108+
}
109+
110+
fun <T : Any> case(column: BindableColumn<T>, dslCompleter: KSimpleCaseDSL<T>.() -> Unit) : BasicColumn =
111+
KSimpleCaseDSL<T>().apply(dslCompleter).run {
112+
SimpleCaseModel.Builder<T>()
113+
.withColumn(column)
114+
.withWhenConditions(whenConditions)
115+
.withElseValue(elseValue)
116+
.build()
117+
}
118+
99119
// aggregate support
100120
fun count(): CountAll = SqlBuilder.count()
101121

src/main/resources/org/mybatis/dynamic/sql/util/messages.properties

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,4 +57,6 @@ ERROR.37=The "{0}" function does not support conditions that fail to render
5757
ERROR.38=Bound values cannot be aliased
5858
ERROR.39=When clauses in case expressions must render (optional conditions are not supported)
5959
ERROR.40=Case expressions must have at least one "when" clause
60+
ERROR.41=You cannot call "then" in a Kotlin case expression more than once
61+
ERROR.42=You cannot call `else` in a Kotlin case expression more than once
6062
INTERNAL.ERROR=Internal Error {0}

src/test/java/examples/animal/data/CaseExpressionTest.java

Lines changed: 43 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import static examples.animal.data.AnimalDataDynamicSqlSupport.animalData;
1919
import static examples.animal.data.AnimalDataDynamicSqlSupport.animalName;
20+
import static examples.animal.data.AnimalDataDynamicSqlSupport.brainWeight;
2021
import static examples.animal.data.AnimalDataDynamicSqlSupport.id;
2122
import static org.assertj.core.api.Assertions.assertThat;
2223
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
@@ -26,6 +27,7 @@
2627
import static org.mybatis.dynamic.sql.SqlBuilder.isEqualTo;
2728
import static org.mybatis.dynamic.sql.SqlBuilder.isEqualToWhenPresent;
2829
import static org.mybatis.dynamic.sql.SqlBuilder.isIn;
30+
import static org.mybatis.dynamic.sql.SqlBuilder.isLessThan;
2931
import static org.mybatis.dynamic.sql.SqlBuilder.or;
3032
import static org.mybatis.dynamic.sql.SqlBuilder.searchedCase;
3133
import static org.mybatis.dynamic.sql.SqlBuilder.select;
@@ -85,9 +87,9 @@ void testSearchedCase() {
8587
CommonSelectMapper mapper = sqlSession.getMapper(CommonSelectMapper.class);
8688

8789
SelectStatementProvider selectStatement = select(animalName, searchedCase()
88-
.when(animalName, isEqualTo("Artic fox")).or(animalName, isEqualTo("Red fox")).thenConstant("'Fox'")
89-
.when(animalName, isEqualTo("Little brown bat")).or(animalName, isEqualTo("Big brown bat")).thenConstant("'Bat'")
90-
.elseConstant("cast('Not a Fox or a bat' as varchar(25))").end().as("AnimalType"))
90+
.when(animalName, isEqualTo("Artic fox")).or(animalName, isEqualTo("Red fox")).then("'Fox'")
91+
.when(animalName, isEqualTo("Little brown bat")).or(animalName, isEqualTo("Big brown bat")).then("'Bat'")
92+
.else_("cast('Not a Fox or a bat' as varchar(25))").end().as("AnimalType"))
9193
.from(animalData, "a")
9294
.where(id, isIn(2, 3, 31, 32, 38, 39))
9395
.orderBy(id)
@@ -134,8 +136,8 @@ void testSearchedCaseNoElse() {
134136
CommonSelectMapper mapper = sqlSession.getMapper(CommonSelectMapper.class);
135137

136138
SelectStatementProvider selectStatement = select(animalName, searchedCase()
137-
.when(animalName, isEqualTo("Artic fox")).or(animalName, isEqualTo("Red fox")).thenConstant("'Fox'")
138-
.when(animalName, isEqualTo("Little brown bat")).or(animalName, isEqualTo("Big brown bat")).thenConstant("'Bat'")
139+
.when(animalName, isEqualTo("Artic fox")).or(animalName, isEqualTo("Red fox")).then("'Fox'")
140+
.when(animalName, isEqualTo("Little brown bat")).or(animalName, isEqualTo("Big brown bat")).then("'Bat'")
139141
.end().as("AnimalType"))
140142
.from(animalData, "a")
141143
.where(id, isIn(2, 3, 31, 32, 38, 39))
@@ -183,10 +185,10 @@ void testSearchedCaseWithGroup() {
183185
CommonSelectMapper mapper = sqlSession.getMapper(CommonSelectMapper.class);
184186

185187
SelectStatementProvider selectStatement = select(animalName, searchedCase()
186-
.when(animalName, isEqualTo("Artic fox")).or(animalName, isEqualTo("Red fox")).thenConstant("'Fox'")
187-
.when(animalName, isEqualTo("Little brown bat")).or(animalName, isEqualTo("Big brown bat")).thenConstant("'Bat'")
188-
.when(group(animalName, isEqualTo("Cat"), and(id, isEqualTo(31))), or(id, isEqualTo(39))).thenConstant("'Fred'")
189-
.elseConstant("cast('Not a Fox or a bat' as varchar(25))").end().as("AnimalType"))
188+
.when(animalName, isEqualTo("Artic fox")).or(animalName, isEqualTo("Red fox")).then("'Fox'")
189+
.when(animalName, isEqualTo("Little brown bat")).or(animalName, isEqualTo("Big brown bat")).then("'Bat'")
190+
.when(group(animalName, isEqualTo("Cat"), and(id, isEqualTo(31))), or(id, isEqualTo(39))).then("'Fred'")
191+
.else_("cast('Not a Fox or a bat' as varchar(25))").end().as("AnimalType"))
190192
.from(animalData, "a")
191193
.where(id, isIn(2, 3, 4, 31, 32, 38, 39))
192194
.orderBy(id)
@@ -233,14 +235,41 @@ void testSearchedCaseWithGroup() {
233235
}
234236
}
235237

238+
@Test
239+
void testSimpleCassLessThan() {
240+
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
241+
CommonSelectMapper mapper = sqlSession.getMapper(CommonSelectMapper.class);
242+
243+
SelectStatementProvider selectStatement = select(animalName, simpleCase(brainWeight)
244+
.when(isLessThan(4.0)).then("'small brain'")
245+
.else_("'large brain'").end().as("brain_size"))
246+
.from(animalData)
247+
.where(id, isIn(31, 32, 38, 39))
248+
.orderBy(id)
249+
.build()
250+
.render(RenderingStrategies.MYBATIS3);
251+
252+
String expected = "select animal_name, case brain_weight " +
253+
"when < #{parameters.p1,jdbcType=DOUBLE} then 'small brain' " +
254+
"else 'large brain' end as brain_size " +
255+
"from AnimalData where id in (" +
256+
"#{parameters.p2,jdbcType=INTEGER},#{parameters.p3,jdbcType=INTEGER}," +
257+
"#{parameters.p4,jdbcType=INTEGER},#{parameters.p5,jdbcType=INTEGER}) " +
258+
"order by id";
259+
assertThat(selectStatement.getSelectStatement()).isEqualTo(expected);
260+
List<Map<String, Object>> records = mapper.selectManyMappedRows(selectStatement);
261+
assertThat(records).hasSize(7);
262+
}
263+
}
264+
236265
@Test
237266
void testSimpleCase() {
238267
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
239268
CommonSelectMapper mapper = sqlSession.getMapper(CommonSelectMapper.class);
240269

241270
SelectStatementProvider selectStatement = select(animalName, simpleCase(animalName)
242-
.when(isEqualTo("Artic fox"), isEqualTo("Red fox")).thenConstant("'yes'")
243-
.elseConstant("cast('no' as VARCHAR(3))").end().as("IsAFox"))
271+
.when(isEqualTo("Artic fox"), isEqualTo("Red fox")).then("'yes'")
272+
.else_("cast('no' as VARCHAR(3))").end().as("IsAFox"))
244273
.from(animalData)
245274
.where(id, isIn(31, 32, 38, 39))
246275
.orderBy(id)
@@ -277,7 +306,7 @@ void testSimpleCaseNoElse() {
277306
CommonSelectMapper mapper = sqlSession.getMapper(CommonSelectMapper.class);
278307

279308
SelectStatementProvider selectStatement = select(animalName, simpleCase(animalName)
280-
.when(isEqualTo("Artic fox"), isEqualTo("Red fox")).thenConstant("'yes'")
309+
.when(isEqualTo("Artic fox"), isEqualTo("Red fox")).then("'yes'")
281310
.end().as("IsAFox"))
282311
.from(animalData)
283312
.where(id, isIn(31, 32, 38, 39))
@@ -312,7 +341,7 @@ void testSimpleCaseNoElse() {
312341
@Test
313342
void testInvalidSearchedCaseNoConditionsRender() {
314343
SelectModel model = select(animalName, searchedCase()
315-
.when(animalName, isEqualToWhenPresent((String) null)).thenConstant("Fred").end())
344+
.when(animalName, isEqualToWhenPresent((String) null)).then("Fred").end())
316345
.from(animalData)
317346
.build();
318347

@@ -324,7 +353,7 @@ void testInvalidSearchedCaseNoConditionsRender() {
324353
@Test
325354
void testInvalidSimpleCaseNoConditionsRender() {
326355
SelectModel model = select(simpleCase(animalName)
327-
.when(isEqualToWhenPresent((String) null)).thenConstant("Fred").end())
356+
.when(isEqualToWhenPresent((String) null)).then("Fred").end())
328357
.from(animalData)
329358
.build();
330359

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* Copyright 2016-2024 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+
* https://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 examples.kotlin.animal.data
17+
18+
import java.sql.JDBCType
19+
20+
import org.mybatis.dynamic.sql.SqlTable
21+
import org.mybatis.dynamic.sql.util.kotlin.elements.column
22+
23+
object AnimalDataDynamicSqlSupport {
24+
val animalData = AnimalData()
25+
val id = animalData.id
26+
val animalName = animalData.animalName
27+
val bodyWeight = animalData.bodyWeight
28+
val brainWeight = animalData.brainWeight
29+
30+
class AnimalData : SqlTable("AnimalData") {
31+
val id = column<Int>(name = "id", jdbcType = JDBCType.INTEGER)
32+
val animalName = column<String>(name = "animal_name", jdbcType = JDBCType.VARCHAR)
33+
val bodyWeight = column<Double>(name = "body_weight", jdbcType = JDBCType.DOUBLE)
34+
val brainWeight = column<Double>(name = "brain_weight", jdbcType = JDBCType.DOUBLE)
35+
}
36+
}

0 commit comments

Comments
 (0)