Skip to content

Commit c9e3801

Browse files
authored
Merge pull request #515 from jeffgbutler/statement-configuration
Prevent Non-Rendering Where Clauses by Default
2 parents 1a51903 + 21e45e7 commit c9e3801

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+1047
-73
lines changed

CHANGELOG.md

+23
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,26 @@ This log will detail notable changes to MyBatis Dynamic SQL. Full details are av
66

77
GitHub milestone: [https://github.com/mybatis/mybatis-dynamic-sql/issues?q=milestone%3A1.4.1+](https://github.com/mybatis/mybatis-dynamic-sql/issues?q=milestone%3A1.4.1+)
88

9+
### Potentially Breaking Change
10+
11+
In this release we have changed the default behavior of the library in one key area. If a where clause is coded,
12+
but fails to render because all the optional conditionals drop out of the where clause, then the library will now
13+
throw a `NonRenderingWhereClauseException`. We have made this change out of an abundance of caution. The prior
14+
behavior would allow generation of statements that inadvertently affected all rows in a table.
15+
16+
We have also deprecated the "empty callback" functions in the "in" conditions in favor of this new configuration
17+
strategy. The "empty callback" methods were effective for "in" conditions that failed to render, but they offered
18+
no help for other conditions that failed to render, or if all conditions fail to render - which is arguably a more
19+
dangerous outcome. If you were using any of these methods, you should remove the calls to those methods and catch the
20+
new `NonRenderingWhereClauseException`.
21+
22+
If you desire the prior behavior where non rendering where clauses are allowed, you can change the global configuration
23+
of the library or - even better - change the configuration of individual statements where this behavior should be allowed.
24+
25+
For examples of global and statement configuration, see the "Configuration of the Library" page.
26+
27+
### Other Changes
28+
929
1. Added support for criteria groups without an initial criteria. This makes it possible to create an independent list
1030
of pre-created criteria and then add the list to a where clause. See the tests in the related pull request for
1131
usage examples. ([#462](https://github.com/mybatis/mybatis-dynamic-sql/pull/462))
@@ -15,6 +35,9 @@ GitHub milestone: [https://github.com/mybatis/mybatis-dynamic-sql/issues?q=miles
1535
3. Updated the Kotlin DSL to use Kotlin 1.7's new "definitely non-null" types where appropriate. This helps us to more
1636
accurately represent the nullable/non-nullable expectations for API method calls.
1737
([#496](https://github.com/mybatis/mybatis-dynamic-sql/pull/496))
38+
4. Added the ability to configure the library and change some default behaviors. Currently, this is limited to changing
39+
the behavior of the library in regard to where clauses that will not render. See the "Configuration of the Library"
40+
page for details. ([#515](https://github.com/mybatis/mybatis-dynamic-sql/pull/515))
1841

1942
## Release 1.4.0 - March 3, 2022
2043

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

+20-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2016-2021 the original author or authors.
2+
* Copyright 2016-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -26,12 +26,23 @@
2626

2727
public abstract class AbstractListValueCondition<T> implements VisitableCondition<T> {
2828
protected final Collection<T> values;
29+
30+
/**
31+
* @deprecated in favor of the statement configuration functions
32+
*/
33+
@Deprecated
2934
protected final Callback emptyCallback;
3035

3136
protected AbstractListValueCondition(Collection<T> values) {
3237
this(values, () -> { });
3338
}
3439

40+
/**
41+
* @deprecated in favor of the statement configuration functions
42+
* @param values values
43+
* @param emptyCallback empty callback
44+
*/
45+
@Deprecated
3546
protected AbstractListValueCondition(Collection<T> values, Callback emptyCallback) {
3647
this.values = Objects.requireNonNull(values);
3748
this.emptyCallback = Objects.requireNonNull(emptyCallback);
@@ -96,6 +107,14 @@ protected <R, S extends AbstractListValueCondition<R>> S mapSupport(Function<? s
96107
*/
97108
public abstract AbstractListValueCondition<T> filter(Predicate<? super T> predicate);
98109

110+
/**
111+
* Specifies a callback function to be called if the value list is empty when rendered.
112+
*
113+
* @deprecated in favor of the statement configuration functions
114+
* @param callback a callback function - typically throws an exception to block the statement from executing
115+
* @return this condition
116+
*/
117+
@Deprecated
99118
public abstract AbstractListValueCondition<T> withListEmptyCallback(Callback callback);
100119

101120
public abstract String renderCondition(String columnName, Stream<String> placeholders);

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

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2016-2020 the original author or authors.
2+
* Copyright 2016-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -17,6 +17,10 @@
1717

1818
import java.util.function.Function;
1919

20+
/**
21+
* @deprecated in favor of the new statement configuration functionality
22+
*/
23+
@Deprecated
2024
@FunctionalInterface
2125
public interface Callback {
2226
void call();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
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.configuration;
17+
18+
import java.io.IOException;
19+
import java.io.InputStream;
20+
import java.util.Properties;
21+
import java.util.logging.Level;
22+
import java.util.logging.Logger;
23+
24+
public class GlobalConfiguration {
25+
public static final String CONFIGURATION_FILE_PROPERTY = "mybatis-dynamic-sql.configurationFile"; //$NON-NLS-1$
26+
private static final String DEFAULT_PROPERTY_FILE = "mybatis-dynamic-sql.properties"; //$NON-NLS-1$
27+
private boolean isNonRenderingWhereClauseAllowed = false;
28+
private final Properties properties = new Properties();
29+
30+
public GlobalConfiguration() {
31+
initialize();
32+
}
33+
34+
private void initialize() {
35+
initializeProperties();
36+
initializeNonRenderingWhereClauseAllowed();
37+
}
38+
39+
private void initializeProperties() {
40+
String configFileName = getConfigurationFileName();
41+
InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(configFileName);
42+
if (inputStream != null) {
43+
loadProperties(inputStream, configFileName);
44+
}
45+
}
46+
47+
private String getConfigurationFileName() {
48+
String property = System.getProperty(CONFIGURATION_FILE_PROPERTY);
49+
if (property == null) {
50+
return DEFAULT_PROPERTY_FILE;
51+
} else {
52+
return property;
53+
}
54+
}
55+
56+
void loadProperties(InputStream inputStream, String propertyFile) {
57+
try {
58+
properties.load(inputStream);
59+
} catch (IOException e) {
60+
Logger logger = Logger.getLogger(GlobalConfiguration.class.getName());
61+
logger.log(Level.SEVERE, e, () -> "IOException reading property file \"" + propertyFile + "\"");
62+
}
63+
}
64+
65+
private void initializeNonRenderingWhereClauseAllowed() {
66+
String value = properties.getProperty("nonRenderingWhereClauseAllowed", "false"); //$NON-NLS-1$ //$NON-NLS-2$
67+
isNonRenderingWhereClauseAllowed = Boolean.parseBoolean(value);
68+
}
69+
70+
public boolean isIsNonRenderingWhereClauseAllowed() {
71+
return isNonRenderingWhereClauseAllowed;
72+
}
73+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
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.configuration;
17+
18+
public class GlobalContext {
19+
20+
private static final GlobalContext instance = new GlobalContext();
21+
22+
private final GlobalConfiguration globalConfiguration = new GlobalConfiguration();
23+
24+
private GlobalContext() {}
25+
26+
public static GlobalConfiguration getConfiguration() {
27+
return instance.globalConfiguration;
28+
}
29+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
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.configuration;
17+
18+
import org.mybatis.dynamic.sql.exception.NonRenderingWhereClauseException;
19+
20+
/**
21+
* This class can be used to change some behaviors of the framework. Every configurable statement
22+
* contains a unique instance of this class, so changes here will only impact a single statement.
23+
* If you intend to change the behavior for all statements, use the {@link GlobalConfiguration}.
24+
* Initial values for this class in each statement are set from the {@link GlobalConfiguration}.
25+
* Configurable behaviors are detailed below:
26+
*
27+
* <dl>
28+
* <dt>nonRenderingWhereClauseAllowed</dt>
29+
* <dd>If false (default), the framework will throw a {@link NonRenderingWhereClauseException}
30+
* if a where clause is specified in the statement, but it fails to render because all
31+
* optional conditions do not render. For example, if an "in" condition specifies an
32+
* empty list of values. If no criteria are specified in a where clause, the framework
33+
* assumes that no where clause was intended and will not throw an exception.
34+
* </dd>
35+
* </dl>
36+
*
37+
* @see GlobalConfiguration
38+
* @since 1.4.1
39+
* @author Jeff Butler
40+
*/
41+
public class StatementConfiguration {
42+
private boolean isNonRenderingWhereClauseAllowed =
43+
GlobalContext.getConfiguration().isIsNonRenderingWhereClauseAllowed();
44+
45+
public boolean isNonRenderingWhereClauseAllowed() {
46+
return isNonRenderingWhereClauseAllowed;
47+
}
48+
49+
public void setNonRenderingWhereClauseAllowed(boolean nonRenderingWhereClauseAllowed) {
50+
this.isNonRenderingWhereClauseAllowed = nonRenderingWhereClauseAllowed;
51+
}
52+
}

src/main/java/org/mybatis/dynamic/sql/delete/DeleteDSL.java

+25-8
Original file line numberDiff line numberDiff line change
@@ -16,21 +16,25 @@
1616
package org.mybatis.dynamic.sql.delete;
1717

1818
import java.util.Objects;
19+
import java.util.function.Consumer;
1920
import java.util.function.Function;
2021

2122
import org.jetbrains.annotations.NotNull;
2223
import org.mybatis.dynamic.sql.SqlTable;
24+
import org.mybatis.dynamic.sql.configuration.StatementConfiguration;
2325
import org.mybatis.dynamic.sql.util.Buildable;
2426
import org.mybatis.dynamic.sql.where.AbstractWhereDSL;
2527
import org.mybatis.dynamic.sql.where.AbstractWhereSupport;
2628
import org.mybatis.dynamic.sql.where.WhereModel;
2729

28-
public class DeleteDSL<R> extends AbstractWhereSupport<DeleteDSL<R>.DeleteWhereBuilder> implements Buildable<R> {
30+
public class DeleteDSL<R> extends AbstractWhereSupport<DeleteDSL<R>.DeleteWhereBuilder, DeleteDSL<R>>
31+
implements Buildable<R> {
2932

3033
private final Function<DeleteModel, R> adapterFunction;
3134
private final SqlTable table;
3235
private final String tableAlias;
33-
private final DeleteWhereBuilder whereBuilder = new DeleteWhereBuilder();
36+
private DeleteWhereBuilder whereBuilder;
37+
private final StatementConfiguration statementConfiguration = new StatementConfiguration();
3438

3539
private DeleteDSL(SqlTable table, String tableAlias, Function<DeleteModel, R> adapterFunction) {
3640
this.table = Objects.requireNonNull(table);
@@ -40,6 +44,9 @@ private DeleteDSL(SqlTable table, String tableAlias, Function<DeleteModel, R> ad
4044

4145
@Override
4246
public DeleteWhereBuilder where() {
47+
if (whereBuilder == null) {
48+
whereBuilder = new DeleteWhereBuilder();
49+
}
4350
return whereBuilder;
4451
}
4552

@@ -52,11 +59,19 @@ public DeleteWhereBuilder where() {
5259
@NotNull
5360
@Override
5461
public R build() {
55-
DeleteModel deleteModel = DeleteModel.withTable(table)
56-
.withTableAlias(tableAlias)
57-
.withWhereModel(whereBuilder.buildWhereModel())
58-
.build();
59-
return adapterFunction.apply(deleteModel);
62+
DeleteModel.Builder deleteModelBuilder = DeleteModel.withTable(table)
63+
.withTableAlias(tableAlias);
64+
if (whereBuilder != null) {
65+
deleteModelBuilder.withWhereModel(whereBuilder.buildWhereModel());
66+
}
67+
68+
return adapterFunction.apply(deleteModelBuilder.build());
69+
}
70+
71+
@Override
72+
public DeleteDSL<R> configureStatement(Consumer<StatementConfiguration> consumer) {
73+
consumer.accept(statementConfiguration);
74+
return this;
6075
}
6176

6277
public static <R> DeleteDSL<R> deleteFrom(Function<DeleteModel, R> adapterFunction, SqlTable table,
@@ -74,7 +89,9 @@ public static DeleteDSL<DeleteModel> deleteFrom(SqlTable table, String tableAlia
7489

7590
public class DeleteWhereBuilder extends AbstractWhereDSL<DeleteWhereBuilder> implements Buildable<R> {
7691

77-
private DeleteWhereBuilder() {}
92+
private DeleteWhereBuilder() {
93+
super(statementConfiguration);
94+
}
7895

7996
@NotNull
8097
@Override
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
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.exception;
17+
18+
import org.mybatis.dynamic.sql.configuration.GlobalConfiguration;
19+
import org.mybatis.dynamic.sql.configuration.StatementConfiguration;
20+
21+
/**
22+
* This exception is thrown when the where clause in a statement will not render.
23+
* This can happen if all the optional conditions in a where clause fail to
24+
* render - for example, if an "in" condition specifies an empty list.
25+
*
26+
* <p>By default, the framework will throw this exception if a where clause
27+
* fails to render. A where clause that fails to render can be very dangerous in that
28+
* it could cause all rows in a table to be affected by a statement - for example,
29+
* all rows could be deleted.
30+
*
31+
* <p>If you intend to allow a where clause to not render, then configure the
32+
* statement to allow it, or change the global configuration.
33+
*
34+
* @see GlobalConfiguration
35+
* @see StatementConfiguration
36+
* @since 1.4.1
37+
* @author Jeff Butler
38+
*/
39+
public class NonRenderingWhereClauseException extends RuntimeException {
40+
public NonRenderingWhereClauseException() {
41+
super("A where clause was specified, but failed to render."); //$NON-NLS-1$
42+
}
43+
}

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838

3939
public abstract class AbstractQueryExpressionDSL<W extends AbstractWhereDSL<?>,
4040
T extends AbstractQueryExpressionDSL<W, T>>
41-
extends AbstractWhereSupport<W> {
41+
extends AbstractWhereSupport<W, T> {
4242

4343
private final List<JoinSpecification.Builder> joinSpecificationBuilders = new ArrayList<>();
4444
private final Map<SqlTable, String> tableAliases = new HashMap<>();

0 commit comments

Comments
 (0)