diff --git a/.github/dependabot.yml b/.github/dependabot.yml index a217b347e..c28504fbe 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,3 +1,19 @@ +# +# Copyright 2016-2020 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. +# + version: 2 updates: - package-ecosystem: maven diff --git a/CHANGELOG.md b/CHANGELOG.md index d5cf2cb9e..2199233e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,18 @@ This log will detail notable changes to MyBatis Dynamic SQL. Full details are available on the GitHub milestone pages. +## Release 1.2.1 - Unreleased + +GitHub milestone: [https://github.com/mybatis/mybatis-dynamic-sql/issues?q=milestone%3A1.2.1+](https://github.com/mybatis/mybatis-dynamic-sql/issues?q=milestone%3A1.2.1+) + +### Fixed + +- Fixed a bug where the In conditions could render incorrectly in certain circumstances. ([#239](https://github.com/mybatis/mybatis-dynamic-sql/issues/239)) + +### Added + +- Added a callback capability to the "In" conditions that will be called before rendering when the conditions are empty. Also, removed the option that forced the library to render invalid SQL in that case. ([#241](https://github.com/mybatis/mybatis-dynamic-sql/pull/241)) + ## Release 1.2.0 - August 19, 2020 GitHub milestone: [https://github.com/mybatis/mybatis-dynamic-sql/issues?q=milestone%3A1.2.0+](https://github.com/mybatis/mybatis-dynamic-sql/issues?q=milestone%3A1.2.0+) @@ -14,7 +26,7 @@ This release includes a significant refactoring of the classes in the "org.mybat With this release, we deprecated several insert methods because they were inconsistently named or awkward. All deprecated methods have documented direct replacements. -All deprecated code will be removed in the next release. +All deprecated code will be removed in the next minor release. ### Added diff --git a/src/main/java/org/mybatis/dynamic/sql/AbstractListValueCondition.java b/src/main/java/org/mybatis/dynamic/sql/AbstractListValueCondition.java index 1c4c6c489..69ac7d289 100644 --- a/src/main/java/org/mybatis/dynamic/sql/AbstractListValueCondition.java +++ b/src/main/java/org/mybatis/dynamic/sql/AbstractListValueCondition.java @@ -22,19 +22,26 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -public abstract class AbstractListValueCondition implements VisitableCondition { +public abstract class AbstractListValueCondition> + implements VisitableCondition { protected final Collection values; protected final UnaryOperator> valueStreamTransformer; - protected boolean renderWhenEmpty = false; + protected final Callback emptyCallback; protected AbstractListValueCondition(Collection values) { - this(values, UnaryOperator.identity()); + this(values, UnaryOperator.identity(), () -> { }); } protected AbstractListValueCondition(Collection values, UnaryOperator> valueStreamTransformer) { + this(values, valueStreamTransformer, () -> { }); + } + + protected AbstractListValueCondition(Collection values, UnaryOperator> valueStreamTransformer, + Callback emptyCallback) { this.valueStreamTransformer = Objects.requireNonNull(valueStreamTransformer); this.values = valueStreamTransformer.apply(Objects.requireNonNull(values).stream()) .collect(Collectors.toList()); + this.emptyCallback = Objects.requireNonNull(emptyCallback); } public final Stream mapValues(Function mapper) { @@ -43,20 +50,20 @@ public final Stream mapValues(Function mapper) { @Override public boolean shouldRender() { - return !values.isEmpty() || renderWhenEmpty; + if (values.isEmpty()) { + emptyCallback.call(); + return false; + } else { + return true; + } } - /** - * Use with caution - this could cause the library to render invalid SQL like "where column in ()". - */ - protected void forceRenderingWhenEmpty() { - renderWhenEmpty = true; - } - @Override public R accept(ConditionVisitor visitor) { return visitor.visit(this); } + public abstract S 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 new file mode 100644 index 000000000..ca42a901b --- /dev/null +++ b/src/main/java/org/mybatis/dynamic/sql/Callback.java @@ -0,0 +1,34 @@ +/** + * Copyright 2016-2020 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 java.util.function.Function; + +@FunctionalInterface +public interface Callback { + void call(); + + static Callback exceptionThrowingCallback(String message) { + return exceptionThrowingCallback(message, RuntimeException::new); + } + + static Callback exceptionThrowingCallback(String message, + Function exceptionBuilder) { + return () -> { + throw exceptionBuilder.apply(message); + }; + } +} diff --git a/src/main/java/org/mybatis/dynamic/sql/ConditionVisitor.java b/src/main/java/org/mybatis/dynamic/sql/ConditionVisitor.java index 8063bc88a..14a958c71 100644 --- a/src/main/java/org/mybatis/dynamic/sql/ConditionVisitor.java +++ b/src/main/java/org/mybatis/dynamic/sql/ConditionVisitor.java @@ -1,5 +1,5 @@ /** - * Copyright 2016-2018 the original author or authors. + * Copyright 2016-2020 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,7 +16,7 @@ package org.mybatis.dynamic.sql; public interface ConditionVisitor { - R visit(AbstractListValueCondition condition); + R visit(AbstractListValueCondition condition); R visit(AbstractNoValueCondition condition); diff --git a/src/main/java/org/mybatis/dynamic/sql/util/StringUtilities.java b/src/main/java/org/mybatis/dynamic/sql/util/StringUtilities.java index b21295a7d..0b54c9685 100644 --- a/src/main/java/org/mybatis/dynamic/sql/util/StringUtilities.java +++ b/src/main/java/org/mybatis/dynamic/sql/util/StringUtilities.java @@ -16,8 +16,6 @@ package org.mybatis.dynamic.sql.util; import java.util.Optional; -import java.util.function.UnaryOperator; -import java.util.stream.Stream; public interface StringUtilities { @@ -42,9 +40,4 @@ static String spaceBefore(String in) { static String safelyUpperCase(String s) { return s == null ? null : s.toUpperCase(); } - - static UnaryOperator> upperCaseAfter(UnaryOperator> valueModifier) { - UnaryOperator> ua = s -> s.map(StringUtilities::safelyUpperCase); - return t -> ua.apply(valueModifier.apply(t)); - } } 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 ec1bc75b9..f7c47ecb3 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 @@ -23,15 +23,20 @@ import java.util.stream.Stream; import org.mybatis.dynamic.sql.AbstractListValueCondition; +import org.mybatis.dynamic.sql.Callback; -public class IsIn extends AbstractListValueCondition { +public class IsIn extends AbstractListValueCondition> { + + protected IsIn(Collection values) { + super(values); + } protected IsIn(Collection values, UnaryOperator> valueStreamTransformer) { super(values, valueStreamTransformer); } - protected IsIn(Collection values) { - super(values); + protected IsIn(Collection values, UnaryOperator> valueStreamTransformer, Callback emptyCallback) { + super(values, valueStreamTransformer, emptyCallback); } @Override @@ -40,6 +45,11 @@ public String renderCondition(String columnName, Stream placeholders) { + placeholders.collect(Collectors.joining(",", "in (", ")")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ } + @Override + public IsIn withListEmptyCallback(Callback callback) { + return new IsIn<>(values, valueStreamTransformer, callback); + } + /** * This method allows you to modify the condition's values before they are placed into the parameter map. * For example, you could filter nulls, or trim strings, etc. This process will run before final rendering of SQL. @@ -51,9 +61,7 @@ public String renderCondition(String columnName, Stream placeholders) { * @return new condition with the specified transformer */ public IsIn then(UnaryOperator> valueStreamTransformer) { - IsIn answer = new IsIn<>(values, valueStreamTransformer); - answer.renderWhenEmpty = renderWhenEmpty; - return answer; + return new IsIn<>(values, valueStreamTransformer, emptyCallback); } public static IsIn of(Collection values) { 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 158c49bec..a72173e69 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 @@ -21,16 +21,22 @@ import java.util.stream.Stream; import org.mybatis.dynamic.sql.AbstractListValueCondition; +import org.mybatis.dynamic.sql.Callback; import org.mybatis.dynamic.sql.util.StringUtilities; -public class IsInCaseInsensitive extends AbstractListValueCondition { +public class IsInCaseInsensitive extends AbstractListValueCondition { protected IsInCaseInsensitive(Collection values) { super(values, s -> s.map(StringUtilities::safelyUpperCase)); } protected IsInCaseInsensitive(Collection values, UnaryOperator> valueStreamTransformer) { - super(values, StringUtilities.upperCaseAfter(valueStreamTransformer)); + super(values, valueStreamTransformer); + } + + protected IsInCaseInsensitive(Collection values, UnaryOperator> valueStreamTransformer, + Callback emptyCallback) { + super(values, valueStreamTransformer, emptyCallback); } @Override @@ -39,20 +45,9 @@ public String renderCondition(String columnName, Stream placeholders) { placeholders.collect(Collectors.joining(",", "in (", ")")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ } - /** - * This method allows you to modify the condition's values before they are placed into the parameter map. - * For example, you could filter nulls, or trim strings, etc. This process will run before final rendering of SQL. - * If you filter values out of the stream, then final condition will not reference those values. If you filter all - * values out of the stream, then the condition will not render. - * - * @param valueStreamTransformer a UnaryOperator that will transform the value stream before - * the values are placed in the parameter map - * @return new condition with the specified transformer - */ - public IsInCaseInsensitive then(UnaryOperator> valueStreamTransformer) { - IsInCaseInsensitive answer = new IsInCaseInsensitive(values, valueStreamTransformer); - answer.renderWhenEmpty = renderWhenEmpty; - return answer; + @Override + public IsInCaseInsensitive withListEmptyCallback(Callback callback) { + return new IsInCaseInsensitive(values, valueStreamTransformer, callback); } public static IsInCaseInsensitive of(Collection values) { diff --git a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsInCaseInsensitiveWhenPresent.java b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsInCaseInsensitiveWhenPresent.java index 90c70c657..6299b779b 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsInCaseInsensitiveWhenPresent.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsInCaseInsensitiveWhenPresent.java @@ -1,5 +1,5 @@ /** - * Copyright 2016-2019 the original author or authors. + * Copyright 2016-2020 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,11 +17,25 @@ import java.util.Collection; import java.util.Objects; +import java.util.function.UnaryOperator; +import java.util.stream.Stream; +import org.mybatis.dynamic.sql.Callback; +import org.mybatis.dynamic.sql.util.StringUtilities; public class IsInCaseInsensitiveWhenPresent extends IsInCaseInsensitive { protected IsInCaseInsensitiveWhenPresent(Collection values) { - super(values, s -> s.filter(Objects::nonNull)); + super(values, s -> s.filter(Objects::nonNull).map(StringUtilities::safelyUpperCase)); + } + + protected IsInCaseInsensitiveWhenPresent(Collection values, + UnaryOperator> valueStreamTransformer, Callback callback) { + super(values, valueStreamTransformer, callback); + } + + @Override + public IsInCaseInsensitiveWhenPresent withListEmptyCallback(Callback callback) { + return new IsInCaseInsensitiveWhenPresent(values, valueStreamTransformer, callback); } public static IsInCaseInsensitiveWhenPresent of(Collection values) { 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 9890292b9..f0546c5d7 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 @@ -23,15 +23,20 @@ import java.util.stream.Stream; import org.mybatis.dynamic.sql.AbstractListValueCondition; +import org.mybatis.dynamic.sql.Callback; -public class IsNotIn extends AbstractListValueCondition { +public class IsNotIn extends AbstractListValueCondition> { + + protected IsNotIn(Collection values) { + super(values); + } protected IsNotIn(Collection values, UnaryOperator> valueStreamTransformer) { super(values, valueStreamTransformer); } - protected IsNotIn(Collection values) { - super(values); + protected IsNotIn(Collection values, UnaryOperator> valueStreamTransformer, Callback emptyCallback) { + super(values, valueStreamTransformer, emptyCallback); } @Override @@ -41,6 +46,11 @@ public String renderCondition(String columnName, Stream placeholders) { Collectors.joining(",", "not in (", ")")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ } + @Override + public IsNotIn withListEmptyCallback(Callback callback) { + return new IsNotIn<>(values, valueStreamTransformer, callback); + } + /** * This method allows you to modify the condition's values before they are placed into the parameter map. * For example, you could filter nulls, or trim strings, etc. This process will run before final rendering of SQL. @@ -52,9 +62,7 @@ public String renderCondition(String columnName, Stream placeholders) { * @return new condition with the specified transformer */ public IsNotIn then(UnaryOperator> valueStreamTransformer) { - IsNotIn answer = new IsNotIn<>(values, valueStreamTransformer); - answer.renderWhenEmpty = renderWhenEmpty; - return answer; + return new IsNotIn<>(values, valueStreamTransformer, emptyCallback); } public static IsNotIn of(Collection values) { 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 8a2171632..68af996ff 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 @@ -21,16 +21,22 @@ import java.util.stream.Stream; import org.mybatis.dynamic.sql.AbstractListValueCondition; +import org.mybatis.dynamic.sql.Callback; import org.mybatis.dynamic.sql.util.StringUtilities; -public class IsNotInCaseInsensitive extends AbstractListValueCondition { +public class IsNotInCaseInsensitive extends AbstractListValueCondition { protected IsNotInCaseInsensitive(Collection values) { super(values, s -> s.map(StringUtilities::safelyUpperCase)); } protected IsNotInCaseInsensitive(Collection values, UnaryOperator> valueStreamTransformer) { - super(values, StringUtilities.upperCaseAfter(valueStreamTransformer)); + super(values, valueStreamTransformer); + } + + protected IsNotInCaseInsensitive(Collection values, UnaryOperator> valueStreamTransformer, + Callback emptyCallback) { + super(values, valueStreamTransformer, emptyCallback); } @Override @@ -40,20 +46,9 @@ public String renderCondition(String columnName, Stream placeholders) { Collectors.joining(",", "not in (", ")")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ } - /** - * This method allows you to modify the condition's values before they are placed into the parameter map. - * For example, you could filter nulls, or trim strings, etc. This process will run before final rendering of SQL. - * If you filter values out of the stream, then final condition will not reference those values. If you filter all - * values out of the stream, then the condition will not render. - * - * @param valueStreamTransformer a UnaryOperator that will transform the value stream before - * the values are placed in the parameter map - * @return new condition with the specified transformer - */ - public IsNotInCaseInsensitive then(UnaryOperator> valueStreamTransformer) { - IsNotInCaseInsensitive answer = new IsNotInCaseInsensitive(values, valueStreamTransformer); - answer.renderWhenEmpty = renderWhenEmpty; - return answer; + @Override + public IsNotInCaseInsensitive withListEmptyCallback(Callback callback) { + return new IsNotInCaseInsensitive(values, valueStreamTransformer, callback); } public static IsNotInCaseInsensitive of(Collection values) { diff --git a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotInCaseInsensitiveWhenPresent.java b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotInCaseInsensitiveWhenPresent.java index 85fdf3f5f..e1ab564f4 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotInCaseInsensitiveWhenPresent.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotInCaseInsensitiveWhenPresent.java @@ -1,5 +1,5 @@ /** - * Copyright 2016-2019 the original author or authors. + * Copyright 2016-2020 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,11 +17,25 @@ import java.util.Collection; import java.util.Objects; +import java.util.function.UnaryOperator; +import java.util.stream.Stream; +import org.mybatis.dynamic.sql.Callback; +import org.mybatis.dynamic.sql.util.StringUtilities; public class IsNotInCaseInsensitiveWhenPresent extends IsNotInCaseInsensitive { protected IsNotInCaseInsensitiveWhenPresent(Collection values) { - super(values, s -> s.filter(Objects::nonNull)); + super(values, s -> s.filter(Objects::nonNull).map(StringUtilities::safelyUpperCase)); + } + + protected IsNotInCaseInsensitiveWhenPresent(Collection values, + UnaryOperator> valueStreamTransformer, Callback callback) { + super(values, valueStreamTransformer, callback); + } + + @Override + public IsNotInCaseInsensitiveWhenPresent withListEmptyCallback(Callback callback) { + return new IsNotInCaseInsensitiveWhenPresent(values, valueStreamTransformer, callback); } public static IsNotInCaseInsensitiveWhenPresent of(Collection values) { diff --git a/src/main/java/org/mybatis/dynamic/sql/where/render/WhereConditionVisitor.java b/src/main/java/org/mybatis/dynamic/sql/where/render/WhereConditionVisitor.java index 2b5772b20..74787870b 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/render/WhereConditionVisitor.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/render/WhereConditionVisitor.java @@ -50,7 +50,7 @@ private WhereConditionVisitor(Builder builder) { } @Override - public FragmentAndParameters visit(AbstractListValueCondition condition) { + public FragmentAndParameters visit(AbstractListValueCondition condition) { FragmentCollector fc = condition.mapValues(this::toFragmentAndParameters) .collect(FragmentCollector.collect()); diff --git a/src/site/markdown/docs/conditions.md b/src/site/markdown/docs/conditions.md index c9873ec8e..fd3a3858d 100644 --- a/src/site/markdown/docs/conditions.md +++ b/src/site/markdown/docs/conditions.md @@ -124,22 +124,20 @@ The library supplies several specializations of optional conditions to be used i ### Optionality with the "In" Conditions Optionality with the "in" and "not in" conditions is a bit more complex than the other types of conditions. The first thing to know is that no "in" or "not in" condition will render if the list of values is empty. For example, there will never be rendered SQL like `where name in ()`. So optionality of the "in" conditions is more about optionality of the *values* of the condition. The library comes with functions that will filter out null values, and will upper 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. Normally an "in" condition will be dropped from rendering if the list of values is empty - either through filtering or from the creation of the list. 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 force the conditions to render even if they are empty - which will cause a database error in most cases. If you want to force "in" conditions to render even if they are empty, you will need to create your own condition and configure it to render when empty. This is easily done by subclassing one of the existing conditions. For example: +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 - public class IsInRequired extends IsIn { - protected IsInRequired(Collection values) { - super(values); - forceRenderingWhenEmpty(); // calling this method will force the condition to render even if the values list is empty - } - - public static IsInRequired isIn(Collection values) { - return new IsInRequired<>(values); - } + private static IsIn isInRequired(Collection values) { + return IsIn.of(values).withListEmptyCallback(() -> { throw new RuntimeException("In values cannot be empty"); } ); } -``` -Note that we do not supply conditions like this as a part of the standard library because we believe that forcing the library to render invalid SQL is an extreme measure and should be undertaken with care. + // 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: @@ -201,4 +199,4 @@ Then the condition could be used in a query as follows: .render(RenderingStrategies.MYBATIS3); ``` -You can apply value stream operations to the conditions `IsIn`, `IsInCaseInsensitive`, `IsNotIn`, and `IsNotInCaseInsensitive`. With the case-insensitive conditions, the library will automatically convert non-null strings to upper case after any value stream operation you specify. +You can apply value stream operations to the conditions `IsIn` and `IsNotIn`. The built in conditions `IsInCaseInsensitive`, `IsInCaseInsensitiveWhenPresent`, `IsNotInCaseInsensitive`, and `IsNotInCaseInsensitiveWhenPresent` do not support additional value stream operations as they already implement a value stream operation as part of the condition. diff --git a/src/test/java/examples/animal/data/AnimalDataTest.java b/src/test/java/examples/animal/data/AnimalDataTest.java index 9debb9995..0ca03a202 100644 --- a/src/test/java/examples/animal/data/AnimalDataTest.java +++ b/src/test/java/examples/animal/data/AnimalDataTest.java @@ -35,7 +35,6 @@ import java.util.Objects; import org.apache.ibatis.datasource.unpooled.UnpooledDataSource; -import org.apache.ibatis.exceptions.PersistenceException; import org.apache.ibatis.jdbc.ScriptRunner; import org.apache.ibatis.mapping.Environment; import org.apache.ibatis.session.Configuration; @@ -47,6 +46,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mybatis.dynamic.sql.BasicColumn; +import org.mybatis.dynamic.sql.Callback; import org.mybatis.dynamic.sql.SqlTable; import org.mybatis.dynamic.sql.delete.render.DeleteStatementProvider; import org.mybatis.dynamic.sql.insert.render.BatchInsert; @@ -55,6 +55,7 @@ import org.mybatis.dynamic.sql.insert.render.InsertStatementProvider; import org.mybatis.dynamic.sql.render.RenderingStrategies; import org.mybatis.dynamic.sql.render.TableAliasCalculator; +import org.mybatis.dynamic.sql.select.SelectModel; import org.mybatis.dynamic.sql.select.render.SelectStatementProvider; import org.mybatis.dynamic.sql.update.render.UpdateStatementProvider; import org.mybatis.dynamic.sql.util.mybatis3.MyBatis3Utils; @@ -592,53 +593,37 @@ void testInConditionWithEventuallyEmptyList() { @Test void testInConditionWithEventuallyEmptyListForceRendering() { - try (SqlSession sqlSession = sqlSessionFactory.openSession()) { - AnimalDataMapper mapper = sqlSession.getMapper(AnimalDataMapper.class); - - List inValues = new ArrayList<>(); - inValues.add(null); - inValues.add(22); - inValues.add(null); - - SelectStatementProvider selectStatement = select(id, animalName, bodyWeight, brainWeight) - .from(animalData) - .where(id, IsInRequired.isIn(inValues).then(s -> s.filter(Objects::nonNull).filter(i -> i != 22))) - .build() - .render(RenderingStrategies.MYBATIS3); + List inValues = new ArrayList<>(); + inValues.add(null); + inValues.add(22); + inValues.add(null); - assertThat(selectStatement.getSelectStatement()) - .isEqualTo("select id, animal_name, body_weight, brain_weight from AnimalData where id in ()"); + SelectModel selectModel = select(id, animalName, bodyWeight, brainWeight) + .from(animalData) + .where(id, isInRequired(inValues).then(s -> s.filter(Objects::nonNull).filter(i -> i != 22))) + .build(); - assertThatExceptionOfType(PersistenceException.class).isThrownBy(() -> mapper.selectMany(selectStatement)); - } + assertThatExceptionOfType(RuntimeException.class).describedAs("Fred").isThrownBy(() -> + selectModel.render(RenderingStrategies.MYBATIS3) + ); } @Test void testInConditionWithEmptyList() { - try (SqlSession sqlSession = sqlSessionFactory.openSession()) { - AnimalDataMapper mapper = sqlSession.getMapper(AnimalDataMapper.class); - - SelectStatementProvider selectStatement = select(id, animalName, bodyWeight, brainWeight) - .from(animalData) - .where(id, IsInRequired.isIn(Collections.emptyList())) - .build() - .render(RenderingStrategies.MYBATIS3); + SelectModel selectModel = select(id, animalName, bodyWeight, brainWeight) + .from(animalData) + .where(id, isInRequired(Collections.emptyList())) + .build(); - assertThatExceptionOfType(PersistenceException.class).isThrownBy(() -> mapper.selectMany(selectStatement)); - } + assertThatExceptionOfType(RuntimeException.class).describedAs("Fred").isThrownBy(() -> + selectModel.render(RenderingStrategies.MYBATIS3) + ); } - public static class IsInRequired extends IsIn { - protected IsInRequired(Collection values) { - super(values); - forceRenderingWhenEmpty(); - } - - public static IsInRequired isIn(Collection values) { - return new IsInRequired<>(values); - } + private static IsIn isInRequired(Collection values) { + return IsIn.of(values).withListEmptyCallback(Callback.exceptionThrowingCallback("Fred")); } - + @Test void testInCaseSensitiveCondition() { try (SqlSession sqlSession = sqlSessionFactory.openSession()) { @@ -723,34 +708,22 @@ void testNotInConditionWithEventuallyEmptyList() { @Test void testNotInConditionWithEventuallyEmptyListForceRendering() { - try (SqlSession sqlSession = sqlSessionFactory.openSession()) { - AnimalDataMapper mapper = sqlSession.getMapper(AnimalDataMapper.class); - - SelectStatementProvider selectStatement = select(id, animalName, bodyWeight, brainWeight) - .from(animalData) - .where(id, IsNotInRequired.isNotIn(null, 22, null) - .then(s -> s.filter(Objects::nonNull).filter(i -> i != 22))) - .build() - .render(RenderingStrategies.MYBATIS3); + SelectModel selectModel = select(id, animalName, bodyWeight, brainWeight) + .from(animalData) + .where(id, isNotInRequired(null, 22, null) + .then(s -> s.filter(Objects::nonNull).filter(i -> i != 22))) + .build(); - assertThat(selectStatement.getSelectStatement()) - .isEqualTo("select id, animal_name, body_weight, brain_weight from AnimalData where id not in ()"); - - assertThatExceptionOfType(PersistenceException.class).isThrownBy(() -> mapper.selectMany(selectStatement)); - } + assertThatExceptionOfType(RuntimeException.class).describedAs("Fred").isThrownBy(() -> + selectModel.render(RenderingStrategies.MYBATIS3) + ); } - public static class IsNotInRequired extends IsNotIn { - protected IsNotInRequired(Collection values) { - super(values); - forceRenderingWhenEmpty(); - } - - @SafeVarargs - public static IsNotInRequired isNotIn(T...values) { - return new IsNotInRequired<>(Arrays.asList(values)); - } + @SafeVarargs + private static IsNotIn isNotInRequired(T...values) { + return IsNotIn.of(Arrays.asList(values)).withListEmptyCallback(Callback.exceptionThrowingCallback("Fred")); } + @Test void testLikeCondition() { try (SqlSession sqlSession = sqlSessionFactory.openSession()) { diff --git a/src/test/java/examples/animal/data/OptionalConditionsWithPredicatesAnimalDataTest.java b/src/test/java/examples/animal/data/OptionalConditionsWithPredicatesAnimalDataTest.java index 258a840a5..5658f4d77 100644 --- a/src/test/java/examples/animal/data/OptionalConditionsWithPredicatesAnimalDataTest.java +++ b/src/test/java/examples/animal/data/OptionalConditionsWithPredicatesAnimalDataTest.java @@ -514,25 +514,6 @@ void testValueStreamTransformerWithCustomCondition() { } } - @Test - void testIsInCaseInsensitiveWhenWithSomeValues() { - try (SqlSession sqlSession = sqlSessionFactory.openSession()) { - AnimalDataMapper mapper = sqlSession.getMapper(AnimalDataMapper.class); - SelectStatementProvider selectStatement = select(id, animalName, bodyWeight, brainWeight) - .from(animalData) - .where(animalName, isInCaseInsensitive("mouse", null, "musk shrew").then(s -> s.filter(Objects::nonNull))) - .orderBy(id) - .build() - .render(RenderingStrategies.MYBATIS3); - List animals = mapper.selectMany(selectStatement); - assertAll( - () -> assertThat(selectStatement.getSelectStatement()).isEqualTo("select id, animal_name, body_weight, brain_weight from AnimalData where upper(animal_name) in (#{parameters.p1,jdbcType=VARCHAR},#{parameters.p2,jdbcType=VARCHAR}) order by id"), - () -> assertThat(animals).hasSize(2), - () -> assertThat(animals.get(0).getId()).isEqualTo(4) - ); - } - } - @Test void testIsInCaseInsensitiveWhenWithNoValues() { try (SqlSession sqlSession = sqlSessionFactory.openSession()) { @@ -613,26 +594,6 @@ void testIsNotInCaseInsensitiveWhenWithValue() { } } - @Test - void testIsNotInCaseInsensitiveWhenWithSomeValues() { - try (SqlSession sqlSession = sqlSessionFactory.openSession()) { - AnimalDataMapper mapper = sqlSession.getMapper(AnimalDataMapper.class); - SelectStatementProvider selectStatement = select(id, animalName, bodyWeight, brainWeight) - .from(animalData) - .where(animalName, isNotInCaseInsensitive("mouse", null, "musk shrew").then(s -> s.filter(Objects::nonNull))) - .and(id, isLessThanOrEqualTo(10)) - .orderBy(id) - .build() - .render(RenderingStrategies.MYBATIS3); - List animals = mapper.selectMany(selectStatement); - assertAll( - () -> assertThat(selectStatement.getSelectStatement()).isEqualTo("select id, animal_name, body_weight, brain_weight from AnimalData where upper(animal_name) not in (#{parameters.p1,jdbcType=VARCHAR},#{parameters.p2,jdbcType=VARCHAR}) and id <= #{parameters.p3,jdbcType=INTEGER} order by id"), - () -> assertThat(animals).hasSize(8), - () -> assertThat(animals.get(0).getId()).isEqualTo(1) - ); - } - } - @Test void testIsNotInCaseInsensitiveWhenWithNoValues() { try (SqlSession sqlSession = sqlSessionFactory.openSession()) { 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 09670aa33..ad78e9eba 100644 --- a/src/test/java/org/mybatis/dynamic/sql/select/SelectStatementTest.java +++ b/src/test/java/org/mybatis/dynamic/sql/select/SelectStatementTest.java @@ -16,14 +16,17 @@ package org.mybatis.dynamic.sql.select; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.junit.jupiter.api.Assertions.assertAll; import static org.mybatis.dynamic.sql.SqlBuilder.*; import java.sql.JDBCType; +import java.util.Collections; import java.util.Date; import java.util.Map; import org.junit.jupiter.api.Test; +import org.mybatis.dynamic.sql.Callback; import org.mybatis.dynamic.sql.SqlColumn; import org.mybatis.dynamic.sql.SqlTable; import org.mybatis.dynamic.sql.render.RenderingStrategies; @@ -34,6 +37,7 @@ class SelectStatementTest { static final SqlTable table = SqlTable.of("foo"); static final SqlColumn column1 = table.column("column1", JDBCType.DATE); static final SqlColumn column2 = table.column("column2", JDBCType.INTEGER); + static final SqlColumn column3 = table.column("column3", JDBCType.VARCHAR); @Test void testSimpleCriteria() { @@ -257,4 +261,56 @@ void testGroupBySingleColumn() { () -> assertThat(parameters).containsEntry("p1", d) ); } + + @Test + void testInCaseInsensitiveEmptyList() { + SelectModel selectModel = select(column1, column3) + .from(table, "a") + .where(column3, isInCaseInsensitive(Collections.emptyList()) + .withListEmptyCallback(Callback.exceptionThrowingCallback("Fred"))) + .build(); + + assertThatExceptionOfType(RuntimeException.class).describedAs("Fred").isThrownBy(() -> + selectModel.render(RenderingStrategies.MYBATIS3) + ); + } + + @Test + void testInCaseInsensitiveWhenPresentEmptyList() { + SelectModel selectModel = select(column1, column3) + .from(table, "a") + .where(column3, isInCaseInsensitiveWhenPresent(Collections.emptyList()) + .withListEmptyCallback(Callback.exceptionThrowingCallback("Fred"))) + .build(); + + assertThatExceptionOfType(RuntimeException.class).describedAs("Fred").isThrownBy(() -> + selectModel.render(RenderingStrategies.MYBATIS3) + ); + } + + @Test + void testNotInCaseInsensitiveEmptyList() { + SelectModel selectModel = select(column1, column3) + .from(table, "a") + .where(column3, isNotInCaseInsensitive(Collections.emptyList()) + .withListEmptyCallback(Callback.exceptionThrowingCallback("Fred"))) + .build(); + + assertThatExceptionOfType(RuntimeException.class).describedAs("Fred").isThrownBy(() -> + selectModel.render(RenderingStrategies.MYBATIS3) + ); + } + + @Test + void testNotInCaseInsensitiveWhenPresentEmptyList() { + SelectModel selectModel = select(column1, column3) + .from(table, "a") + .where(column3, isNotInCaseInsensitiveWhenPresent(Collections.emptyList()) + .withListEmptyCallback(Callback.exceptionThrowingCallback("Fred"))) + .build(); + + assertThatExceptionOfType(RuntimeException.class).describedAs("Fred").isThrownBy(() -> + selectModel.render(RenderingStrategies.MYBATIS3) + ); + } } diff --git a/src/test/java/org/mybatis/dynamic/sql/util/StringUtilitiesTest.java b/src/test/java/org/mybatis/dynamic/sql/util/StringUtilitiesTest.java deleted file mode 100644 index d49016fe7..000000000 --- a/src/test/java/org/mybatis/dynamic/sql/util/StringUtilitiesTest.java +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Copyright 2016-2020 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 static org.assertj.core.api.Assertions.assertThat; - -import java.util.List; -import java.util.function.UnaryOperator; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import org.junit.jupiter.api.Test; - -class StringUtilitiesTest { - - @Test - void testThatUpperCaseIsAppliedAfter() { - Stream ss = Stream.of("fred", "wilma", "barney", "betty"); - - UnaryOperator> valueModifier = s -> s.filter(st -> st.equals("fred")); - - UnaryOperator> ua = StringUtilities.upperCaseAfter(valueModifier); - - List list = ua.apply(ss).collect(Collectors.toList()); - assertThat(list).containsExactly("FRED"); - } -}