Skip to content

Commit 6e6e225

Browse files
authored
Merge pull request #833 from jeffgbutler/empty-list-exception
Throw InvalidSqlException during rendering if an In condition is empty
2 parents 68cadc5 + f036631 commit 6e6e225

File tree

15 files changed

+94
-176
lines changed

15 files changed

+94
-176
lines changed

CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ Other important changes:
2626
- The library now requires Java 17
2727
- Deprecated code from prior releases is removed
2828
- We now allow CASE expressions in ORDER BY Clauses
29+
- The "In" conditions will now throw `InvalidSqlException` during rendering if the list of values is empty. Previously
30+
an empty In condition would render as invalid SQL and would usually cause a runtime exception from the database.
31+
With this change, the exception thrown is more predictable and the error is caught before sending the SQL to the
32+
database.
2933

3034
## Release 1.5.2 - June 3, 2024
3135

src/main/java/org/mybatis/dynamic/sql/exception/DuplicateTableAliasException.java

+2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
package org.mybatis.dynamic.sql.exception;
1717

18+
import java.io.Serial;
1819
import java.util.Objects;
1920

2021
import org.mybatis.dynamic.sql.SqlTable;
@@ -34,6 +35,7 @@
3435
*/
3536
public class DuplicateTableAliasException extends DynamicSqlException {
3637

38+
@Serial
3739
private static final long serialVersionUID = -2631664872557787391L;
3840

3941
public DuplicateTableAliasException(SqlTable table, String newAlias, String existingAlias) {

src/main/java/org/mybatis/dynamic/sql/exception/DynamicSqlException.java

+3
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,10 @@
1515
*/
1616
package org.mybatis.dynamic.sql.exception;
1717

18+
import java.io.Serial;
19+
1820
public class DynamicSqlException extends RuntimeException {
21+
@Serial
1922
private static final long serialVersionUID = 349021672061361244L;
2023

2124
public DynamicSqlException(String message) {

src/main/java/org/mybatis/dynamic/sql/exception/InvalidSqlException.java

+3
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,10 @@
1515
*/
1616
package org.mybatis.dynamic.sql.exception;
1717

18+
import java.io.Serial;
19+
1820
public class InvalidSqlException extends DynamicSqlException {
21+
@Serial
1922
private static final long serialVersionUID = 1666851020951347843L;
2023

2124
public InvalidSqlException(String message) {

src/main/java/org/mybatis/dynamic/sql/exception/NonRenderingWhereClauseException.java

+3
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
import org.mybatis.dynamic.sql.configuration.StatementConfiguration;
2020
import org.mybatis.dynamic.sql.util.Messages;
2121

22+
import java.io.Serial;
23+
2224
/**
2325
* This exception is thrown when the where clause in a statement will not render.
2426
* This can happen if all the optional conditions in a where clause fail to
@@ -40,6 +42,7 @@
4042
* @author Jeff Butler
4143
*/
4244
public class NonRenderingWhereClauseException extends DynamicSqlException {
45+
@Serial
4346
private static final long serialVersionUID = 6619119078542625135L;
4447

4548
public NonRenderingWhereClauseException() {

src/main/java/org/mybatis/dynamic/sql/util/Validator.java

+10-8
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,20 @@ public static void assertNotEmpty(Collection<?> collection, String messageNumber
2626
assertFalse(collection.isEmpty(), messageNumber);
2727
}
2828

29+
public static void assertNotEmpty(Collection<?> collection, String messageNumber, String p1) {
30+
assertFalse(collection.isEmpty(), messageNumber, p1);
31+
}
32+
2933
public static void assertFalse(boolean condition, String messageNumber) {
30-
internalAssertFalse(condition, Messages.getString(messageNumber));
34+
if (condition) {
35+
throw new InvalidSqlException(Messages.getString(messageNumber));
36+
}
3137
}
3238

3339
public static void assertFalse(boolean condition, String messageNumber, String p1) {
34-
internalAssertFalse(condition, Messages.getString(messageNumber, p1));
40+
if (condition) {
41+
throw new InvalidSqlException(Messages.getString(messageNumber, p1));
42+
}
3543
}
3644

3745
public static void assertTrue(boolean condition, String messageNumber) {
@@ -41,10 +49,4 @@ public static void assertTrue(boolean condition, String messageNumber) {
4149
public static void assertTrue(boolean condition, String messageNumber, String p1) {
4250
assertFalse(!condition, messageNumber, p1);
4351
}
44-
45-
private static void internalAssertFalse(boolean condition, String message) {
46-
if (condition) {
47-
throw new InvalidSqlException(message);
48-
}
49-
}
5052
}

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

+2
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323

2424
import org.mybatis.dynamic.sql.AbstractListValueCondition;
2525
import org.mybatis.dynamic.sql.render.RenderingContext;
26+
import org.mybatis.dynamic.sql.util.Validator;
2627

2728
public class IsIn<T> extends AbstractListValueCondition<T> {
2829
private static final IsIn<?> EMPTY = new IsIn<>(Collections.emptyList());
@@ -39,6 +40,7 @@ protected IsIn(Collection<T> values) {
3940

4041
@Override
4142
public boolean shouldRender(RenderingContext renderingContext) {
43+
Validator.assertNotEmpty(values, "ERROR.44", "IsIn"); //$NON-NLS-1$ //$NON-NLS-2$
4244
return true;
4345
}
4446

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

+2
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import org.mybatis.dynamic.sql.AbstractListValueCondition;
2525
import org.mybatis.dynamic.sql.render.RenderingContext;
2626
import org.mybatis.dynamic.sql.util.StringUtilities;
27+
import org.mybatis.dynamic.sql.util.Validator;
2728

2829
public class IsInCaseInsensitive extends AbstractListValueCondition<String>
2930
implements CaseInsensitiveVisitableCondition {
@@ -39,6 +40,7 @@ protected IsInCaseInsensitive(Collection<String> values) {
3940

4041
@Override
4142
public boolean shouldRender(RenderingContext renderingContext) {
43+
Validator.assertNotEmpty(values, "ERROR.44", "IsInCaseInsensitive"); //$NON-NLS-1$ //$NON-NLS-2$
4244
return true;
4345
}
4446

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

+2
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323

2424
import org.mybatis.dynamic.sql.AbstractListValueCondition;
2525
import org.mybatis.dynamic.sql.render.RenderingContext;
26+
import org.mybatis.dynamic.sql.util.Validator;
2627

2728
public class IsNotIn<T> extends AbstractListValueCondition<T> {
2829
private static final IsNotIn<?> EMPTY = new IsNotIn<>(Collections.emptyList());
@@ -39,6 +40,7 @@ protected IsNotIn(Collection<T> values) {
3940

4041
@Override
4142
public boolean shouldRender(RenderingContext renderingContext) {
43+
Validator.assertNotEmpty(values, "ERROR.44", "IsNotIn"); //$NON-NLS-1$ //$NON-NLS-2$
4244
return true;
4345
}
4446

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

+2
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import org.mybatis.dynamic.sql.AbstractListValueCondition;
2525
import org.mybatis.dynamic.sql.render.RenderingContext;
2626
import org.mybatis.dynamic.sql.util.StringUtilities;
27+
import org.mybatis.dynamic.sql.util.Validator;
2728

2829
public class IsNotInCaseInsensitive extends AbstractListValueCondition<String>
2930
implements CaseInsensitiveVisitableCondition {
@@ -39,6 +40,7 @@ protected IsNotInCaseInsensitive(Collection<String> values) {
3940

4041
@Override
4142
public boolean shouldRender(RenderingContext renderingContext) {
43+
Validator.assertNotEmpty(values, "ERROR.44", "IsNotInCaseInsensitive"); //$NON-NLS-1$ //$NON-NLS-2$
4244
return true;
4345
}
4446

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

+1
Original file line numberDiff line numberDiff line change
@@ -60,4 +60,5 @@ ERROR.40=Case expressions must have at least one "when" clause
6060
ERROR.41=You cannot call "then" in a Kotlin case expression more than once
6161
ERROR.42=You cannot call `else` in a Kotlin case expression more than once
6262
ERROR.43=A Kotlin cast expression must have one, and only one, `as` element
63+
ERROR.44={0} conditions must contain at least one value
6364
INTERNAL.ERROR=Internal Error {0}

src/site/markdown/docs/conditions.md

+4-10
Original file line numberDiff line numberDiff line change
@@ -204,8 +204,8 @@ mapping if you so desire.
204204

205205
Starting with version 1.5.2, we made a change to the rendering rules for the "in" conditions. This was done to limit the
206206
danger of conditions failing to render and thus affecting more rows than expected. For the base conditions ("isIn",
207-
"isNotIn", etc.), if the list of values is empty, then the condition will still render, but the resulting SQL will
208-
be invalid and will cause a runtime exception. We believe this is the safest outcome. For example, suppose
207+
"isNotIn", etc.), if the list of values is empty, then the library will throw
208+
`org.mybatis.dynamic.sql.exception.InvalidSqlException`. We believe this is the safest outcome. For example, suppose
209209
a DELETE statement was coded as follows:
210210

211211
```java
@@ -214,12 +214,6 @@ a DELETE statement was coded as follows:
214214
.and(id, isIn(Collections.emptyList()));
215215
```
216216

217-
This statement will be rendered as follows:
218-
219-
```sql
220-
delete from foo where status = ? and id in ()
221-
```
222-
223217
This will cause a runtime error due to invalid SQL, but it eliminates the possibility of deleting ALL rows with
224218
active status. If you want to allow the "in" condition to drop from the SQL if the list is empty, then use the
225219
"inWhenPresent" condition.
@@ -229,8 +223,8 @@ and the case-insensitive versions of these conditions:
229223

230224
| Input | Effect |
231225
|------------------------------------------|-----------------------------------------------------------------------------------|
232-
| isIn(null) | NullPointerException |
233-
| isIn(Collections.emptyList()) | Rendered as "in ()" (Invalid SQL) |
226+
| isIn(null) | NullPointerException thrown |
227+
| isIn(Collections.emptyList()) | InvalidSqlException thrown |
234228
| isIn(2, 3, null) | Rendered as "in (?, ?, ?)" (Parameter values are 2, 3, and null) |
235229
| isInWhenPresent(null) | Condition Not Rendered |
236230
| isInWhenPresent(Collections.emptyList()) | Condition Not Rendered |

0 commit comments

Comments
 (0)