diff --git a/CHANGELOG.md b/CHANGELOG.md index 21e7cb3aa..86e260063 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,26 @@ This log will detail notable changes to MyBatis Dynamic SQL. Full details are av 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+) +### Potentially Breaking Change + +In this release we have changed the default behavior of the library in one key area. If a where clause is coded, +but fails to render because all the optional conditionals drop out of the where clause, then the library will now +throw a `NonRenderingWhereClauseException`. We have made this change out of an abundance of caution. The prior +behavior would allow generation of statements that inadvertently affected all rows in a table. + +We have also deprecated the "empty callback" functions in the "in" conditions in favor of this new configuration +strategy. The "empty callback" methods were effective for "in" conditions that failed to render, but they offered +no help for other conditions that failed to render, or if all conditions fail to render - which is arguably a more +dangerous outcome. If you were using any of these methods, you should remove the calls to those methods and catch the +new `NonRenderingWhereClauseException`. + +If you desire the prior behavior where non rendering where clauses are allowed, you can change the global configuration +of the library or - even better - change the configuration of individual statements where this behavior should be allowed. + +For examples of global and statement configuration, see the "Configuration of the Library" page. + +### Other Changes + 1. Added support for criteria groups without an initial criteria. This makes it possible to create an independent list of pre-created criteria and then add the list to a where clause. See the tests in the related pull request for 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 3. Updated the Kotlin DSL to use Kotlin 1.7's new "definitely non-null" types where appropriate. This helps us to more accurately represent the nullable/non-nullable expectations for API method calls. ([#496](https://github.com/mybatis/mybatis-dynamic-sql/pull/496)) +4. Added the ability to configure the library and change some default behaviors. Currently, this is limited to changing + the behavior of the library in regard to where clauses that will not render. See the "Configuration of the Library" + page for details. ([#515](https://github.com/mybatis/mybatis-dynamic-sql/pull/515)) ## Release 1.4.0 - March 3, 2022 diff --git a/src/main/java/org/mybatis/dynamic/sql/AbstractListValueCondition.java b/src/main/java/org/mybatis/dynamic/sql/AbstractListValueCondition.java index 148865302..b6f3e64d3 100644 --- a/src/main/java/org/mybatis/dynamic/sql/AbstractListValueCondition.java +++ b/src/main/java/org/mybatis/dynamic/sql/AbstractListValueCondition.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2021 the original author or authors. + * Copyright 2016-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,12 +26,23 @@ public abstract class AbstractListValueCondition implements VisitableCondition { protected final Collection values; + + /** + * @deprecated in favor of the statement configuration functions + */ + @Deprecated protected final Callback emptyCallback; protected AbstractListValueCondition(Collection values) { this(values, () -> { }); } + /** + * @deprecated in favor of the statement configuration functions + * @param values values + * @param emptyCallback empty callback + */ + @Deprecated protected AbstractListValueCondition(Collection values, Callback emptyCallback) { this.values = Objects.requireNonNull(values); this.emptyCallback = Objects.requireNonNull(emptyCallback); @@ -96,6 +107,14 @@ protected > S mapSupport(Function filter(Predicate predicate); + /** + * Specifies a callback function to be called if the value list is empty when rendered. + * + * @deprecated in favor of the statement configuration functions + * @param callback a callback function - typically throws an exception to block the statement from executing + * @return this condition + */ + @Deprecated public abstract AbstractListValueCondition withListEmptyCallback(Callback callback); public abstract String renderCondition(String columnName, Stream placeholders); diff --git a/src/main/java/org/mybatis/dynamic/sql/Callback.java b/src/main/java/org/mybatis/dynamic/sql/Callback.java index 2c5f07704..4e38c2864 100644 --- a/src/main/java/org/mybatis/dynamic/sql/Callback.java +++ b/src/main/java/org/mybatis/dynamic/sql/Callback.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2020 the original author or authors. + * Copyright 2016-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,10 @@ import java.util.function.Function; +/** + * @deprecated in favor of the new statement configuration functionality + */ +@Deprecated @FunctionalInterface public interface Callback { void call(); diff --git a/src/main/java/org/mybatis/dynamic/sql/configuration/GlobalConfiguration.java b/src/main/java/org/mybatis/dynamic/sql/configuration/GlobalConfiguration.java new file mode 100644 index 000000000..cd6ea4dd0 --- /dev/null +++ b/src/main/java/org/mybatis/dynamic/sql/configuration/GlobalConfiguration.java @@ -0,0 +1,73 @@ +/* + * Copyright 2016-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.mybatis.dynamic.sql.configuration; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Properties; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class GlobalConfiguration { + public static final String CONFIGURATION_FILE_PROPERTY = "mybatis-dynamic-sql.configurationFile"; //$NON-NLS-1$ + private static final String DEFAULT_PROPERTY_FILE = "mybatis-dynamic-sql.properties"; //$NON-NLS-1$ + private boolean isNonRenderingWhereClauseAllowed = false; + private final Properties properties = new Properties(); + + public GlobalConfiguration() { + initialize(); + } + + private void initialize() { + initializeProperties(); + initializeNonRenderingWhereClauseAllowed(); + } + + private void initializeProperties() { + String configFileName = getConfigurationFileName(); + InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(configFileName); + if (inputStream != null) { + loadProperties(inputStream, configFileName); + } + } + + private String getConfigurationFileName() { + String property = System.getProperty(CONFIGURATION_FILE_PROPERTY); + if (property == null) { + return DEFAULT_PROPERTY_FILE; + } else { + return property; + } + } + + void loadProperties(InputStream inputStream, String propertyFile) { + try { + properties.load(inputStream); + } catch (IOException e) { + Logger logger = Logger.getLogger(GlobalConfiguration.class.getName()); + logger.log(Level.SEVERE, e, () -> "IOException reading property file \"" + propertyFile + "\""); + } + } + + private void initializeNonRenderingWhereClauseAllowed() { + String value = properties.getProperty("nonRenderingWhereClauseAllowed", "false"); //$NON-NLS-1$ //$NON-NLS-2$ + isNonRenderingWhereClauseAllowed = Boolean.parseBoolean(value); + } + + public boolean isIsNonRenderingWhereClauseAllowed() { + return isNonRenderingWhereClauseAllowed; + } +} diff --git a/src/main/java/org/mybatis/dynamic/sql/configuration/GlobalContext.java b/src/main/java/org/mybatis/dynamic/sql/configuration/GlobalContext.java new file mode 100644 index 000000000..9965ec2a7 --- /dev/null +++ b/src/main/java/org/mybatis/dynamic/sql/configuration/GlobalContext.java @@ -0,0 +1,29 @@ +/* + * Copyright 2016-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.mybatis.dynamic.sql.configuration; + +public class GlobalContext { + + private static final GlobalContext instance = new GlobalContext(); + + private final GlobalConfiguration globalConfiguration = new GlobalConfiguration(); + + private GlobalContext() {} + + public static GlobalConfiguration getConfiguration() { + return instance.globalConfiguration; + } +} diff --git a/src/main/java/org/mybatis/dynamic/sql/configuration/StatementConfiguration.java b/src/main/java/org/mybatis/dynamic/sql/configuration/StatementConfiguration.java new file mode 100644 index 000000000..014a1001d --- /dev/null +++ b/src/main/java/org/mybatis/dynamic/sql/configuration/StatementConfiguration.java @@ -0,0 +1,52 @@ +/* + * Copyright 2016-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.mybatis.dynamic.sql.configuration; + +import org.mybatis.dynamic.sql.exception.NonRenderingWhereClauseException; + +/** + * This class can be used to change some behaviors of the framework. Every configurable statement + * contains a unique instance of this class, so changes here will only impact a single statement. + * If you intend to change the behavior for all statements, use the {@link GlobalConfiguration}. + * Initial values for this class in each statement are set from the {@link GlobalConfiguration}. + * Configurable behaviors are detailed below: + * + *
+ *
nonRenderingWhereClauseAllowed
+ *
If false (default), the framework will throw a {@link NonRenderingWhereClauseException} + * if a where clause is specified in the statement, but it fails to render because all + * optional conditions do not render. For example, if an "in" condition specifies an + * empty list of values. If no criteria are specified in a where clause, the framework + * assumes that no where clause was intended and will not throw an exception. + *
+ *
+ * + * @see GlobalConfiguration + * @since 1.4.1 + * @author Jeff Butler + */ +public class StatementConfiguration { + private boolean isNonRenderingWhereClauseAllowed = + GlobalContext.getConfiguration().isIsNonRenderingWhereClauseAllowed(); + + public boolean isNonRenderingWhereClauseAllowed() { + return isNonRenderingWhereClauseAllowed; + } + + public void setNonRenderingWhereClauseAllowed(boolean nonRenderingWhereClauseAllowed) { + this.isNonRenderingWhereClauseAllowed = nonRenderingWhereClauseAllowed; + } +} diff --git a/src/main/java/org/mybatis/dynamic/sql/delete/DeleteDSL.java b/src/main/java/org/mybatis/dynamic/sql/delete/DeleteDSL.java index 4c06507b9..8b59ff8e1 100644 --- a/src/main/java/org/mybatis/dynamic/sql/delete/DeleteDSL.java +++ b/src/main/java/org/mybatis/dynamic/sql/delete/DeleteDSL.java @@ -16,21 +16,25 @@ package org.mybatis.dynamic.sql.delete; import java.util.Objects; +import java.util.function.Consumer; import java.util.function.Function; import org.jetbrains.annotations.NotNull; import org.mybatis.dynamic.sql.SqlTable; +import org.mybatis.dynamic.sql.configuration.StatementConfiguration; import org.mybatis.dynamic.sql.util.Buildable; import org.mybatis.dynamic.sql.where.AbstractWhereDSL; import org.mybatis.dynamic.sql.where.AbstractWhereSupport; import org.mybatis.dynamic.sql.where.WhereModel; -public class DeleteDSL extends AbstractWhereSupport.DeleteWhereBuilder> implements Buildable { +public class DeleteDSL extends AbstractWhereSupport.DeleteWhereBuilder, DeleteDSL> + implements Buildable { private final Function adapterFunction; private final SqlTable table; private final String tableAlias; - private final DeleteWhereBuilder whereBuilder = new DeleteWhereBuilder(); + private DeleteWhereBuilder whereBuilder; + private final StatementConfiguration statementConfiguration = new StatementConfiguration(); private DeleteDSL(SqlTable table, String tableAlias, Function adapterFunction) { this.table = Objects.requireNonNull(table); @@ -40,6 +44,9 @@ private DeleteDSL(SqlTable table, String tableAlias, Function ad @Override public DeleteWhereBuilder where() { + if (whereBuilder == null) { + whereBuilder = new DeleteWhereBuilder(); + } return whereBuilder; } @@ -52,11 +59,19 @@ public DeleteWhereBuilder where() { @NotNull @Override public R build() { - DeleteModel deleteModel = DeleteModel.withTable(table) - .withTableAlias(tableAlias) - .withWhereModel(whereBuilder.buildWhereModel()) - .build(); - return adapterFunction.apply(deleteModel); + DeleteModel.Builder deleteModelBuilder = DeleteModel.withTable(table) + .withTableAlias(tableAlias); + if (whereBuilder != null) { + deleteModelBuilder.withWhereModel(whereBuilder.buildWhereModel()); + } + + return adapterFunction.apply(deleteModelBuilder.build()); + } + + @Override + public DeleteDSL configureStatement(Consumer consumer) { + consumer.accept(statementConfiguration); + return this; } public static DeleteDSL deleteFrom(Function adapterFunction, SqlTable table, @@ -74,7 +89,9 @@ public static DeleteDSL deleteFrom(SqlTable table, String tableAlia public class DeleteWhereBuilder extends AbstractWhereDSL implements Buildable { - private DeleteWhereBuilder() {} + private DeleteWhereBuilder() { + super(statementConfiguration); + } @NotNull @Override diff --git a/src/main/java/org/mybatis/dynamic/sql/exception/NonRenderingWhereClauseException.java b/src/main/java/org/mybatis/dynamic/sql/exception/NonRenderingWhereClauseException.java new file mode 100644 index 000000000..ceec1289c --- /dev/null +++ b/src/main/java/org/mybatis/dynamic/sql/exception/NonRenderingWhereClauseException.java @@ -0,0 +1,43 @@ +/* + * Copyright 2016-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.mybatis.dynamic.sql.exception; + +import org.mybatis.dynamic.sql.configuration.GlobalConfiguration; +import org.mybatis.dynamic.sql.configuration.StatementConfiguration; + +/** + * This exception is thrown when the where clause in a statement will not render. + * This can happen if all the optional conditions in a where clause fail to + * render - for example, if an "in" condition specifies an empty list. + * + *

By default, the framework will throw this exception if a where clause + * fails to render. A where clause that fails to render can be very dangerous in that + * it could cause all rows in a table to be affected by a statement - for example, + * all rows could be deleted. + * + *

If you intend to allow a where clause to not render, then configure the + * statement to allow it, or change the global configuration. + * + * @see GlobalConfiguration + * @see StatementConfiguration + * @since 1.4.1 + * @author Jeff Butler + */ +public class NonRenderingWhereClauseException extends RuntimeException { + public NonRenderingWhereClauseException() { + super("A where clause was specified, but failed to render."); //$NON-NLS-1$ + } +} diff --git a/src/main/java/org/mybatis/dynamic/sql/select/AbstractQueryExpressionDSL.java b/src/main/java/org/mybatis/dynamic/sql/select/AbstractQueryExpressionDSL.java index 1cabb6300..c5130ddac 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/AbstractQueryExpressionDSL.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/AbstractQueryExpressionDSL.java @@ -38,7 +38,7 @@ public abstract class AbstractQueryExpressionDSL, T extends AbstractQueryExpressionDSL> - extends AbstractWhereSupport { + extends AbstractWhereSupport { private final List joinSpecificationBuilders = new ArrayList<>(); private final Map tableAliases = new HashMap<>(); diff --git a/src/main/java/org/mybatis/dynamic/sql/select/CountDSL.java b/src/main/java/org/mybatis/dynamic/sql/select/CountDSL.java index e3f80ec17..139373453 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/CountDSL.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/CountDSL.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2021 the original author or authors. + * Copyright 2016-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,12 +16,14 @@ package org.mybatis.dynamic.sql.select; import java.util.Objects; +import java.util.function.Consumer; import java.util.function.Function; import org.jetbrains.annotations.NotNull; import org.mybatis.dynamic.sql.BasicColumn; import org.mybatis.dynamic.sql.SqlBuilder; import org.mybatis.dynamic.sql.SqlTable; +import org.mybatis.dynamic.sql.configuration.StatementConfiguration; import org.mybatis.dynamic.sql.util.Buildable; import org.mybatis.dynamic.sql.where.AbstractWhereDSL; import org.mybatis.dynamic.sql.where.WhereModel; @@ -39,8 +41,9 @@ public class CountDSL extends AbstractQueryExpressionDSL.CountWhe implements Buildable { private final Function adapterFunction; - private final CountWhereBuilder whereBuilder = new CountWhereBuilder(); + private CountWhereBuilder whereBuilder; private final BasicColumn countColumn; + private final StatementConfiguration statementConfiguration = new StatementConfiguration(); private CountDSL(BasicColumn countColumn, SqlTable table, Function adapterFunction) { super(table); @@ -50,6 +53,9 @@ private CountDSL(BasicColumn countColumn, SqlTable table, Function configureStatement(Consumer consumer) { + consumer.accept(statementConfiguration); + return this; + } + private SelectModel buildModel() { QueryExpressionModel.Builder b = new QueryExpressionModel.Builder() .withSelectColumn(countColumn) .withTable(table()) - .withTableAliases(tableAliases()) - .withWhereModel(whereBuilder.buildWhereModel()); + .withTableAliases(tableAliases()); + + if (whereBuilder != null) { + b.withWhereModel(whereBuilder.buildWhereModel()); + } buildJoinModel().ifPresent(b::withJoinModel); @@ -118,7 +133,9 @@ public CountDSL from(SqlTable table) { public class CountWhereBuilder extends AbstractWhereDSL implements Buildable { - private CountWhereBuilder() {} + private CountWhereBuilder() { + super(statementConfiguration); + } @NotNull @Override diff --git a/src/main/java/org/mybatis/dynamic/sql/select/QueryExpressionDSL.java b/src/main/java/org/mybatis/dynamic/sql/select/QueryExpressionDSL.java index 4d44c46e6..8f379bb70 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/QueryExpressionDSL.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/QueryExpressionDSL.java @@ -20,12 +20,14 @@ import java.util.Collection; import java.util.List; import java.util.Objects; +import java.util.function.Consumer; import org.jetbrains.annotations.NotNull; import org.mybatis.dynamic.sql.BasicColumn; import org.mybatis.dynamic.sql.SortSpecification; import org.mybatis.dynamic.sql.SqlTable; import org.mybatis.dynamic.sql.TableExpression; +import org.mybatis.dynamic.sql.configuration.StatementConfiguration; import org.mybatis.dynamic.sql.select.join.JoinCondition; import org.mybatis.dynamic.sql.select.join.JoinCriterion; import org.mybatis.dynamic.sql.select.join.JoinSpecification; @@ -43,8 +45,9 @@ public class QueryExpressionDSL private final SelectDSL selectDSL; private final boolean isDistinct; private final List selectList; - private final QueryExpressionWhereBuilder whereBuilder = new QueryExpressionWhereBuilder(); + private QueryExpressionWhereBuilder whereBuilder; private GroupByModel groupByModel; + private final StatementConfiguration statementConfiguration = new StatementConfiguration(); QueryExpressionDSL(FromGatherer fromGatherer, TableExpression table) { super(table); @@ -61,9 +64,18 @@ public class QueryExpressionDSL @Override public QueryExpressionWhereBuilder where() { + if (whereBuilder == null) { + whereBuilder = new QueryExpressionWhereBuilder(); + } return whereBuilder; } + @Override + public QueryExpressionDSL configureStatement(Consumer consumer) { + consumer.accept(statementConfiguration); + return this; + } + @NotNull @Override public R build() { @@ -149,15 +161,19 @@ public UnionBuilder unionAll() { } protected QueryExpressionModel buildModel() { - return QueryExpressionModel.withSelectList(selectList) + QueryExpressionModel.Builder builder = QueryExpressionModel.withSelectList(selectList) .withConnector(connector) .withTable(table()) .isDistinct(isDistinct) .withTableAliases(tableAliases()) - .withWhereModel(whereBuilder.buildWhereModel()) .withJoinModel(buildJoinModel().orElse(null)) - .withGroupByModel(groupByModel) - .build(); + .withGroupByModel(groupByModel); + + if (whereBuilder != null) { + builder.withWhereModel(whereBuilder.buildWhereModel()); + } + + return builder.build(); } public SelectDSL.LimitFinisher limit(long limit) { @@ -240,7 +256,9 @@ public FromGatherer build() { public class QueryExpressionWhereBuilder extends AbstractWhereDSL implements Buildable { - private QueryExpressionWhereBuilder() {} + private QueryExpressionWhereBuilder() { + super(statementConfiguration); + } public UnionBuilder union() { return QueryExpressionDSL.this.union(); @@ -313,7 +331,8 @@ public JoinSpecificationFinisher on(BasicColumn joinColumn, JoinCondition onJoin } } - public class JoinSpecificationFinisher extends AbstractWhereSupport + public class JoinSpecificationFinisher + extends AbstractWhereSupport implements Buildable { private final JoinSpecification.Builder joinSpecificationBuilder; @@ -354,6 +373,12 @@ public R build() { return QueryExpressionDSL.this.build(); } + @Override + public JoinSpecificationFinisher configureStatement(Consumer consumer) { + consumer.accept(statementConfiguration); + return this; + } + @Override public QueryExpressionWhereBuilder where() { return QueryExpressionDSL.this.where(); diff --git a/src/main/java/org/mybatis/dynamic/sql/select/SelectDSL.java b/src/main/java/org/mybatis/dynamic/sql/select/SelectDSL.java index 339487116..c11de060a 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/SelectDSL.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/SelectDSL.java @@ -20,6 +20,7 @@ import java.util.Collection; import java.util.List; import java.util.Objects; +import java.util.function.Consumer; import java.util.function.Function; import java.util.stream.Collectors; @@ -28,8 +29,10 @@ import org.mybatis.dynamic.sql.SortSpecification; import org.mybatis.dynamic.sql.SqlTable; import org.mybatis.dynamic.sql.TableExpression; +import org.mybatis.dynamic.sql.configuration.StatementConfiguration; import org.mybatis.dynamic.sql.select.QueryExpressionDSL.FromGatherer; import org.mybatis.dynamic.sql.util.Buildable; +import org.mybatis.dynamic.sql.util.ConfigurableStatement; /** * Implements a SQL DSL for building select statements. @@ -38,7 +41,7 @@ * * @param the type of model produced by this builder, typically SelectModel */ -public class SelectDSL implements Buildable { +public class SelectDSL implements Buildable, ConfigurableStatement> { private final Function adapterFunction; private final List> queryExpressions = new ArrayList<>(); @@ -125,6 +128,12 @@ public FetchFirstFinisher fetchFirst(long fetchFirstRows) { return new FetchFirstFinisher(); } + @Override + public SelectDSL configureStatement(Consumer consumer) { + queryExpressions.forEach(q -> q.configureStatement(consumer)); + return this; + } + @NotNull @Override public R build() { diff --git a/src/main/java/org/mybatis/dynamic/sql/update/UpdateDSL.java b/src/main/java/org/mybatis/dynamic/sql/update/UpdateDSL.java index 870f6dbcf..8f79be1f0 100644 --- a/src/main/java/org/mybatis/dynamic/sql/update/UpdateDSL.java +++ b/src/main/java/org/mybatis/dynamic/sql/update/UpdateDSL.java @@ -18,6 +18,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Objects; +import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Supplier; @@ -25,6 +26,7 @@ import org.mybatis.dynamic.sql.BasicColumn; import org.mybatis.dynamic.sql.SqlColumn; import org.mybatis.dynamic.sql.SqlTable; +import org.mybatis.dynamic.sql.configuration.StatementConfiguration; import org.mybatis.dynamic.sql.select.SelectModel; import org.mybatis.dynamic.sql.util.AbstractColumnMapping; import org.mybatis.dynamic.sql.util.Buildable; @@ -40,13 +42,15 @@ import org.mybatis.dynamic.sql.where.AbstractWhereSupport; import org.mybatis.dynamic.sql.where.WhereModel; -public class UpdateDSL extends AbstractWhereSupport.UpdateWhereBuilder> implements Buildable { +public class UpdateDSL extends AbstractWhereSupport.UpdateWhereBuilder, UpdateDSL> + implements Buildable { private final Function adapterFunction; private final List columnMappings = new ArrayList<>(); private final SqlTable table; private final String tableAlias; - private final UpdateWhereBuilder whereBuilder = new UpdateWhereBuilder(); + private UpdateWhereBuilder whereBuilder; + private final StatementConfiguration statementConfiguration = new StatementConfiguration(); private UpdateDSL(SqlTable table, String tableAlias, Function adapterFunction) { this.table = Objects.requireNonNull(table); @@ -60,6 +64,10 @@ public SetClauseFinisher set(SqlColumn column) { @Override public UpdateWhereBuilder where() { + if (whereBuilder == null) { + whereBuilder = new UpdateWhereBuilder(); + } + return whereBuilder; } @@ -72,12 +80,21 @@ public UpdateWhereBuilder where() { @NotNull @Override public R build() { - UpdateModel updateModel = UpdateModel.withTable(table) + UpdateModel.Builder updateModelBuilder = UpdateModel.withTable(table) .withTableAlias(tableAlias) - .withColumnMappings(columnMappings) - .withWhereModel(whereBuilder.buildWhereModel()) - .build(); - return adapterFunction.apply(updateModel); + .withColumnMappings(columnMappings); + + if (whereBuilder != null) { + updateModelBuilder.withWhereModel(whereBuilder.buildWhereModel()); + } + + return adapterFunction.apply(updateModelBuilder.build()); + } + + @Override + public UpdateDSL configureStatement(Consumer consumer) { + consumer.accept(statementConfiguration); + return this; } public static UpdateDSL update(Function adapterFunction, SqlTable table, String tableAlias) { @@ -155,7 +172,9 @@ public UpdateDSL equalToWhenPresent(Supplier valueSupplier) { public class UpdateWhereBuilder extends AbstractWhereDSL implements Buildable { - private UpdateWhereBuilder() {} + private UpdateWhereBuilder() { + super(statementConfiguration); + } @NotNull @Override diff --git a/src/main/java/org/mybatis/dynamic/sql/util/ConfigurableStatement.java b/src/main/java/org/mybatis/dynamic/sql/util/ConfigurableStatement.java new file mode 100644 index 000000000..bc9b1527d --- /dev/null +++ b/src/main/java/org/mybatis/dynamic/sql/util/ConfigurableStatement.java @@ -0,0 +1,24 @@ +/* + * Copyright 2016-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.mybatis.dynamic.sql.util; + +import java.util.function.Consumer; + +import org.mybatis.dynamic.sql.configuration.StatementConfiguration; + +public interface ConfigurableStatement { + R configureStatement(Consumer consumer); +} diff --git a/src/main/java/org/mybatis/dynamic/sql/where/AbstractWhereDSL.java b/src/main/java/org/mybatis/dynamic/sql/where/AbstractWhereDSL.java index 24f394971..aee7fda22 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/AbstractWhereDSL.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/AbstractWhereDSL.java @@ -18,6 +18,8 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Objects; +import java.util.function.Consumer; import org.jetbrains.annotations.NotNull; import org.mybatis.dynamic.sql.AndOrCriteriaGroup; @@ -28,11 +30,25 @@ import org.mybatis.dynamic.sql.ExistsPredicate; import org.mybatis.dynamic.sql.SqlCriterion; import org.mybatis.dynamic.sql.VisitableCondition; +import org.mybatis.dynamic.sql.configuration.StatementConfiguration; +import org.mybatis.dynamic.sql.util.ConfigurableStatement; -public abstract class AbstractWhereDSL> { +public abstract class AbstractWhereDSL> implements ConfigurableStatement { private SqlCriterion initialCriterion; // WARNING - may be null! private final List subCriteria = new ArrayList<>(); + private final StatementConfiguration statementConfiguration; + + protected AbstractWhereDSL(StatementConfiguration statementConfiguration) { + this.statementConfiguration = Objects.requireNonNull(statementConfiguration); + } + + @Override + public T configureStatement(Consumer consumer) { + consumer.accept(statementConfiguration); + return getThis(); + } + @NotNull public T where(BindableColumn column, VisitableCondition condition, AndOrCriteriaGroup... subCriteria) { @@ -172,7 +188,7 @@ public T or(List criteria) { } protected WhereModel internalBuild() { - return new WhereModel(initialCriterion, subCriteria); + return new WhereModel(initialCriterion, subCriteria, statementConfiguration); } private SqlCriterion buildCriterion(BindableColumn column, VisitableCondition condition) { diff --git a/src/main/java/org/mybatis/dynamic/sql/where/AbstractWhereSupport.java b/src/main/java/org/mybatis/dynamic/sql/where/AbstractWhereSupport.java index 29384e153..ac30a8c95 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/AbstractWhereSupport.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/AbstractWhereSupport.java @@ -24,6 +24,7 @@ import org.mybatis.dynamic.sql.ExistsPredicate; import org.mybatis.dynamic.sql.SqlCriterion; import org.mybatis.dynamic.sql.VisitableCondition; +import org.mybatis.dynamic.sql.util.ConfigurableStatement; /** * Base class for DSLs that support where clauses - which is every DSL except Insert. @@ -33,7 +34,8 @@ * * @param the implementation of the Where DSL customized for a particular SQL statement. */ -public abstract class AbstractWhereSupport> { +public abstract class AbstractWhereSupport, D extends AbstractWhereSupport> + implements ConfigurableStatement { public abstract W where(); diff --git a/src/main/java/org/mybatis/dynamic/sql/where/WhereDSL.java b/src/main/java/org/mybatis/dynamic/sql/where/WhereDSL.java index a5eb3332b..f9563c35c 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/WhereDSL.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/WhereDSL.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2020 the original author or authors. + * Copyright 2016-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,9 +15,12 @@ */ package org.mybatis.dynamic.sql.where; -public class WhereDSL extends AbstractWhereDSL { +import org.mybatis.dynamic.sql.configuration.StatementConfiguration; - private WhereDSL() {} +public class WhereDSL extends AbstractWhereDSL { + private WhereDSL() { + super(new StatementConfiguration()); + } @Override protected WhereDSL getThis() { diff --git a/src/main/java/org/mybatis/dynamic/sql/where/WhereModel.java b/src/main/java/org/mybatis/dynamic/sql/where/WhereModel.java index f102ddc03..5e894ec16 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/WhereModel.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/WhereModel.java @@ -18,11 +18,13 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Objects; import java.util.Optional; import java.util.concurrent.atomic.AtomicInteger; import org.mybatis.dynamic.sql.AndOrCriteriaGroup; import org.mybatis.dynamic.sql.SqlCriterion; +import org.mybatis.dynamic.sql.configuration.StatementConfiguration; import org.mybatis.dynamic.sql.render.RenderingStrategy; import org.mybatis.dynamic.sql.render.TableAliasCalculator; import org.mybatis.dynamic.sql.where.render.WhereClauseProvider; @@ -35,9 +37,13 @@ public class WhereModel { private final SqlCriterion initialCriterion; private final List subCriteria = new ArrayList<>(); - public WhereModel(SqlCriterion initialCriterion, List subCriteria) { + private final StatementConfiguration statementConfiguration; + + public WhereModel(SqlCriterion initialCriterion, List subCriteria, + StatementConfiguration statementConfiguration) { this.initialCriterion = initialCriterion; this.subCriteria.addAll(subCriteria); + this.statementConfiguration = Objects.requireNonNull(statementConfiguration); } public Optional initialCriterion() { @@ -48,6 +54,10 @@ public List subCriteria() { return Collections.unmodifiableList(subCriteria); } + public boolean isUnrenderableClauseAllowed() { + return statementConfiguration.isNonRenderingWhereClauseAllowed(); + } + /** * Renders a where clause without table aliases. * diff --git a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsIn.java b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsIn.java index e55dd8932..c579f57b8 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsIn.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsIn.java @@ -38,6 +38,11 @@ public static IsIn empty() { return t; } + /** + * @deprecated in favor of the statement configuration functions + * @return a new empty condition + */ + @Deprecated private IsIn emptyWithCallBack() { return new IsIn<>(Collections.emptyList(), emptyCallback); } @@ -46,6 +51,12 @@ protected IsIn(Collection values) { super(values); } + /** + * @deprecated in favor of the statement configuration functions + * @param values values + * @param emptyCallback empty callback + */ + @Deprecated protected IsIn(Collection values, Callback emptyCallback) { super(values, emptyCallback); } @@ -56,6 +67,12 @@ public String renderCondition(String columnName, Stream placeholders) { + placeholders.collect(Collectors.joining(",", "in (", ")")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ } + /** + * @deprecated in favor of the statement configuration functions + * @param callback a callback function - typically throws an exception to block the statement from executing + * @return this condition + */ + @Deprecated @Override public IsIn withListEmptyCallback(Callback callback) { return new IsIn<>(values, callback); diff --git a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsInCaseInsensitive.java b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsInCaseInsensitive.java index 368ecc8bc..ddbf948c4 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsInCaseInsensitive.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsInCaseInsensitive.java @@ -34,6 +34,11 @@ public static IsInCaseInsensitive empty() { return EMPTY; } + /** + * @deprecated in favor of the statement configuration functions + * @return a new empty condition + */ + @Deprecated private IsInCaseInsensitive emptyWithCallback() { return new IsInCaseInsensitive(Collections.emptyList(), emptyCallback); } @@ -42,6 +47,12 @@ protected IsInCaseInsensitive(Collection values) { super(values); } + /** + * @deprecated in favor of the statement configuration functions + * @param values values + * @param emptyCallback empty callback + */ + @Deprecated protected IsInCaseInsensitive(Collection values, Callback emptyCallback) { super(values, emptyCallback); } @@ -53,6 +64,12 @@ public String renderCondition(String columnName, Stream placeholders) { Collectors.joining(",", "in (", ")")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ } + /** + * @deprecated in favor of the statement configuration functions + * @param callback a callback function - typically throws an exception to block the statement from executing + * @return this condition + */ + @Deprecated @Override public IsInCaseInsensitive withListEmptyCallback(Callback callback) { return new IsInCaseInsensitive(values, callback); diff --git a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotIn.java b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotIn.java index 4a2f27546..44102bf9f 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotIn.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotIn.java @@ -38,6 +38,11 @@ public static IsNotIn empty() { return t; } + /** + * @deprecated in favor of the statement configuration functions + * @return a new empty condition + */ + @Deprecated private IsNotIn emptyWithCallback() { return new IsNotIn<>(Collections.emptyList(), emptyCallback); } @@ -46,6 +51,12 @@ protected IsNotIn(Collection values) { super(values); } + /** + * @deprecated in favor of the statement configuration functions + * @param values values + * @param emptyCallback empty callback + */ + @Deprecated protected IsNotIn(Collection values, Callback emptyCallback) { super(values, emptyCallback); } @@ -57,6 +68,13 @@ public String renderCondition(String columnName, Stream placeholders) { Collectors.joining(",", "not in (", ")")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ } + /** + * + * @deprecated in favor of the statement configuration functions + * @param callback a callback function - typically throws an exception to block the statement from executing + * @return this condition + */ + @Deprecated @Override public IsNotIn withListEmptyCallback(Callback callback) { return new IsNotIn<>(values, callback); diff --git a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotInCaseInsensitive.java b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotInCaseInsensitive.java index 211493090..df1735ed2 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotInCaseInsensitive.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotInCaseInsensitive.java @@ -34,6 +34,11 @@ public static IsNotInCaseInsensitive empty() { return EMPTY; } + /** + * @deprecated in favor of the statement configuration functions + * @return a new empty condition + */ + @Deprecated private IsNotInCaseInsensitive emptyWithCallback() { return new IsNotInCaseInsensitive(Collections.emptyList(), emptyCallback); } @@ -42,6 +47,12 @@ protected IsNotInCaseInsensitive(Collection values) { super(values); } + /** + * @deprecated in favor of the statement configuration functions + * @param values values + * @param emptyCallback empty callback + */ + @Deprecated protected IsNotInCaseInsensitive(Collection values, Callback emptyCallback) { super(values, emptyCallback); } @@ -53,6 +64,12 @@ public String renderCondition(String columnName, Stream placeholders) { Collectors.joining(",", "not in (", ")")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ } + /** + * @deprecated in favor of the statement configuration functions + * @param callback a callback function - typically throws an exception to block the statement from executing + * @return this condition + */ + @Deprecated @Override public IsNotInCaseInsensitive withListEmptyCallback(Callback callback) { return new IsNotInCaseInsensitive(values, callback); diff --git a/src/main/java/org/mybatis/dynamic/sql/where/render/WhereRenderer.java b/src/main/java/org/mybatis/dynamic/sql/where/render/WhereRenderer.java index 53f36b559..5208d1e8e 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/render/WhereRenderer.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/render/WhereRenderer.java @@ -21,6 +21,7 @@ import java.util.stream.Collectors; import org.mybatis.dynamic.sql.SqlCriterion; +import org.mybatis.dynamic.sql.exception.NonRenderingWhereClauseException; import org.mybatis.dynamic.sql.render.RenderingStrategy; import org.mybatis.dynamic.sql.render.TableAliasCalculator; import org.mybatis.dynamic.sql.util.FragmentCollector; @@ -42,12 +43,18 @@ private WhereRenderer(Builder builder) { } public Optional render() { - return whereModel.initialCriterion().map(this::renderWithInitialCriterion) + Optional whereClause = whereModel.initialCriterion().map(this::renderWithInitialCriterion) .orElseGet(this::renderWithoutInitialCriterion) .map(rc -> WhereClauseProvider.withWhereClause(rc.fragmentAndParameters().fragment()) .withParameters(rc.fragmentAndParameters().parameters()) .build() ); + + if (whereClause.isPresent() || whereModel.isUnrenderableClauseAllowed()) { + return whereClause; + } else { + throw new NonRenderingWhereClauseException(); + } } private Optional renderWithInitialCriterion(SqlCriterion initialCriterion) { diff --git a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinBaseBuilders.kt b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinBaseBuilders.kt index 8de59a32e..9a8c86210 100644 --- a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinBaseBuilders.kt +++ b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinBaseBuilders.kt @@ -20,6 +20,7 @@ import org.mybatis.dynamic.sql.AndOrCriteriaGroup import org.mybatis.dynamic.sql.ExistsPredicate import org.mybatis.dynamic.sql.SqlTable import org.mybatis.dynamic.sql.VisitableCondition +import org.mybatis.dynamic.sql.configuration.StatementConfiguration import org.mybatis.dynamic.sql.select.AbstractQueryExpressionDSL import org.mybatis.dynamic.sql.where.AbstractWhereDSL import org.mybatis.dynamic.sql.where.AbstractWhereSupport @@ -37,14 +38,19 @@ fun WhereApplier.andThen(after: WhereApplier): WhereApplier = { @MyBatisDslMarker @Suppress("TooManyFunctions") -abstract class KotlinBaseBuilder> { +abstract class KotlinBaseBuilder> { + + fun configureStatement(c: StatementConfiguration.() -> Unit) { + getDsl().configureStatement(c) + } + fun where(criteria: GroupingCriteriaReceiver): Unit = with(GroupingCriteriaCollector().apply(criteria)) { this@KotlinBaseBuilder.getDsl().where(initialCriterion, subCriteria) } fun where(criteria: List) { - this@KotlinBaseBuilder.getDsl().where(criteria) + getDsl().where(criteria) } fun and(criteria: GroupingCriteriaReceiver): Unit = @@ -53,7 +59,7 @@ abstract class KotlinBaseBuilder> { } fun and(criteria: List) { - this@KotlinBaseBuilder.getDsl().where().and(criteria) + getDsl().where().and(criteria) } fun or(criteria: GroupingCriteriaReceiver): Unit = @@ -62,7 +68,7 @@ abstract class KotlinBaseBuilder> { } fun or(criteria: List) { - this@KotlinBaseBuilder.getDsl().where().or(criteria) + getDsl().where().or(criteria) } fun applyWhere(whereApplier: WhereApplier) = whereApplier.invoke(this) diff --git a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinSubQueryBuilders.kt b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinSubQueryBuilders.kt index 3767e7aa8..c816da276 100644 --- a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinSubQueryBuilders.kt +++ b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinSubQueryBuilders.kt @@ -20,6 +20,7 @@ import org.mybatis.dynamic.sql.SqlBuilder import org.mybatis.dynamic.sql.SqlColumn import org.mybatis.dynamic.sql.select.SelectModel import org.mybatis.dynamic.sql.util.Buildable +import java.lang.NullPointerException @MyBatisDslMarker sealed class KotlinBaseSubQueryBuilder : Buildable { @@ -40,7 +41,11 @@ sealed class KotlinBaseSubQueryBuilder : Buildable { } override fun build(): SelectModel = - selectBuilder?.build()?: throw KInvalidSQLException("You must specify a select statement in a sub query") + try { + selectBuilder!!.build() + } catch (e: NullPointerException) { + throw KInvalidSQLException("You must specify a select statement in a sub query") + } } class KotlinSubQueryBuilder : KotlinBaseSubQueryBuilder() diff --git a/src/site/markdown/docs/conditions.md b/src/site/markdown/docs/conditions.md index cea2e2885..4947c39eb 100644 --- a/src/site/markdown/docs/conditions.md +++ b/src/site/markdown/docs/conditions.md @@ -200,29 +200,6 @@ of the *values* of the condition. The library comes with functions that will fil case String values to enable case insensitive queries. There are extension points to add additional filtering and mapping if you so desire. -We think it is a good thing that the library will not render invalid SQL. An "in" condition will always be dropped from -rendering if the list of values is empty. But there is some danger with this stance. Because the condition could be -dropped from the rendered SQL, more rows could be impacted than expected if the list ends up empty for whatever reason. -Our recommended solution is to make sure that you validate list values - especially if they are coming from direct user -input. Another option is to take some action when the list is empty. This can be especially helpful when you are -applying filters to the value list or using one of the built-in "when present" conditions. In that case, it is -possible that the list of values could end up empty after a validation check. - -The "In" conditions support a callback that will be invoked when the value list is empty and just before the statement -is rendered. You could use the callback to create a condition that will throw an exception when the list is empty. -For example: - -```java - private static IsIn isInRequired(Collection values) { - return IsIn.of(values).withListEmptyCallback(() -> { throw new RuntimeException("In values cannot be empty"); } ); - } - - // Alternatively, there is a convenience method in the Callback interface - private static IsIn isInRequired(Collection values) { - return IsIn.of(values).withListEmptyCallback(Callback.exceptionThrowingCallback("In values cannot be empty")); - } -``` - The following table shows the different supplied In conditions and how they will render for different sets of inputs. The table assumes the following types of input: @@ -286,3 +263,21 @@ Then the condition could be used in a query as follows: .build() .render(RenderingStrategies.MYBATIS3); ``` + +## Potential for Non Rendering Where Clauses + +An "in" condition will always be dropped from rendering if the list of values is empty. Other conditions could be +dropped from a where clause due to filtering. If all conditions fail to render, then the entire where clause will be +dropped from the rendered SQL. In general, we think it is a good thing that the library will not render invalid SQL. +But this stance does present a danger - if a where clause is dropped from the rendered SQL, then the statement could +end up impacting all rows in a table. For example, a delete statement could inadvertently delete all rows in a table. + +By default, and out of an abundance of caution, the library will not allow a statement to render if the entire where +clause will be dropped. If a where clause is coded, but fails to render, then the library will throw a +`NonRenderingWhereClauseException` by default. + +If no where clause is coded in a statement, then we assume the statement is intended to affect all rows in a table. +In that case no exception will be thrown. + +This behavior can be modified through configuration - either globally for the entire library, or for each statement +individually. See the "Configuration of the Library" page for details on configuration options. diff --git a/src/site/markdown/docs/configuration.md b/src/site/markdown/docs/configuration.md new file mode 100644 index 000000000..4796d387d --- /dev/null +++ b/src/site/markdown/docs/configuration.md @@ -0,0 +1,58 @@ +# Configuration of the Library + +This page will detail the behaviors of MyBatis Dynamic SQL that can be modified. +Configuration is available with version 1.4.1 and later of the library. + +The library can be configured globally - which will change the behavior for all statements - or each individual statement +can be configured. There are sensible defaults for all configuration values, so configuration is not strictly necessary. +If you want to change any of the default behaviors of the library, then the information on this page will help. + +Prior to version 1.4.1 of the library, there was no configurable behavior in the library. In version 1.4.1 we changed +the default behavior of the library to throw an exception if a where clause fails to render. We did this out of an +abundance of caution because the optional conditions in a where clause could lead to a statement that affected all +rows in a table (for example, a delete statement could inadvertently delete all rows in a table). If you want the library +to function as it did before version 1.4.1 , then you can change the global configuration as shown below. + +## Global Configuration + +On first use the library will initialize the global configuration. The global configuration can be specified via a property +file named `mybatis-dynamic-sql.properties` in the root of the classpath. If you wish to use a different file name, +you can specify the file name as a JVM property named `mybatis-dynamic-sql.configurationFile`. Note that the global +configuration is created one time and shared for every statement in the same JVM. + +The configuration file is a standard Java properties file. The possible values are detailed in the next section. + +## Global Configuration Properties + +| Property | Default | Meaning | +|--------------------------------|---------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| nonRenderingWhereClauseAllowed | false | If a where clause is specified, but fails to render, then the library will throw a `NonRenderingWhereClauseException` by default. If you set this value to true, then no exception will be thrown. This could enable statements to be rendered without where clauses that affect all rows in a table. | + +## Statement Configuration + +If the global configuration is not acceptable for any individual statement, you can also configure the statement in the +DSL. Consider the following statement: + +```java +DeleteStatementProvider deleteStatement = deleteFrom(animalData) + .where(id, isIn(null, 22, null).filter(Objects::nonNull).filter(i -> i != 22)) + .configureStatement(c -> c.setNonRenderingWhereClauseAllowed(true)) + .build() + .render(RenderingStrategies.MYBATIS3); +``` + +In this case, the `isIn` condition has filters that will remove all values from the condition. In that case, the +condition will not render and, subsequently, the where clause will not render. This means that the generated delete +statement would delete all rows in the table. By default, the global configuration will block this statement from +rendering and throw a `NonRenderingWhereClauseException`. If for some reason you would like to allow a statement +like this to be rendered, then you can allow it as shown above with the `configureStatement` method. + +The Kotlin DSL contains the same function: + +```kotlin +val deleteStatement = deleteFrom(person) { + where { id isEqualToWhenPresent null } + configureStatement { isNonRenderingWhereClauseAllowed = true } +} +``` + diff --git a/src/site/site.xml b/src/site/site.xml index 88110419d..d7cab962a 100644 --- a/src/site/site.xml +++ b/src/site/site.xml @@ -37,6 +37,7 @@ + diff --git a/src/test/java/examples/animal/data/AnimalDataTest.java b/src/test/java/examples/animal/data/AnimalDataTest.java index 69b4601c7..e34b2a8e7 100644 --- a/src/test/java/examples/animal/data/AnimalDataTest.java +++ b/src/test/java/examples/animal/data/AnimalDataTest.java @@ -75,6 +75,7 @@ class AnimalDataTest { void setup() throws Exception { Class.forName(JDBC_DRIVER); InputStream is = getClass().getResourceAsStream("/examples/animal/data/CreateAnimalData.sql"); + assert is != null; try (Connection connection = DriverManager.getConnection(JDBC_URL, "sa", "")) { ScriptRunner sr = new ScriptRunner(connection); sr.setLogWriter(null); @@ -651,6 +652,7 @@ void testInConditionWithEventuallyEmptyList() { SelectStatementProvider selectStatement = select(id, animalName, bodyWeight, brainWeight) .from(animalData) .where(id, isIn(null, 22, null).filter(Objects::nonNull).filter(i -> i != 22)) + .configureStatement(c -> c.setNonRenderingWhereClauseAllowed(true)) .build() .render(RenderingStrategies.MYBATIS3); @@ -766,6 +768,7 @@ void testNotInConditionWithEventuallyEmptyList() { SelectStatementProvider selectStatement = select(id, animalName, bodyWeight, brainWeight) .from(animalData) .where(id, isNotIn(null, 22, null).filter(Objects::nonNull).filter(i -> i != 22)) + .configureStatement(c -> c.setNonRenderingWhereClauseAllowed(true)) .build() .render(RenderingStrategies.MYBATIS3); diff --git a/src/test/java/examples/animal/data/OptionalConditionsAnimalDataTest.java b/src/test/java/examples/animal/data/OptionalConditionsAnimalDataTest.java index dbceeec5a..2774a9dc3 100644 --- a/src/test/java/examples/animal/data/OptionalConditionsAnimalDataTest.java +++ b/src/test/java/examples/animal/data/OptionalConditionsAnimalDataTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2020 the original author or authors. + * Copyright 2016-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -51,6 +51,7 @@ class OptionalConditionsAnimalDataTest { void setup() throws Exception { Class.forName(JDBC_DRIVER); InputStream is = getClass().getResourceAsStream("/examples/animal/data/CreateAnimalData.sql"); + assert is != null; try (Connection connection = DriverManager.getConnection(JDBC_URL, "sa", "")) { ScriptRunner sr = new ScriptRunner(connection); sr.setLogWriter(null); @@ -72,6 +73,7 @@ void testAllIgnored() { .from(animalData) .where(id, isGreaterThanWhenPresent(NULL_INTEGER)) // the where clause should not render .orderBy(id) + .configureStatement(c -> c.setNonRenderingWhereClauseAllowed(true)) .build() .render(RenderingStrategies.MYBATIS3); List animals = mapper.selectMany(selectStatement); diff --git a/src/test/java/examples/animal/data/OptionalConditionsWithPredicatesAnimalDataTest.java b/src/test/java/examples/animal/data/OptionalConditionsWithPredicatesAnimalDataTest.java index 3207a3ba2..3bf1dc847 100644 --- a/src/test/java/examples/animal/data/OptionalConditionsWithPredicatesAnimalDataTest.java +++ b/src/test/java/examples/animal/data/OptionalConditionsWithPredicatesAnimalDataTest.java @@ -57,6 +57,7 @@ class OptionalConditionsWithPredicatesAnimalDataTest { void setup() throws Exception { Class.forName(JDBC_DRIVER); InputStream is = getClass().getResourceAsStream("/examples/animal/data/CreateAnimalData.sql"); + assert is != null; try (Connection connection = DriverManager.getConnection(JDBC_URL, "sa", "")) { ScriptRunner sr = new ScriptRunner(connection); sr.setLogWriter(null); @@ -78,6 +79,7 @@ void testAllIgnored() { .from(animalData) .where(id, isGreaterThan(NULL_INTEGER).filter(Objects::nonNull)) // the where clause should not render .orderBy(id) + .configureStatement(c -> c.setNonRenderingWhereClauseAllowed(true)) .build() .render(RenderingStrategies.MYBATIS3); List animals = mapper.selectMany(selectStatement); diff --git a/src/test/java/examples/complexquery/ComplexQueryTest.java b/src/test/java/examples/complexquery/ComplexQueryTest.java index 28543469c..acbac6851 100644 --- a/src/test/java/examples/complexquery/ComplexQueryTest.java +++ b/src/test/java/examples/complexquery/ComplexQueryTest.java @@ -110,7 +110,8 @@ void testAllNull() { SelectStatementProvider search(Integer targetId, String fName, String lName) { QueryExpressionDSL.QueryExpressionWhereBuilder builder = select(id, firstName, lastName) .from(person) - .where(); + .where() + .configureStatement(c -> c.setNonRenderingWhereClauseAllowed(true)); if (targetId != null) { builder diff --git a/src/test/java/examples/emptywhere/EmptyWhereTest.java b/src/test/java/examples/emptywhere/EmptyWhereTest.java index 3bbe41d86..69807fe45 100644 --- a/src/test/java/examples/emptywhere/EmptyWhereTest.java +++ b/src/test/java/examples/emptywhere/EmptyWhereTest.java @@ -119,6 +119,7 @@ void testDeleteVariations(Variation variation) { builder.and(firstName, isEqualTo(variation.firstName).filter(Objects::nonNull)); builder.or(PersonDynamicSqlSupport.lastName, isEqualTo(variation.lastName).filter(Objects::nonNull)); + builder.configureStatement(c -> c.setNonRenderingWhereClauseAllowed(true)); DeleteStatementProvider deleteStatement = builder.build().render(RenderingStrategies.MYBATIS3); @@ -159,6 +160,7 @@ void testSelectVariations(Variation variation) { builder.and(firstName, isEqualTo(variation.firstName).filter(Objects::nonNull)); builder.or(PersonDynamicSqlSupport.lastName, isEqualTo(variation.lastName).filter(Objects::nonNull)); + builder.configureStatement(c -> c.setNonRenderingWhereClauseAllowed(true)); SelectStatementProvider selectStatement = builder.build().render(RenderingStrategies.MYBATIS3); @@ -200,6 +202,7 @@ void testJoinVariations(Variation variation) { builder.and(firstName, isEqualTo(variation.firstName).filter(Objects::nonNull)); builder.or(PersonDynamicSqlSupport.lastName, isEqualTo(variation.lastName).filter(Objects::nonNull)); + builder.configureStatement(c -> c.setNonRenderingWhereClauseAllowed(true)); SelectStatementProvider selectStatement = builder.build().render(RenderingStrategies.MYBATIS3); @@ -243,6 +246,7 @@ void testUpdateVariations(Variation variation) { builder.and(firstName, isEqualTo(variation.firstName).filter(Objects::nonNull)); builder.or(PersonDynamicSqlSupport.lastName, isEqualTo(variation.lastName).filter(Objects::nonNull)); + builder.configureStatement(c -> c.setNonRenderingWhereClauseAllowed(true)); UpdateStatementProvider updateStatement = builder.build().render(RenderingStrategies.MYBATIS3); @@ -279,6 +283,7 @@ void testWhereVariations(Variation variation) { builder.and(firstName, isEqualTo(variation.firstName).filter(Objects::nonNull)); builder.or(PersonDynamicSqlSupport.lastName, isEqualTo(variation.lastName).filter(Objects::nonNull)); + builder.configureStatement(c -> c.setNonRenderingWhereClauseAllowed(true)); WhereClauseProvider whereClause = builder.build().render(RenderingStrategies.MYBATIS3); diff --git a/src/test/java/examples/joins/JoinMapperTest.java b/src/test/java/examples/joins/JoinMapperTest.java index a8e599350..61118911f 100644 --- a/src/test/java/examples/joins/JoinMapperTest.java +++ b/src/test/java/examples/joins/JoinMapperTest.java @@ -163,6 +163,7 @@ void testCompoundJoin2() { SelectStatementProvider selectStatement = select(orderMaster.orderId, orderDate, orderDetail.lineNumber, orderDetail.description, orderDetail.quantity) .from(orderMaster, "om") .join(orderDetail, "od").on(orderMaster.orderId, equalTo(orderDetail.orderId)) + .configureStatement(c -> c.setNonRenderingWhereClauseAllowed(true)) .and(orderMaster.orderId, equalTo(orderDetail.orderId)) .build() .render(RenderingStrategies.MYBATIS3); diff --git a/src/test/java/issues/gh105/Issue105Test.java b/src/test/java/issues/gh105/Issue105Test.java index 7d4aa99df..211aaf6e6 100644 --- a/src/test/java/issues/gh105/Issue105Test.java +++ b/src/test/java/issues/gh105/Issue105Test.java @@ -99,6 +99,7 @@ void testFuzzyLikeBothNull() { .from(person) .where(firstName, isLike(fName).filter(Objects::nonNull).map(SearchUtils::addWildcards)) .and(lastName, isLike(lName).filter(Objects::nonNull).map(SearchUtils::addWildcards)) + .configureStatement(c -> c.setNonRenderingWhereClauseAllowed(true)) .build() .render(RenderingStrategies.MYBATIS3); @@ -527,6 +528,7 @@ void testBetweenTransformWithNull() { SelectStatementProvider selectStatement = select(id, firstName, lastName) .from(person) .where(age, isBetween(1).and((Integer) null).filter(Predicates.bothPresent()).map(i1 -> i1 + 1, i2 -> i2 + 2)) + .configureStatement(c -> c.setNonRenderingWhereClauseAllowed(true)) .build() .render(RenderingStrategies.MYBATIS3); @@ -542,6 +544,7 @@ void testBetweenWhenPresentTransformWithNull() { SelectStatementProvider selectStatement = select(id, firstName, lastName) .from(person) .where(age, isBetweenWhenPresent(1).and((Integer) null).map(i1 -> i1 + 1, i2 -> i2 + 2)) + .configureStatement(c -> c.setNonRenderingWhereClauseAllowed(true)) .build() .render(RenderingStrategies.MYBATIS3); @@ -557,6 +560,7 @@ void testEqualTransformWithNull() { SelectStatementProvider selectStatement = select(id, firstName, lastName) .from(person) .where(age, isEqualTo((Integer) null).filter(Objects::nonNull).map(i -> i + 1)) + .configureStatement(c -> c.setNonRenderingWhereClauseAllowed(true)) .build() .render(RenderingStrategies.MYBATIS3); @@ -572,6 +576,7 @@ void testEqualWhenPresentTransformWithNull() { SelectStatementProvider selectStatement = select(id, firstName, lastName) .from(person) .where(age, isEqualToWhenPresent((Integer) null).map(i -> i + 1)) + .configureStatement(c -> c.setNonRenderingWhereClauseAllowed(true)) .build() .render(RenderingStrategies.MYBATIS3); @@ -587,6 +592,7 @@ void testGreaterThanTransformWithNull() { SelectStatementProvider selectStatement = select(id, firstName, lastName) .from(person) .where(age, isGreaterThan((Integer) null).filter(Objects::nonNull).map(i -> i + 1)) + .configureStatement(c -> c.setNonRenderingWhereClauseAllowed(true)) .build() .render(RenderingStrategies.MYBATIS3); @@ -602,6 +608,7 @@ void testGreaterThanOrEqualTransformWithNull() { SelectStatementProvider selectStatement = select(id, firstName, lastName) .from(person) .where(age, isGreaterThanOrEqualTo((Integer) null).filter(Objects::nonNull).map(i -> i + 1)) + .configureStatement(c -> c.setNonRenderingWhereClauseAllowed(true)) .build() .render(RenderingStrategies.MYBATIS3); @@ -617,6 +624,7 @@ void testGreaterThanOrEqualWhenPresentTransformWithNull() { SelectStatementProvider selectStatement = select(id, firstName, lastName) .from(person) .where(age, isGreaterThanOrEqualToWhenPresent((Integer) null).map(i -> i + 1)) + .configureStatement(c -> c.setNonRenderingWhereClauseAllowed(true)) .build() .render(RenderingStrategies.MYBATIS3); @@ -632,6 +640,7 @@ void testGreaterThanWhenPresentTransformWithNull() { SelectStatementProvider selectStatement = select(id, firstName, lastName) .from(person) .where(age, isGreaterThanWhenPresent((Integer) null).map(i -> i + 1)) + .configureStatement(c -> c.setNonRenderingWhereClauseAllowed(true)) .build() .render(RenderingStrategies.MYBATIS3); @@ -647,6 +656,7 @@ void testLessThanTransformWithNull() { SelectStatementProvider selectStatement = select(id, firstName, lastName) .from(person) .where(age, isLessThan((Integer) null).filter(Objects::nonNull).map(i -> i + 1)) + .configureStatement(c -> c.setNonRenderingWhereClauseAllowed(true)) .build() .render(RenderingStrategies.MYBATIS3); @@ -662,6 +672,7 @@ void testLessThanOrEqualTransformWithNull() { SelectStatementProvider selectStatement = select(id, firstName, lastName) .from(person) .where(age, isLessThanOrEqualTo((Integer) null).filter(Objects::nonNull).map(i -> i + 1)) + .configureStatement(c -> c.setNonRenderingWhereClauseAllowed(true)) .build() .render(RenderingStrategies.MYBATIS3); @@ -677,6 +688,7 @@ void testLessThanOrEqualWhenPresentTransformWithNull() { SelectStatementProvider selectStatement = select(id, firstName, lastName) .from(person) .where(age, isLessThanOrEqualToWhenPresent((Integer) null).map(i -> i + 1)) + .configureStatement(c -> c.setNonRenderingWhereClauseAllowed(true)) .build() .render(RenderingStrategies.MYBATIS3); @@ -692,6 +704,7 @@ void testLessThanWhenPresentTransformWithNull() { SelectStatementProvider selectStatement = select(id, firstName, lastName) .from(person) .where(age, isLessThanWhenPresent((Integer) null).map(i -> i + 1)) + .configureStatement(c -> c.setNonRenderingWhereClauseAllowed(true)) .build() .render(RenderingStrategies.MYBATIS3); @@ -707,6 +720,7 @@ void testLikeTransformWithNull() { SelectStatementProvider selectStatement = select(id, firstName, lastName) .from(person) .where(firstName, isLike((String) null).filter(Objects::nonNull).map(SearchUtils::addWildcards)) + .configureStatement(c -> c.setNonRenderingWhereClauseAllowed(true)) .build() .render(RenderingStrategies.MYBATIS3); @@ -722,6 +736,7 @@ void testLikeCaseInsensitiveTransformWithNull() { SelectStatementProvider selectStatement = select(id, firstName, lastName) .from(person) .where(firstName, isLikeCaseInsensitive((String) null).filter(Objects::nonNull).map(SearchUtils::addWildcards)) + .configureStatement(c -> c.setNonRenderingWhereClauseAllowed(true)) .build() .render(RenderingStrategies.MYBATIS3); @@ -737,6 +752,7 @@ void testLikeCaseInsensitiveWhenPresentTransformWithNull() { SelectStatementProvider selectStatement = select(id, firstName, lastName) .from(person) .where(firstName, isLikeCaseInsensitiveWhenPresent((String) null).map(SearchUtils::addWildcards)) + .configureStatement(c -> c.setNonRenderingWhereClauseAllowed(true)) .build() .render(RenderingStrategies.MYBATIS3); @@ -752,6 +768,7 @@ void testLikeWhenPresentTransformWithNull() { SelectStatementProvider selectStatement = select(id, firstName, lastName) .from(person) .where(firstName, isLikeWhenPresent((String) null).map(SearchUtils::addWildcards)) + .configureStatement(c -> c.setNonRenderingWhereClauseAllowed(true)) .build() .render(RenderingStrategies.MYBATIS3); @@ -767,6 +784,7 @@ void testNotBetweenTransformWithNull() { SelectStatementProvider selectStatement = select(id, firstName, lastName) .from(person) .where(age, isNotBetween((Integer) null).and(10).filter(Predicates.bothPresent()).map(i1 -> i1 + 1, i2 -> i2 + 2)) + .configureStatement(c -> c.setNonRenderingWhereClauseAllowed(true)) .build() .render(RenderingStrategies.MYBATIS3); @@ -782,6 +800,7 @@ void testNotBetweenWhenPresentTransformWithNull() { SelectStatementProvider selectStatement = select(id, firstName, lastName) .from(person) .where(age, isNotBetweenWhenPresent(1).and((Integer) null).map(i1 -> i1 + 1, i2 -> i2 + 2)) + .configureStatement(c -> c.setNonRenderingWhereClauseAllowed(true)) .build() .render(RenderingStrategies.MYBATIS3); @@ -797,6 +816,7 @@ void testNotEqualTransformWithNull() { SelectStatementProvider selectStatement = select(id, firstName, lastName) .from(person) .where(age, isNotEqualTo((Integer) null).filter(Objects::nonNull).map(i -> i + 1)) + .configureStatement(c -> c.setNonRenderingWhereClauseAllowed(true)) .build() .render(RenderingStrategies.MYBATIS3); @@ -812,6 +832,7 @@ void testNotEqualWhenPresentTransformWithNull() { SelectStatementProvider selectStatement = select(id, firstName, lastName) .from(person) .where(age, isNotEqualToWhenPresent((Integer) null).map(i -> i + 1)) + .configureStatement(c -> c.setNonRenderingWhereClauseAllowed(true)) .build() .render(RenderingStrategies.MYBATIS3); @@ -827,6 +848,7 @@ void testNotLikeTransformWithNull() { SelectStatementProvider selectStatement = select(id, firstName, lastName) .from(person) .where(firstName, isNotLike((String) null).filter(Objects::nonNull).map(SearchUtils::addWildcards)) + .configureStatement(c -> c.setNonRenderingWhereClauseAllowed(true)) .build() .render(RenderingStrategies.MYBATIS3); @@ -842,6 +864,7 @@ void testNotLikeCaseInsensitiveTransformWithNull() { SelectStatementProvider selectStatement = select(id, firstName, lastName) .from(person) .where(firstName, isNotLikeCaseInsensitive((String) null).filter(Objects::nonNull).map(SearchUtils::addWildcards)) + .configureStatement(c -> c.setNonRenderingWhereClauseAllowed(true)) .build() .render(RenderingStrategies.MYBATIS3); @@ -857,6 +880,7 @@ void testNotLikeCaseInsensitiveWhenPresentTransformWithNull() { SelectStatementProvider selectStatement = select(id, firstName, lastName) .from(person) .where(firstName, isNotLikeCaseInsensitiveWhenPresent((String) null).map(SearchUtils::addWildcards)) + .configureStatement(c -> c.setNonRenderingWhereClauseAllowed(true)) .build() .render(RenderingStrategies.MYBATIS3); @@ -872,6 +896,7 @@ void testNotLikeWhenPresentTransformWithNull() { SelectStatementProvider selectStatement = select(id, firstName, lastName) .from(person) .where(firstName, isNotLikeWhenPresent((String) null).map(SearchUtils::addWildcards)) + .configureStatement(c -> c.setNonRenderingWhereClauseAllowed(true)) .build() .render(RenderingStrategies.MYBATIS3); diff --git a/src/test/java/org/mybatis/dynamic/sql/StatementConfigurationTest.java b/src/test/java/org/mybatis/dynamic/sql/StatementConfigurationTest.java new file mode 100644 index 000000000..dfe489977 --- /dev/null +++ b/src/test/java/org/mybatis/dynamic/sql/StatementConfigurationTest.java @@ -0,0 +1,173 @@ +/* + * Copyright 2016-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.mybatis.dynamic.sql; + +import static examples.complexquery.PersonDynamicSqlSupport.firstName; +import static examples.complexquery.PersonDynamicSqlSupport.id; +import static examples.complexquery.PersonDynamicSqlSupport.lastName; +import static examples.complexquery.PersonDynamicSqlSupport.person; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.mybatis.dynamic.sql.SqlBuilder.countFrom; +import static org.mybatis.dynamic.sql.SqlBuilder.deleteFrom; +import static org.mybatis.dynamic.sql.SqlBuilder.select; +import static org.mybatis.dynamic.sql.SqlBuilder.update; + +import org.junit.jupiter.api.Test; +import org.mybatis.dynamic.sql.delete.DeleteModel; +import org.mybatis.dynamic.sql.delete.render.DeleteStatementProvider; +import org.mybatis.dynamic.sql.exception.NonRenderingWhereClauseException; +import org.mybatis.dynamic.sql.render.RenderingStrategies; +import org.mybatis.dynamic.sql.select.SelectModel; +import org.mybatis.dynamic.sql.select.render.SelectStatementProvider; +import org.mybatis.dynamic.sql.update.UpdateModel; +import org.mybatis.dynamic.sql.update.render.UpdateStatementProvider; + +class StatementConfigurationTest { + @Test + void testCountWhereCalledButNoCriteriaThrowsException() { + SelectModel selectModel = countFrom(person) + .where() + .build(); + + assertThatExceptionOfType(NonRenderingWhereClauseException.class).isThrownBy(() -> + selectModel.render(RenderingStrategies.MYBATIS3) + ); + } + + @Test + void testCountWhereCalledButNoCriteriaRequiresConfiguration() { + SelectStatementProvider selectStatement = countFrom(person) + .where() + .configureStatement(c -> c.setNonRenderingWhereClauseAllowed(true)) + .build() + .render(RenderingStrategies.MYBATIS3); + + assertThat(selectStatement.getSelectStatement()).isEqualTo("select count(*) from Person"); + } + + @Test + void testCountWhereNotCalledIsOK() { + SelectStatementProvider selectStatement = countFrom(person) + .build() + .render(RenderingStrategies.MYBATIS3); + + assertThat(selectStatement.getSelectStatement()).isEqualTo("select count(*) from Person"); + } + + @Test + void testDeleteWhereCalledButNoCriteriaThrowsException() { + DeleteModel deleteModel = deleteFrom(person) + .where() + .build(); + + assertThatExceptionOfType(NonRenderingWhereClauseException.class).isThrownBy(() -> + deleteModel.render(RenderingStrategies.MYBATIS3) + ); + } + + @Test + void testDeleteWhereCalledButNoCriteriaRequiresConfiguration() { + DeleteStatementProvider deleteStatement = deleteFrom(person) + .where() + .configureStatement(c -> c.setNonRenderingWhereClauseAllowed(true)) + .build() + .render(RenderingStrategies.MYBATIS3); + + assertThat(deleteStatement.getDeleteStatement()).isEqualTo("delete from Person"); + } + + @Test + void testDeleteWhereNotCalledIsOK() { + DeleteStatementProvider deleteStatement = deleteFrom(person) + .build() + .render(RenderingStrategies.MYBATIS3); + + assertThat(deleteStatement.getDeleteStatement()).isEqualTo("delete from Person"); + } + + @Test + void testSelectWhereCalledButNoCriteriaThrowsException() { + SelectModel selectModel = select(id, firstName, lastName) + .from(person) + .where() + .build(); + + assertThatExceptionOfType(NonRenderingWhereClauseException.class).isThrownBy(() -> + selectModel.render(RenderingStrategies.MYBATIS3) + ); + } + + @Test + void testSelectWhereCalledButNoCriteriaRequiresConfiguration() { + SelectStatementProvider selectStatement = select(id, firstName, lastName) + .from(person) + .where() + .configureStatement(c -> c.setNonRenderingWhereClauseAllowed(true)) + .build() + .render(RenderingStrategies.MYBATIS3); + + assertThat(selectStatement.getSelectStatement()) + .isEqualTo("select person_id, first_name, last_name from Person"); + } + + @Test + void testSelectWhereNotCalledIsOK() { + SelectStatementProvider selectStatement = select(id, firstName, lastName) + .from(person) + .build() + .render(RenderingStrategies.MYBATIS3); + + assertThat(selectStatement.getSelectStatement()) + .isEqualTo("select person_id, first_name, last_name from Person"); + } + + @Test + void testUpdateWhereCalledButNoCriteriaThrowsException() { + UpdateModel updateModel = update(person) + .set(id).equalTo(1) + .where() + .build(); + + assertThatExceptionOfType(NonRenderingWhereClauseException.class).isThrownBy(() -> + updateModel.render(RenderingStrategies.MYBATIS3) + ); + } + + @Test + void testUpdateWhereCalledButNoCriteriaRequiresConfiguration() { + UpdateStatementProvider updateStatement = update(person) + .set(id).equalTo(1) + .where() + .configureStatement(c -> c.setNonRenderingWhereClauseAllowed(true)) + .build() + .render(RenderingStrategies.MYBATIS3); + + assertThat(updateStatement.getUpdateStatement()) + .isEqualTo("update Person set person_id = #{parameters.p1}"); + } + + @Test + void testUpdateWhereNotCalledIsOK() { + UpdateStatementProvider updateStatement = update(person) + .set(id).equalTo(1) + .build() + .render(RenderingStrategies.MYBATIS3); + + assertThat(updateStatement.getUpdateStatement()) + .isEqualTo("update Person set person_id = #{parameters.p1}"); + } +} diff --git a/src/test/java/org/mybatis/dynamic/sql/configuration/GlobalConfigurationTest.java b/src/test/java/org/mybatis/dynamic/sql/configuration/GlobalConfigurationTest.java new file mode 100644 index 000000000..1628e3086 --- /dev/null +++ b/src/test/java/org/mybatis/dynamic/sql/configuration/GlobalConfigurationTest.java @@ -0,0 +1,96 @@ +/* + * Copyright 2016-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.mybatis.dynamic.sql.configuration; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Handler; +import java.util.logging.LogRecord; +import java.util.logging.Logger; + +import org.junit.jupiter.api.Test; + +class GlobalConfigurationTest { + @Test + void testAlternateConfigurationFileChangesDefaults() { + System.setProperty(GlobalConfiguration.CONFIGURATION_FILE_PROPERTY, "defaultTrue.properties"); + GlobalConfiguration configuration = new GlobalConfiguration(); + System.clearProperty(GlobalConfiguration.CONFIGURATION_FILE_PROPERTY); + + assertThat(configuration.isIsNonRenderingWhereClauseAllowed()).isTrue(); + } + + @Test + void testMissingPropertyUsesDefaults() { + System.setProperty(GlobalConfiguration.CONFIGURATION_FILE_PROPERTY, "empty.properties"); + GlobalConfiguration configuration = new GlobalConfiguration(); + System.clearProperty(GlobalConfiguration.CONFIGURATION_FILE_PROPERTY); + + assertThat(configuration.isIsNonRenderingWhereClauseAllowed()).isFalse(); + } + + @Test + void testMissingPropertyFileUsesDefaults() { + System.setProperty(GlobalConfiguration.CONFIGURATION_FILE_PROPERTY, "apfbsglf.properties"); + GlobalConfiguration configuration = new GlobalConfiguration(); + System.clearProperty(GlobalConfiguration.CONFIGURATION_FILE_PROPERTY); + + assertThat(configuration.isIsNonRenderingWhereClauseAllowed()).isFalse(); + } + + @Test + void testBadPropertyFile() throws IOException { + GlobalConfiguration configuration = new GlobalConfiguration(); + InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream("empty.properties"); + assert inputStream != null; + inputStream.close(); + + TestLogHandler testLogHandler = new TestLogHandler(); + Logger logger = Logger.getLogger(GlobalConfiguration.class.getName()); + // we only want to use our handler for this test, so we don't pollute the test output + logger.setUseParentHandlers(false); + logger.addHandler(testLogHandler); + + configuration.loadProperties(inputStream, "empty.properties"); + assertThat(testLogHandler.records).hasSize(1); + assertThat(testLogHandler.getRecords().get(0).getMessage()) + .isEqualTo("IOException reading property file \"empty.properties\""); + } + + public static class TestLogHandler extends Handler { + + private final List records = new ArrayList<>(); + + public List getRecords() { + return records; + } + + @Override + public void publish(LogRecord record) { + records.add(record); + } + + @Override + public void flush() {} + + @Override + public void close() {} + } +} diff --git a/src/test/java/org/mybatis/dynamic/sql/select/SelectStatementTest.java b/src/test/java/org/mybatis/dynamic/sql/select/SelectStatementTest.java index 571caf9a8..70c279c57 100644 --- a/src/test/java/org/mybatis/dynamic/sql/select/SelectStatementTest.java +++ b/src/test/java/org/mybatis/dynamic/sql/select/SelectStatementTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2021 the original author or authors. + * Copyright 2016-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -398,6 +398,7 @@ void testInWhenPresentNullList() { SelectStatementProvider selectStatement = select(column1, column3) .from(table) .where(column3, isInWhenPresent((Collection) null)) + .configureStatement(c -> c.setNonRenderingWhereClauseAllowed(true)) .build() .render(RenderingStrategies.MYBATIS3); @@ -409,6 +410,7 @@ void testInCaseInsensitiveWhenPresentNullList() { SelectStatementProvider selectStatement = select(column1, column3) .from(table) .where(column3, isInCaseInsensitiveWhenPresent((Collection) null)) + .configureStatement(c -> c.setNonRenderingWhereClauseAllowed(true)) .build() .render(RenderingStrategies.MYBATIS3); @@ -420,6 +422,7 @@ void testNotInWhenPresentNullList() { SelectStatementProvider selectStatement = select(column1, column3) .from(table) .where(column3, isNotInWhenPresent((Collection) null)) + .configureStatement(c -> c.setNonRenderingWhereClauseAllowed(true)) .build() .render(RenderingStrategies.MYBATIS3); @@ -431,6 +434,7 @@ void testNotInCaseInsensitiveWhenPresentNullList() { SelectStatementProvider selectStatement = select(column1, column3) .from(table) .where(column3, isNotInCaseInsensitiveWhenPresent((Collection) null)) + .configureStatement(c -> c.setNonRenderingWhereClauseAllowed(true)) .build() .render(RenderingStrategies.MYBATIS3); diff --git a/src/test/java/org/mybatis/dynamic/sql/where/render/OptionalCriterionRenderTest.java b/src/test/java/org/mybatis/dynamic/sql/where/render/OptionalCriterionRenderTest.java index 00cf36dd6..0e0cdcc30 100644 --- a/src/test/java/org/mybatis/dynamic/sql/where/render/OptionalCriterionRenderTest.java +++ b/src/test/java/org/mybatis/dynamic/sql/where/render/OptionalCriterionRenderTest.java @@ -38,6 +38,7 @@ void testNoRenderableCriteria() { Integer nullId = null; WhereClauseProvider whereClause = where(id, isEqualToWhenPresent(nullId)) + .configureStatement(c -> c.setNonRenderingWhereClauseAllowed(true)) .build() .render(RenderingStrategies.SPRING_NAMED_PARAMETER); @@ -52,6 +53,7 @@ void testNoRenderableCriteriaWithIf() { Integer nullId = null; WhereClauseProvider whereClause = where(id, isEqualTo(nullId).filter(Objects::nonNull)) + .configureStatement(c -> c.setNonRenderingWhereClauseAllowed(true)) .build() .render(RenderingStrategies.SPRING_NAMED_PARAMETER); @@ -64,6 +66,7 @@ void testNoRenderableCriteriaWithIf() { @Test void testDisabledIsNull() { WhereClauseProvider whereClause = where(id, isNull().filter(() -> false)) + .configureStatement(c -> c.setNonRenderingWhereClauseAllowed(true)) .build() .render(RenderingStrategies.SPRING_NAMED_PARAMETER); @@ -88,6 +91,7 @@ void testEnabledIsNull() { @Test void testDisabledIsNotNull() { WhereClauseProvider whereClause = where(id, isNotNull().filter(() -> false)) + .configureStatement(c -> c.setNonRenderingWhereClauseAllowed(true)) .build() .render(RenderingStrategies.SPRING_NAMED_PARAMETER); @@ -308,6 +312,7 @@ void testCollapsingCriteriaGroup1() { WhereClauseProvider whereClause = where( group(firstName, isEqualToWhenPresent(name1)), or(lastName, isEqualToWhenPresent(name1))) + .configureStatement(c -> c.setNonRenderingWhereClauseAllowed(true)) .build() .render(RenderingStrategies.SPRING_NAMED_PARAMETER); diff --git a/src/test/kotlin/examples/kotlin/spring/canonical/InfixElementsTest.kt b/src/test/kotlin/examples/kotlin/spring/canonical/InfixElementsTest.kt index d0b144ef6..0e5593550 100644 --- a/src/test/kotlin/examples/kotlin/spring/canonical/InfixElementsTest.kt +++ b/src/test/kotlin/examples/kotlin/spring/canonical/InfixElementsTest.kt @@ -28,16 +28,21 @@ import org.mybatis.dynamic.sql.util.kotlin.KInvalidSQLException import org.mybatis.dynamic.sql.util.kotlin.elements.isLike import org.mybatis.dynamic.sql.util.kotlin.elements.stringConstant import org.mybatis.dynamic.sql.util.kotlin.elements.upper +import org.mybatis.dynamic.sql.util.kotlin.mybatis3.count +import org.mybatis.dynamic.sql.util.kotlin.spring.countFrom +import org.mybatis.dynamic.sql.util.kotlin.spring.delete +import org.mybatis.dynamic.sql.util.kotlin.spring.deleteFrom import org.mybatis.dynamic.sql.util.kotlin.spring.select import org.mybatis.dynamic.sql.util.kotlin.spring.selectList import org.mybatis.dynamic.sql.util.kotlin.spring.selectOne +import org.mybatis.dynamic.sql.util.kotlin.spring.update import org.springframework.beans.factory.annotation.Autowired import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate import org.springframework.test.context.junit.jupiter.SpringJUnitConfig import org.springframework.transaction.annotation.Transactional @Suppress("LargeClass") -@SpringJUnitConfig(classes = [SpringConfiguration::class]) +@SpringJUnitConfig(SpringConfiguration::class) @Transactional open class InfixElementsTest { @Autowired @@ -99,6 +104,7 @@ open class InfixElementsTest { from(person) where { id isEqualToWhenPresent null } orderBy(id) + configureStatement { isNonRenderingWhereClauseAllowed = true } } assertThat(selectStatement.selectStatement).isEqualTo( @@ -129,12 +135,29 @@ open class InfixElementsTest { assertThat(rows[0]).isEqualTo("Fred") } + @Test + fun testDeleteIsNotEqualToWhenPresentNull() { + val deleteStatement = deleteFrom(person) { + where { id isNotEqualToWhenPresent null } + configureStatement { isNonRenderingWhereClauseAllowed = true } + } + + assertThat(deleteStatement.deleteStatement).isEqualTo( + "delete from Person" + ) + + val rows = template.delete(deleteStatement) + + assertThat(rows).isEqualTo(6) + } + @Test fun testIsNotEqualToWhenPresentNull() { val selectStatement = select(firstName) { from(person) where { id isNotEqualToWhenPresent null } orderBy(id) + configureStatement { isNonRenderingWhereClauseAllowed = true } } assertThat(selectStatement.selectStatement).isEqualTo( @@ -171,6 +194,7 @@ open class InfixElementsTest { from(person) where { id isGreaterThanWhenPresent null } orderBy(id) + configureStatement { isNonRenderingWhereClauseAllowed = true } } assertThat(selectStatement.selectStatement).isEqualTo( @@ -207,6 +231,7 @@ open class InfixElementsTest { from(person) where { id isGreaterThanOrEqualToWhenPresent null } orderBy(id) + configureStatement { isNonRenderingWhereClauseAllowed = true } } assertThat(selectStatement.selectStatement).isEqualTo( @@ -243,6 +268,7 @@ open class InfixElementsTest { from(person) where { id isGreaterThanWhenPresent null } orderBy(id) + configureStatement { isNonRenderingWhereClauseAllowed = true } } assertThat(selectStatement.selectStatement).isEqualTo( @@ -297,6 +323,7 @@ open class InfixElementsTest { from(person) where { id isLessThanOrEqualToWhenPresent null } orderBy(id) + configureStatement { isNonRenderingWhereClauseAllowed = true } } assertThat(selectStatement.selectStatement).isEqualTo( @@ -465,6 +492,7 @@ open class InfixElementsTest { from(person) where { id isBetweenWhenPresent null and 3 } orderBy(id) + configureStatement { isNonRenderingWhereClauseAllowed = true } } assertThat(selectStatement.selectStatement).isEqualTo( @@ -483,6 +511,7 @@ open class InfixElementsTest { from(person) where { id isBetweenWhenPresent 2 and null } orderBy(id) + configureStatement { isNonRenderingWhereClauseAllowed = true } } assertThat(selectStatement.selectStatement).isEqualTo( @@ -501,6 +530,7 @@ open class InfixElementsTest { from(person) where { id isBetweenWhenPresent null and null } orderBy(id) + configureStatement { isNonRenderingWhereClauseAllowed = true } } assertThat(selectStatement.selectStatement).isEqualTo( @@ -555,6 +585,7 @@ open class InfixElementsTest { from(person) where { id isNotBetweenWhenPresent null and 3 } orderBy(id) + configureStatement { isNonRenderingWhereClauseAllowed = true } } assertThat(selectStatement.selectStatement).isEqualTo( @@ -573,6 +604,7 @@ open class InfixElementsTest { from(person) where { id isNotBetweenWhenPresent 2 and null } orderBy(id) + configureStatement { isNonRenderingWhereClauseAllowed = true } } assertThat(selectStatement.selectStatement).isEqualTo( @@ -591,6 +623,7 @@ open class InfixElementsTest { from(person) where { id isNotBetweenWhenPresent null and null } orderBy(id) + configureStatement { isNonRenderingWhereClauseAllowed = true } } assertThat(selectStatement.selectStatement).isEqualTo( @@ -627,6 +660,7 @@ open class InfixElementsTest { from(person) where { firstName isLikeWhenPresent null } orderBy(id) + configureStatement { isNonRenderingWhereClauseAllowed = true } } assertThat(selectStatement.selectStatement).isEqualTo( @@ -681,6 +715,7 @@ open class InfixElementsTest { from(person) where { firstName isNotLikeWhenPresent null } orderBy(id) + configureStatement { isNonRenderingWhereClauseAllowed = true } } assertThat(selectStatement.selectStatement).isEqualTo( @@ -735,6 +770,7 @@ open class InfixElementsTest { from(person) where { firstName isLikeCaseInsensitiveWhenPresent null } orderBy(id) + configureStatement { isNonRenderingWhereClauseAllowed = true } } assertThat(selectStatement.selectStatement).isEqualTo( @@ -789,6 +825,7 @@ open class InfixElementsTest { from(person) where { firstName isNotLikeCaseInsensitiveWhenPresent null } orderBy(id) + configureStatement { isNonRenderingWhereClauseAllowed = true } } assertThat(selectStatement.selectStatement).isEqualTo( @@ -843,6 +880,7 @@ open class InfixElementsTest { from(person) where { firstName.isInCaseInsensitiveWhenPresent(null, null) } orderBy(id) + configureStatement { isNonRenderingWhereClauseAllowed = true } } assertThat(selectStatement.selectStatement).isEqualTo( @@ -897,6 +935,7 @@ open class InfixElementsTest { from(person) where { firstName.isNotInCaseInsensitiveWhenPresent(null, null) } orderBy(id) + configureStatement { isNonRenderingWhereClauseAllowed = true } } assertThat(selectStatement.selectStatement).isEqualTo( @@ -922,6 +961,7 @@ open class InfixElementsTest { .map { "%$it%" }) } orderBy(id) + configureStatement { isNonRenderingWhereClauseAllowed = true } } assertThat(selectStatement.selectStatement).isEqualTo( @@ -1192,4 +1232,36 @@ open class InfixElementsTest { assertThat(rows).hasSize(2) assertThat(rows[0]).isEqualTo("Pebbles") } + + @Test + fun testUpdate() { + val updateStatement = update(person) { + set(id) equalTo 1 + // following should have no impact - where clause not specified + configureStatement { isNonRenderingWhereClauseAllowed = false } + } + + assertThat(updateStatement.updateStatement).isEqualTo("update Person set id = :p1") + } + + @Test + fun testCount() { + val selectStatement = countFrom(person) { + // following should have no impact - where clause not specified + configureStatement { isNonRenderingWhereClauseAllowed = false } + } + + assertThat(selectStatement.selectStatement).isEqualTo("select count(*) from Person") + } + + @Test + fun testQueryExpression() { + val selectStatement = select(id) { + from(person) + // following should have no impact - where clause not specified + configureStatement { isNonRenderingWhereClauseAllowed = false } + } + + assertThat(selectStatement.selectStatement).isEqualTo("select id from Person") + } } diff --git a/src/test/kotlin/examples/kotlin/spring/canonical/KotlinElementsTest.kt b/src/test/kotlin/examples/kotlin/spring/canonical/KotlinElementsTest.kt index bd6cb3d4a..8027116c0 100644 --- a/src/test/kotlin/examples/kotlin/spring/canonical/KotlinElementsTest.kt +++ b/src/test/kotlin/examples/kotlin/spring/canonical/KotlinElementsTest.kt @@ -57,7 +57,7 @@ import org.springframework.test.context.junit.jupiter.SpringJUnitConfig import org.springframework.transaction.annotation.Transactional @Suppress("LargeClass", "MaxLineLength") -@SpringJUnitConfig(classes = [SpringConfiguration::class]) +@SpringJUnitConfig(SpringConfiguration::class) @Transactional open class KotlinElementsTest { @Autowired @@ -335,6 +335,7 @@ open class KotlinElementsTest { from(person) where { id (isBetweenWhenPresent(null).and(3)) } orderBy(id) + configureStatement { isNonRenderingWhereClauseAllowed = true } } assertThat(selectStatement.selectStatement).isEqualTo( @@ -353,6 +354,7 @@ open class KotlinElementsTest { from(person) where { id (isBetweenWhenPresent(2).and(null)) } orderBy(id) + configureStatement { isNonRenderingWhereClauseAllowed = true } } assertThat(selectStatement.selectStatement).isEqualTo( @@ -371,6 +373,7 @@ open class KotlinElementsTest { from(person) where { id (isBetweenWhenPresent(null).and(null)) } orderBy(id) + configureStatement { isNonRenderingWhereClauseAllowed = true } } assertThat(selectStatement.selectStatement).isEqualTo( @@ -425,6 +428,7 @@ open class KotlinElementsTest { from(person) where { id (isNotBetweenWhenPresent(null).and(3)) } orderBy(id) + configureStatement { isNonRenderingWhereClauseAllowed = true } } assertThat(selectStatement.selectStatement).isEqualTo( @@ -443,6 +447,7 @@ open class KotlinElementsTest { from(person) where { id (isNotBetweenWhenPresent(2).and(null)) } orderBy(id) + configureStatement { isNonRenderingWhereClauseAllowed = true } } assertThat(selectStatement.selectStatement).isEqualTo( @@ -461,6 +466,7 @@ open class KotlinElementsTest { from(person) where { id (isNotBetweenWhenPresent(null).and(null)) } orderBy(id) + configureStatement { isNonRenderingWhereClauseAllowed = true } } assertThat(selectStatement.selectStatement).isEqualTo( @@ -551,6 +557,7 @@ open class KotlinElementsTest { from(person) where { firstName (isInCaseInsensitiveWhenPresent(null, null)) } orderBy(id) + configureStatement { isNonRenderingWhereClauseAllowed = true } } assertThat(selectStatement.selectStatement).isEqualTo( @@ -605,6 +612,7 @@ open class KotlinElementsTest { from(person) where { firstName (isNotInCaseInsensitiveWhenPresent(null, null)) } orderBy(id) + configureStatement { isNonRenderingWhereClauseAllowed = true } } assertThat(selectStatement.selectStatement).isEqualTo( diff --git a/src/test/resources/defaultTrue.properties b/src/test/resources/defaultTrue.properties new file mode 100644 index 000000000..e6f3f74fa --- /dev/null +++ b/src/test/resources/defaultTrue.properties @@ -0,0 +1,17 @@ +# +# Copyright 2016-2022 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +nonRenderingWhereClauseAllowed=true diff --git a/src/test/resources/empty.properties b/src/test/resources/empty.properties new file mode 100644 index 000000000..b6782c516 --- /dev/null +++ b/src/test/resources/empty.properties @@ -0,0 +1,17 @@ +# +# Copyright 2016-2022 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# intentionally empty for testing! diff --git a/src/test/resources/mybatis-dynamic-sql.properties b/src/test/resources/mybatis-dynamic-sql.properties new file mode 100644 index 000000000..8d5370a3d --- /dev/null +++ b/src/test/resources/mybatis-dynamic-sql.properties @@ -0,0 +1,17 @@ +# +# Copyright 2016-2022 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +nonRenderingWhereClauseAllowed=false