Skip to content

Commit 28ba6f3

Browse files
authored
Merge pull request #794 from jeffgbutler/new-in-rendering
Change "in" Condition Rendering - Render With Empty Lists
2 parents dc169f2 + f5a5318 commit 28ba6f3

22 files changed

+953
-206
lines changed

CHANGELOG.md

+13-5
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,17 @@ This log will detail notable changes to MyBatis Dynamic SQL. Full details are av
44

55
## Release 1.5.2 - Unreleased
66

7-
This is a small maintenance release with improvements to the Kotlin DSL for CASE expressions. We worked on this soon
8-
after the 1.5.1 release, so wanted to get it out quickly.
9-
See this PR for details: ([#785](https://github.com/mybatis/mybatis-dynamic-sql/pull/785))
7+
This is a small maintenance release with the following changes:
8+
9+
1. Improvements to the Kotlin DSL for CASE expressions (infix methods for "else" and "then"). See this PR for
10+
details: ([#785](https://github.com/mybatis/mybatis-dynamic-sql/pull/785))
11+
2. **Potentially Breaking Change**: the "in" conditions ("isIn", "isNotIn", "isInCaseInsensitive",
12+
"isNotInCaseInsensitive") will now render if the input list of values is empty. This will lead
13+
to a runtime exception. This change was made out of an abundance of caution and is the safest choice.
14+
If you wish to allow "in" conditions to be removed from where clauses when the list is empty,
15+
then use the "when present" versions of those conditions. If you are unsure how this works, please
16+
read the documentation here: https://mybatis.org/mybatis-dynamic-sql/docs/conditions.html#optionality-with-the-%E2%80%9Cin%E2%80%9D-conditions
17+
For background on the reason for the change, see the discussion here: https://github.com/mybatis/mybatis-dynamic-sql/issues/788
1018

1119
GitHub milestone: [https://github.com/mybatis/mybatis-dynamic-sql/milestone/14?closed=1](https://github.com/mybatis/mybatis-dynamic-sql/milestone/14?closed=1)
1220

@@ -34,8 +42,8 @@ We've tested this extensively and the code is, of course, 100% covered by test c
3442
covered every scenario. Please let us know if you find issues.
3543

3644
Full documentation is available here:
37-
- [Java Case Expression DSL Documentation](caseExpressions.md)
38-
- [Kotlin Case Expression DSL Documentation](kotlinCaseExpressions.md)
45+
- [Java Case Expression DSL Documentation](https://mybatis.org/mybatis-dynamic-sql/docs/caseExpressions.html)
46+
- [Kotlin Case Expression DSL Documentation](https://mybatis.org/mybatis-dynamic-sql/docs/kotlinCaseExpressions.html)
3947

4048
The pull request for this change is ([#761](https://github.com/mybatis/mybatis-dynamic-sql/pull/761))
4149

src/main/java/org/mybatis/dynamic/sql/AbstractListValueCondition.java

+4-17
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,6 @@
2323
import java.util.stream.Collectors;
2424
import java.util.stream.Stream;
2525

26-
import org.mybatis.dynamic.sql.render.RenderingContext;
27-
2826
public abstract class AbstractListValueCondition<T> implements VisitableCondition<T> {
2927
protected final Collection<T> values;
3028

@@ -41,15 +39,6 @@ public boolean isEmpty() {
4139
return values.isEmpty();
4240
}
4341

44-
@Override
45-
public boolean shouldRender(RenderingContext renderingContext) {
46-
if (isEmpty()) {
47-
return renderingContext.isEmptyListConditionRenderingAllowed();
48-
} else {
49-
return true;
50-
}
51-
}
52-
5342
@Override
5443
public <R> R accept(ConditionVisitor<T, R> visitor) {
5544
return visitor.visit(this);
@@ -85,14 +74,12 @@ protected <R, S extends AbstractListValueCondition<R>> S mapSupport(Function<? s
8574
}
8675

8776
/**
88-
* If renderable, apply the predicate to each value in the list and return a new condition with the filtered values.
89-
* Else returns a condition that will not render (this). If all values are filtered out of the value
90-
* list, then the condition will not render.
77+
* If not empty, apply the predicate to each value in the list and return a new condition with the filtered values.
78+
* Else returns an empty condition (this).
9179
*
92-
* @param predicate
93-
* predicate applied to the values, if renderable
80+
* @param predicate predicate applied to the values, if not empty
9481
*
95-
* @return a new condition with filtered values if renderable, otherwise a condition that will not render.
82+
* @return a new condition with filtered values if renderable, otherwise an empty condition
9683
*/
9784
public abstract AbstractListValueCondition<T> filter(Predicate<? super T> predicate);
9885

src/main/java/org/mybatis/dynamic/sql/SqlBuilder.java

+21-17
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,8 @@
7676
import org.mybatis.dynamic.sql.where.condition.IsGreaterThanWithSubselect;
7777
import org.mybatis.dynamic.sql.where.condition.IsIn;
7878
import org.mybatis.dynamic.sql.where.condition.IsInCaseInsensitive;
79+
import org.mybatis.dynamic.sql.where.condition.IsInCaseInsensitiveWhenPresent;
80+
import org.mybatis.dynamic.sql.where.condition.IsInWhenPresent;
7981
import org.mybatis.dynamic.sql.where.condition.IsInWithSubselect;
8082
import org.mybatis.dynamic.sql.where.condition.IsLessThan;
8183
import org.mybatis.dynamic.sql.where.condition.IsLessThanColumn;
@@ -91,6 +93,8 @@
9193
import org.mybatis.dynamic.sql.where.condition.IsNotEqualToWithSubselect;
9294
import org.mybatis.dynamic.sql.where.condition.IsNotIn;
9395
import org.mybatis.dynamic.sql.where.condition.IsNotInCaseInsensitive;
96+
import org.mybatis.dynamic.sql.where.condition.IsNotInCaseInsensitiveWhenPresent;
97+
import org.mybatis.dynamic.sql.where.condition.IsNotInWhenPresent;
9498
import org.mybatis.dynamic.sql.where.condition.IsNotInWithSubselect;
9599
import org.mybatis.dynamic.sql.where.condition.IsNotLike;
96100
import org.mybatis.dynamic.sql.where.condition.IsNotLikeCaseInsensitive;
@@ -764,12 +768,12 @@ static <T> IsInWithSubselect<T> isIn(Buildable<SelectModel> selectModelBuilder)
764768
}
765769

766770
@SafeVarargs
767-
static <T> IsIn<T> isInWhenPresent(T... values) {
768-
return IsIn.of(values).filter(Objects::nonNull);
771+
static <T> IsInWhenPresent<T> isInWhenPresent(T... values) {
772+
return IsInWhenPresent.of(values);
769773
}
770774

771-
static <T> IsIn<T> isInWhenPresent(Collection<T> values) {
772-
return values == null ? IsIn.empty() : IsIn.of(values).filter(Objects::nonNull);
775+
static <T> IsInWhenPresent<T> isInWhenPresent(Collection<T> values) {
776+
return values == null ? IsInWhenPresent.empty() : IsInWhenPresent.of(values);
773777
}
774778

775779
@SafeVarargs
@@ -786,12 +790,12 @@ static <T> IsNotInWithSubselect<T> isNotIn(Buildable<SelectModel> selectModelBui
786790
}
787791

788792
@SafeVarargs
789-
static <T> IsNotIn<T> isNotInWhenPresent(T... values) {
790-
return IsNotIn.of(values).filter(Objects::nonNull);
793+
static <T> IsNotInWhenPresent<T> isNotInWhenPresent(T... values) {
794+
return IsNotInWhenPresent.of(values);
791795
}
792796

793-
static <T> IsNotIn<T> isNotInWhenPresent(Collection<T> values) {
794-
return values == null ? IsNotIn.empty() : IsNotIn.of(values).filter(Objects::nonNull);
797+
static <T> IsNotInWhenPresent<T> isNotInWhenPresent(Collection<T> values) {
798+
return values == null ? IsNotInWhenPresent.empty() : IsNotInWhenPresent.of(values);
795799
}
796800

797801
static <T> IsBetween.Builder<T> isBetween(T value1) {
@@ -909,12 +913,12 @@ static IsInCaseInsensitive isInCaseInsensitive(Collection<String> values) {
909913
return IsInCaseInsensitive.of(values);
910914
}
911915

912-
static IsInCaseInsensitive isInCaseInsensitiveWhenPresent(String... values) {
913-
return IsInCaseInsensitive.of(values).filter(Objects::nonNull);
916+
static IsInCaseInsensitiveWhenPresent isInCaseInsensitiveWhenPresent(String... values) {
917+
return IsInCaseInsensitiveWhenPresent.of(values);
914918
}
915919

916-
static IsInCaseInsensitive isInCaseInsensitiveWhenPresent(Collection<String> values) {
917-
return values == null ? IsInCaseInsensitive.empty() : IsInCaseInsensitive.of(values).filter(Objects::nonNull);
920+
static IsInCaseInsensitiveWhenPresent isInCaseInsensitiveWhenPresent(Collection<String> values) {
921+
return values == null ? IsInCaseInsensitiveWhenPresent.empty() : IsInCaseInsensitiveWhenPresent.of(values);
918922
}
919923

920924
static IsNotInCaseInsensitive isNotInCaseInsensitive(String... values) {
@@ -925,13 +929,13 @@ static IsNotInCaseInsensitive isNotInCaseInsensitive(Collection<String> values)
925929
return IsNotInCaseInsensitive.of(values);
926930
}
927931

928-
static IsNotInCaseInsensitive isNotInCaseInsensitiveWhenPresent(String... values) {
929-
return IsNotInCaseInsensitive.of(values).filter(Objects::nonNull);
932+
static IsNotInCaseInsensitiveWhenPresent isNotInCaseInsensitiveWhenPresent(String... values) {
933+
return IsNotInCaseInsensitiveWhenPresent.of(values);
930934
}
931935

932-
static IsNotInCaseInsensitive isNotInCaseInsensitiveWhenPresent(Collection<String> values) {
933-
return values == null ? IsNotInCaseInsensitive.empty() :
934-
IsNotInCaseInsensitive.of(values).filter(Objects::nonNull);
936+
static IsNotInCaseInsensitiveWhenPresent isNotInCaseInsensitiveWhenPresent(Collection<String> values) {
937+
return values == null ? IsNotInCaseInsensitiveWhenPresent.empty() :
938+
IsNotInCaseInsensitiveWhenPresent.of(values);
935939
}
936940

937941
// order by support

src/main/java/org/mybatis/dynamic/sql/configuration/GlobalConfiguration.java

-8
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ public class GlobalConfiguration {
2626
public static final String CONFIGURATION_FILE_PROPERTY = "mybatis-dynamic-sql.configurationFile"; //$NON-NLS-1$
2727
private static final String DEFAULT_PROPERTY_FILE = "mybatis-dynamic-sql.properties"; //$NON-NLS-1$
2828
private boolean isNonRenderingWhereClauseAllowed = false;
29-
private boolean isEmptyListConditionRenderingAllowed = false;
3029
private final Properties properties = new Properties();
3130

3231
public GlobalConfiguration() {
@@ -66,16 +65,9 @@ void loadProperties(InputStream inputStream, String propertyFile) {
6665
private void initializeKnownProperties() {
6766
String value = properties.getProperty("nonRenderingWhereClauseAllowed", "false"); //$NON-NLS-1$ //$NON-NLS-2$
6867
isNonRenderingWhereClauseAllowed = Boolean.parseBoolean(value);
69-
70-
value = properties.getProperty("emptyListConditionRenderingAllowed", "false"); //$NON-NLS-1$ //$NON-NLS-2$
71-
isEmptyListConditionRenderingAllowed = Boolean.parseBoolean(value);
7268
}
7369

7470
public boolean isIsNonRenderingWhereClauseAllowed() {
7571
return isNonRenderingWhereClauseAllowed;
7672
}
77-
78-
public boolean isEmptyListConditionRenderingAllowed() {
79-
return isEmptyListConditionRenderingAllowed;
80-
}
8173
}

src/main/java/org/mybatis/dynamic/sql/configuration/StatementConfiguration.java

-19
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,6 @@
2525
* Configurable behaviors are detailed below:
2626
*
2727
* <dl>
28-
* <dt>emptyListConditionRenderingAllowed</dt>
29-
* <dd>If false (default), the framework will not render list conditions that are empty in a where clause.
30-
* This is beneficial in that it will not allow the library to generate invalid SQL, but it has a
31-
* potentially dangerous side effect where a statement could be generated that impacts more rows
32-
* then expected. If true, an empty list will be rendered as "in ()", "not in ()", etc. which will likely
33-
* cause an SQLException at runtime.
34-
* </dd>
3528
* <dt>nonRenderingWhereClauseAllowed</dt>
3629
* <dd>If false (default), the framework will throw a {@link NonRenderingWhereClauseException}
3730
* if a where clause is specified in the statement, but it fails to render because all
@@ -51,9 +44,6 @@ public class StatementConfiguration {
5144
private boolean isNonRenderingWhereClauseAllowed =
5245
GlobalContext.getConfiguration().isIsNonRenderingWhereClauseAllowed();
5346

54-
private boolean isEmptyListConditionRenderingAllowed =
55-
GlobalContext.getConfiguration().isEmptyListConditionRenderingAllowed();
56-
5747
public boolean isNonRenderingWhereClauseAllowed() {
5848
return isNonRenderingWhereClauseAllowed;
5949
}
@@ -62,13 +52,4 @@ public StatementConfiguration setNonRenderingWhereClauseAllowed(boolean nonRende
6252
isNonRenderingWhereClauseAllowed = nonRenderingWhereClauseAllowed;
6353
return this;
6454
}
65-
66-
public boolean isEmptyListConditionRenderingAllowed() {
67-
return isEmptyListConditionRenderingAllowed;
68-
}
69-
70-
public StatementConfiguration setEmptyListConditionRenderingAllowed(boolean emptyListConditionRenderingAllowed) {
71-
isEmptyListConditionRenderingAllowed = emptyListConditionRenderingAllowed;
72-
return this;
73-
}
7455
}

src/main/java/org/mybatis/dynamic/sql/render/RenderingContext.java

-4
Original file line numberDiff line numberDiff line change
@@ -101,10 +101,6 @@ public boolean isNonRenderingClauseAllowed() {
101101
return statementConfiguration.isNonRenderingWhereClauseAllowed();
102102
}
103103

104-
public boolean isEmptyListConditionRenderingAllowed() {
105-
return statementConfiguration.isEmptyListConditionRenderingAllowed();
106-
}
107-
108104
/**
109105
* Create a new rendering context based on this, with the table alias calculator modified to include the
110106
* specified child table alias calculator. This is used by the query expression renderer when the alias calculator

src/main/java/org/mybatis/dynamic/sql/where/condition/IsIn.java

+10-5
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import java.util.function.Predicate;
2323

2424
import org.mybatis.dynamic.sql.AbstractListValueCondition;
25+
import org.mybatis.dynamic.sql.render.RenderingContext;
2526

2627
public class IsIn<T> extends AbstractListValueCondition<T> {
2728
private static final IsIn<?> EMPTY = new IsIn<>(Collections.emptyList());
@@ -36,6 +37,11 @@ protected IsIn(Collection<T> values) {
3637
super(values);
3738
}
3839

40+
@Override
41+
public boolean shouldRender(RenderingContext renderingContext) {
42+
return true;
43+
}
44+
3945
@Override
4046
public String operator() {
4147
return "in"; //$NON-NLS-1$
@@ -47,13 +53,12 @@ public IsIn<T> filter(Predicate<? super T> predicate) {
4753
}
4854

4955
/**
50-
* If renderable, apply the mapping to each value in the list return a new condition with the mapped values.
51-
* Else return a condition that will not render (this).
56+
* If not empty, apply the mapping to each value in the list return a new condition with the mapped values.
57+
* Else return an empty condition (this).
5258
*
53-
* @param mapper a mapping function to apply to the values, if renderable
59+
* @param mapper a mapping function to apply to the values, if not empty
5460
* @param <R> type of the new condition
55-
* @return a new condition with mapped values if renderable, otherwise a condition
56-
* that will not render.
61+
* @return a new condition with mapped values if renderable, otherwise an empty condition
5762
*/
5863
public <R> IsIn<R> map(Function<? super T, ? extends R> mapper) {
5964
Function<Collection<R>, IsIn<R>> constructor = IsIn::new;

src/main/java/org/mybatis/dynamic/sql/where/condition/IsInCaseInsensitive.java

+10-5
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import java.util.function.UnaryOperator;
2323

2424
import org.mybatis.dynamic.sql.AbstractListValueCondition;
25+
import org.mybatis.dynamic.sql.render.RenderingContext;
2526
import org.mybatis.dynamic.sql.util.StringUtilities;
2627

2728
public class IsInCaseInsensitive extends AbstractListValueCondition<String>
@@ -36,6 +37,11 @@ protected IsInCaseInsensitive(Collection<String> values) {
3637
super(values);
3738
}
3839

40+
@Override
41+
public boolean shouldRender(RenderingContext renderingContext) {
42+
return true;
43+
}
44+
3945
@Override
4046
public String operator() {
4147
return "in"; //$NON-NLS-1$
@@ -47,12 +53,11 @@ public IsInCaseInsensitive filter(Predicate<? super String> predicate) {
4753
}
4854

4955
/**
50-
* If renderable, apply the mapping to each value in the list return a new condition with the mapped values.
51-
* Else return a condition that will not render (this).
56+
* If not empty, apply the mapping to each value in the list return a new condition with the mapped values.
57+
* Else return an empty condition (this).
5258
*
53-
* @param mapper a mapping function to apply to the values, if renderable
54-
* @return a new condition with mapped values if renderable, otherwise a condition
55-
* that will not render.
59+
* @param mapper a mapping function to apply to the values, if not empty
60+
* @return a new condition with mapped values if renderable, otherwise an empty condition
5661
*/
5762
public IsInCaseInsensitive map(UnaryOperator<String> mapper) {
5863
return mapSupport(mapper, IsInCaseInsensitive::new, IsInCaseInsensitive::empty);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
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.where.condition;
17+
18+
import java.util.Arrays;
19+
import java.util.Collection;
20+
import java.util.Collections;
21+
import java.util.Objects;
22+
import java.util.function.Predicate;
23+
import java.util.function.UnaryOperator;
24+
import java.util.stream.Collectors;
25+
26+
import org.mybatis.dynamic.sql.AbstractListValueCondition;
27+
import org.mybatis.dynamic.sql.util.StringUtilities;
28+
29+
public class IsInCaseInsensitiveWhenPresent extends AbstractListValueCondition<String>
30+
implements CaseInsensitiveVisitableCondition {
31+
private static final IsInCaseInsensitiveWhenPresent EMPTY = new IsInCaseInsensitiveWhenPresent(Collections.emptyList());
32+
33+
public static IsInCaseInsensitiveWhenPresent empty() {
34+
return EMPTY;
35+
}
36+
37+
protected IsInCaseInsensitiveWhenPresent(Collection<String> values) {
38+
super(values.stream().filter(Objects::nonNull).collect(Collectors.toList()));
39+
}
40+
41+
@Override
42+
public String operator() {
43+
return "in"; //$NON-NLS-1$
44+
}
45+
46+
@Override
47+
public IsInCaseInsensitiveWhenPresent filter(Predicate<? super String> predicate) {
48+
return filterSupport(predicate, IsInCaseInsensitiveWhenPresent::new, this, IsInCaseInsensitiveWhenPresent::empty);
49+
}
50+
51+
/**
52+
* If not empty, apply the mapping to each value in the list return a new condition with the mapped values.
53+
* Else return an empty condition (this).
54+
*
55+
* @param mapper a mapping function to apply to the values, if not empty
56+
* @return a new condition with mapped values if renderable, otherwise an empty condition
57+
*/
58+
public IsInCaseInsensitiveWhenPresent map(UnaryOperator<String> mapper) {
59+
return mapSupport(mapper, IsInCaseInsensitiveWhenPresent::new, IsInCaseInsensitiveWhenPresent::empty);
60+
}
61+
62+
public static IsInCaseInsensitiveWhenPresent of(String... values) {
63+
return of(Arrays.asList(values));
64+
}
65+
66+
public static IsInCaseInsensitiveWhenPresent of(Collection<String> values) {
67+
return new IsInCaseInsensitiveWhenPresent(values).map(StringUtilities::safelyUpperCase);
68+
}
69+
}

0 commit comments

Comments
 (0)