Skip to content

Implement Callback for In and NotIn Conditions When the Value List is Empty #241

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Aug 27, 2020
Merged
16 changes: 16 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -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
Expand Down
14 changes: 13 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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+)
Expand All @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,26 @@
import java.util.stream.Collectors;
import java.util.stream.Stream;

public abstract class AbstractListValueCondition<T> implements VisitableCondition<T> {
public abstract class AbstractListValueCondition<T, S extends AbstractListValueCondition<T, S>>
implements VisitableCondition<T> {
protected final Collection<T> values;
protected final UnaryOperator<Stream<T>> valueStreamTransformer;
protected boolean renderWhenEmpty = false;
protected final Callback emptyCallback;

protected AbstractListValueCondition(Collection<T> values) {
this(values, UnaryOperator.identity());
this(values, UnaryOperator.identity(), () -> { });
}

protected AbstractListValueCondition(Collection<T> values, UnaryOperator<Stream<T>> valueStreamTransformer) {
this(values, valueStreamTransformer, () -> { });
}

protected AbstractListValueCondition(Collection<T> values, UnaryOperator<Stream<T>> 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 <R> Stream<R> mapValues(Function<T, R> mapper) {
Expand All @@ -43,20 +50,20 @@ public final <R> Stream<R> mapValues(Function<T, R> 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> R accept(ConditionVisitor<T, R> visitor) {
return visitor.visit(this);
}

public abstract S withListEmptyCallback(Callback callback);

public abstract String renderCondition(String columnName, Stream<String> placeholders);
}
34 changes: 34 additions & 0 deletions src/main/java/org/mybatis/dynamic/sql/Callback.java
Original file line number Diff line number Diff line change
@@ -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<String, ? extends RuntimeException> exceptionBuilder) {
return () -> {
throw exceptionBuilder.apply(message);
};
}
}
4 changes: 2 additions & 2 deletions src/main/java/org/mybatis/dynamic/sql/ConditionVisitor.java
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -16,7 +16,7 @@
package org.mybatis.dynamic.sql;

public interface ConditionVisitor<T, R> {
R visit(AbstractListValueCondition<T> condition);
R visit(AbstractListValueCondition<T, ?> condition);

R visit(AbstractNoValueCondition<T> condition);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand All @@ -42,9 +40,4 @@ static String spaceBefore(String in) {
static String safelyUpperCase(String s) {
return s == null ? null : s.toUpperCase();
}

static UnaryOperator<Stream<String>> upperCaseAfter(UnaryOperator<Stream<String>> valueModifier) {
UnaryOperator<Stream<String>> ua = s -> s.map(StringUtilities::safelyUpperCase);
return t -> ua.apply(valueModifier.apply(t));
}
}
20 changes: 14 additions & 6 deletions src/main/java/org/mybatis/dynamic/sql/where/condition/IsIn.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,20 @@
import java.util.stream.Stream;

import org.mybatis.dynamic.sql.AbstractListValueCondition;
import org.mybatis.dynamic.sql.Callback;

public class IsIn<T> extends AbstractListValueCondition<T> {
public class IsIn<T> extends AbstractListValueCondition<T, IsIn<T>> {

protected IsIn(Collection<T> values) {
super(values);
}

protected IsIn(Collection<T> values, UnaryOperator<Stream<T>> valueStreamTransformer) {
super(values, valueStreamTransformer);
}

protected IsIn(Collection<T> values) {
super(values);
protected IsIn(Collection<T> values, UnaryOperator<Stream<T>> valueStreamTransformer, Callback emptyCallback) {
super(values, valueStreamTransformer, emptyCallback);
}

@Override
Expand All @@ -40,6 +45,11 @@ public String renderCondition(String columnName, Stream<String> placeholders) {
+ placeholders.collect(Collectors.joining(",", "in (", ")")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}

@Override
public IsIn<T> 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.
Expand All @@ -51,9 +61,7 @@ public String renderCondition(String columnName, Stream<String> placeholders) {
* @return new condition with the specified transformer
*/
public IsIn<T> then(UnaryOperator<Stream<T>> valueStreamTransformer) {
IsIn<T> answer = new IsIn<>(values, valueStreamTransformer);
answer.renderWhenEmpty = renderWhenEmpty;
return answer;
return new IsIn<>(values, valueStreamTransformer, emptyCallback);
}

public static <T> IsIn<T> of(Collection<T> values) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> {
public class IsInCaseInsensitive extends AbstractListValueCondition<String, IsInCaseInsensitive> {

protected IsInCaseInsensitive(Collection<String> values) {
super(values, s -> s.map(StringUtilities::safelyUpperCase));
}

protected IsInCaseInsensitive(Collection<String> values, UnaryOperator<Stream<String>> valueStreamTransformer) {
super(values, StringUtilities.upperCaseAfter(valueStreamTransformer));
super(values, valueStreamTransformer);
}

protected IsInCaseInsensitive(Collection<String> values, UnaryOperator<Stream<String>> valueStreamTransformer,
Callback emptyCallback) {
super(values, valueStreamTransformer, emptyCallback);
}

@Override
Expand All @@ -39,20 +45,9 @@ public String renderCondition(String columnName, Stream<String> 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<Stream<String>> 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<String> values) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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<String> values) {
super(values, s -> s.filter(Objects::nonNull));
super(values, s -> s.filter(Objects::nonNull).map(StringUtilities::safelyUpperCase));
}

protected IsInCaseInsensitiveWhenPresent(Collection<String> values,
UnaryOperator<Stream<String>> valueStreamTransformer, Callback callback) {
super(values, valueStreamTransformer, callback);
}

@Override
public IsInCaseInsensitiveWhenPresent withListEmptyCallback(Callback callback) {
return new IsInCaseInsensitiveWhenPresent(values, valueStreamTransformer, callback);
}

public static IsInCaseInsensitiveWhenPresent of(Collection<String> values) {
Expand Down
20 changes: 14 additions & 6 deletions src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotIn.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,20 @@
import java.util.stream.Stream;

import org.mybatis.dynamic.sql.AbstractListValueCondition;
import org.mybatis.dynamic.sql.Callback;

public class IsNotIn<T> extends AbstractListValueCondition<T> {
public class IsNotIn<T> extends AbstractListValueCondition<T, IsNotIn<T>> {

protected IsNotIn(Collection<T> values) {
super(values);
}

protected IsNotIn(Collection<T> values, UnaryOperator<Stream<T>> valueStreamTransformer) {
super(values, valueStreamTransformer);
}

protected IsNotIn(Collection<T> values) {
super(values);
protected IsNotIn(Collection<T> values, UnaryOperator<Stream<T>> valueStreamTransformer, Callback emptyCallback) {
super(values, valueStreamTransformer, emptyCallback);
}

@Override
Expand All @@ -41,6 +46,11 @@ public String renderCondition(String columnName, Stream<String> placeholders) {
Collectors.joining(",", "not in (", ")")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}

@Override
public IsNotIn<T> 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.
Expand All @@ -52,9 +62,7 @@ public String renderCondition(String columnName, Stream<String> placeholders) {
* @return new condition with the specified transformer
*/
public IsNotIn<T> then(UnaryOperator<Stream<T>> valueStreamTransformer) {
IsNotIn<T> answer = new IsNotIn<>(values, valueStreamTransformer);
answer.renderWhenEmpty = renderWhenEmpty;
return answer;
return new IsNotIn<>(values, valueStreamTransformer, emptyCallback);
}

public static <T> IsNotIn<T> of(Collection<T> values) {
Expand Down
Loading