Skip to content

Condition Filter and Map are now Interfaces #921

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 15 commits into from
Mar 14, 2025
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -103,7 +103,8 @@ Runtime behavior changes:
- Rendering for all the conditions (isEqualTo, etc.) has changed. This should be transparent to most users unless you
have coded a direct implementation of `VisitableCondition`. The change makes it easier to code custom conditions that
are not supported by the library out of the box. The statement renderers now call methods `renderCondition` and
`renderLeftColumn` that you can override to implement any rendering you need.
`renderLeftColumn` that you can override to implement any rendering you need. In addition, we've made `filter` and
`map` support optional if you implement custom conditions

## Release 1.5.2 - June 3, 2024

Original file line number Diff line number Diff line change
@@ -73,16 +73,6 @@ protected <R, S extends AbstractListValueCondition<R>> S mapSupport(Function<? s
}
}

/**
* If not empty, apply the predicate to each value in the list and return a new condition with the filtered values.
* Else returns an empty condition (this).
*
* @param predicate predicate applied to the values, if not empty
*
* @return a new condition with filtered values if renderable, otherwise an empty condition
*/
public abstract AbstractListValueCondition<T> filter(Predicate<? super T> predicate);

public abstract String operator();

@Override
@@ -100,4 +90,54 @@ private FragmentAndParameters toFragmentAndParameters(T value, RenderingContext
.withParameter(parameterInfo.parameterMapKey(), leftColumn.convertParameterType(value))
.build();
}

/**
* Conditions may implement Filterable to add optionality to rendering.
*
* <p>If a condition is Filterable, then a user may add a filter to the usage of the condition that makes a decision
* whether to render the condition at runtime. Conditions that fail the filter will be dropped from the
* rendered SQL.
*
* <p>Implementations of Filterable may call
* {@link AbstractListValueCondition#filterSupport(Predicate, Function, AbstractListValueCondition, Supplier)} as
* a common implementation of the filtering algorithm.
*
* @param <T> the Java type related to the database column type
*/
public interface Filterable<T> {
/**
* If renderable and the value matches the predicate, returns this condition. Else returns a condition
* that will not render.
*
* @param predicate predicate applied to the value, if renderable
* @return this condition if renderable and the value matches the predicate, otherwise a condition
* that will not render.
*/
AbstractListValueCondition<T> filter(Predicate<? super T> predicate);
}

/**
* Conditions may implement Mappable to alter condition values or types during rendering.
*
* <p>If a condition is Mappable, then a user may add a mapper to the usage of the condition that can alter the
* values of a condition, or change that datatype.
*
* <p>Implementations of Mappable may call
* {@link AbstractListValueCondition#mapSupport(Function, Function, Supplier)} as
* a common implementation of the mapping algorithm.
*
* @param <T> the Java type related to the database column type
*/
public interface Mappable<T> {
/**
* If renderable, apply the mapping to the value and return a new condition with the new value. Else return a
* condition that will not render (this).
*
* @param mapper a mapping function to apply to the value, if renderable
* @param <R> type of the new condition
* @return a new condition with the result of applying the mapper to the value of this condition,
* if renderable, otherwise a condition that will not render.
*/
<R> AbstractListValueCondition<R> map(Function<? super T, ? extends R> mapper);
}
}
Original file line number Diff line number Diff line change
@@ -38,4 +38,30 @@ protected <S extends AbstractNoValueCondition<?>> S filterSupport(BooleanSupplie
public FragmentAndParameters renderCondition(RenderingContext renderingContext, BindableColumn<T> leftColumn) {
return FragmentAndParameters.fromFragment(operator());
}

/**
* Conditions may implement Filterable to add optionality to rendering.
*
* <p>If a condition is Filterable, then a user may add a filter to the usage of the condition that makes a decision
* whether to render the condition at runtime. Conditions that fail the filter will be dropped from the
* rendered SQL.
*
* <p>Implementations of Filterable may call
* {@link AbstractNoValueCondition#filterSupport(BooleanSupplier, Supplier, AbstractNoValueCondition)} as
* a common implementation of the filtering algorithm.
*/
public interface Filterable {
/**
* If renderable and the supplier returns true, returns this condition. Else returns a condition that will not
* render.
*
* @param booleanSupplier
* function that specifies whether the condition should render
* @param <S>
* condition type - not used except for compilation compliance
*
* @return this condition if renderable and the supplier returns true, otherwise a condition that will not render.
*/
<S> AbstractNoValueCondition<S> filter(BooleanSupplier booleanSupplier);
}
}
Original file line number Diff line number Diff line change
@@ -54,16 +54,6 @@ protected <R, S extends AbstractSingleValueCondition<R>> S mapSupport(Function<?
}
}

/**
* If renderable and the value matches the predicate, returns this condition. Else returns a condition
* that will not render.
*
* @param predicate predicate applied to the value, if renderable
* @return this condition if renderable and the value matches the predicate, otherwise a condition
* that will not render.
*/
public abstract AbstractSingleValueCondition<T> filter(Predicate<? super T> predicate);

public abstract String operator();

@Override
@@ -75,4 +65,54 @@ public FragmentAndParameters renderCondition(RenderingContext renderingContext,
.withParameter(parameterInfo.parameterMapKey(), leftColumn.convertParameterType(value()))
.build();
}

/**
* Conditions may implement Filterable to add optionality to rendering.
*
* <p>If a condition is Filterable, then a user may add a filter to the usage of the condition that makes a decision
* whether to render the condition at runtime. Conditions that fail the filter will be dropped from the
* rendered SQL.
*
* <p>Implementations of Filterable may call
* {@link AbstractSingleValueCondition#filterSupport(Predicate, Supplier, AbstractSingleValueCondition)} as
* a common implementation of the filtering algorithm.
*
* @param <T> the Java type related to the database column type
*/
public interface Filterable<T> {
/**
* If renderable and the value matches the predicate, returns this condition. Else returns a condition
* that will not render.
*
* @param predicate predicate applied to the value, if renderable
* @return this condition if renderable and the value matches the predicate, otherwise a condition
* that will not render.
*/
AbstractSingleValueCondition<T> filter(Predicate<? super T> predicate);
}

/**
* Conditions may implement Mappable to alter condition values or types during rendering.
*
* <p>If a condition is Mappable, then a user may add a mapper to the usage of the condition that can alter the
* values of a condition, or change that datatype.
*
* <p>Implementations of Mappable may call
* {@link AbstractSingleValueCondition#mapSupport(Function, Function, Supplier)} as
* a common implementation of the mapping algorithm.
*
* @param <T> the Java type related to the database column type
*/
public interface Mappable<T> {
/**
* If renderable, apply the mapping to the value and return a new condition with the new value. Else return a
* condition that will not render (this).
*
* @param mapper a mapping function to apply to the value, if renderable
* @param <R> type of the new condition
* @return a new condition with the result of applying the mapper to the value of this condition,
* if renderable, otherwise a condition that will not render.
*/
<R> AbstractSingleValueCondition<R> map(Function<? super T, ? extends R> mapper);
}
}
Original file line number Diff line number Diff line change
@@ -67,27 +67,6 @@ protected <R, S extends AbstractTwoValueCondition<R>> S mapSupport(Function<? su
}
}

/**
* If renderable and the values match the predicate, returns this condition. Else returns a condition
* that will not render.
*
* @param predicate predicate applied to the values, if renderable
* @return this condition if renderable and the values match the predicate, otherwise a condition
* that will not render.
*/
public abstract AbstractTwoValueCondition<T> filter(BiPredicate<? super T, ? super T> predicate);

/**
* If renderable and both values match the predicate, returns this condition. Else returns a condition
* that will not render. This function implements a short-circuiting test. If the
* first value does not match the predicate, then the second value will not be tested.
*
* @param predicate predicate applied to both values, if renderable
* @return this condition if renderable and the values match the predicate, otherwise a condition
* that will not render.
*/
public abstract AbstractTwoValueCondition<T> filter(Predicate<? super T> predicate);

public abstract String operator1();

public abstract String operator2();
@@ -107,4 +86,79 @@ public FragmentAndParameters renderCondition(RenderingContext renderingContext,
.withParameter(parameterInfo2.parameterMapKey(), leftColumn.convertParameterType(value2()))
.build();
}

/**
* Conditions may implement Filterable to add optionality to rendering.
*
* <p>If a condition is Filterable, then a user may add a filter to the usage of the condition that makes a decision
* whether to render the condition at runtime. Conditions that fail the filter will be dropped from the
* rendered SQL.
*
* <p>Implementations of Filterable may call
* {@link AbstractTwoValueCondition#filterSupport(Predicate, Supplier, AbstractTwoValueCondition)}
* or {@link AbstractTwoValueCondition#filterSupport(BiPredicate, Supplier, AbstractTwoValueCondition)} as
* a common implementation of the filtering algorithm.
*
* @param <T> the Java type related to the database column type
*/
public interface Filterable<T> {
/**
* If renderable and the values match the predicate, returns this condition. Else returns a condition
* that will not render.
*
* @param predicate predicate applied to the values, if renderable
* @return this condition if renderable and the values match the predicate, otherwise a condition
* that will not render.
*/
AbstractTwoValueCondition<T> filter(BiPredicate<? super T, ? super T> predicate);

/**
* If renderable and both values match the predicate, returns this condition. Else returns a condition
* that will not render. This function implements a short-circuiting test. If the
* first value does not match the predicate, then the second value will not be tested.
*
* @param predicate predicate applied to both values, if renderable
* @return this condition if renderable and the values match the predicate, otherwise a condition
* that will not render.
*/
AbstractTwoValueCondition<T> filter(Predicate<? super T> predicate);
}

/**
* Conditions may implement Mappable to alter condition values or types during rendering.
*
* <p>If a condition is Mappable, then a user may add a mapper to the usage of the condition that can alter the
* values of a condition, or change that datatype.
*
* <p>Implementations of Mappable may call
* {@link AbstractTwoValueCondition#mapSupport(Function, Function, BiFunction, Supplier)} as
* a common implementation of the mapping algorithm.
*
* @param <T> the Java type related to the database column type
*/
public interface Mappable<T> {
/**
* If renderable, apply the mappings to the values and return a new condition with the new values. Else return a
* condition that will not render (this).
*
* @param mapper1 a mapping function to apply to the first value, if renderable
* @param mapper2 a mapping function to apply to the second value, if renderable
* @param <R> type of the new condition
* @return a new condition with the result of applying the mappers to the values of this condition,
* if renderable, otherwise a condition that will not render.
*/
<R> AbstractTwoValueCondition<R> map(Function<? super T, ? extends R> mapper1,
Function<? super T, ? extends R> mapper2);

/**
* If renderable, apply the mapping to both values and return a new condition with the new values. Else return a
* condition that will not render (this).
*
* @param mapper a mapping function to apply to both values, if renderable
* @param <R> type of the new condition
* @return a new condition with the result of applying the mappers to the values of this condition,
* if renderable, otherwise a condition that will not render.
*/
<R> AbstractTwoValueCondition<R> map(Function<? super T, ? extends R> mapper);
}
}
33 changes: 17 additions & 16 deletions src/main/java/org/mybatis/dynamic/sql/SqlBuilder.java
Original file line number Diff line number Diff line change
@@ -882,68 +882,69 @@ static IsEqualTo<Boolean> isFalse() {
}

// conditions for strings only
static IsLikeCaseInsensitive isLikeCaseInsensitive(String value) {
static IsLikeCaseInsensitive<String> isLikeCaseInsensitive(String value) {
return IsLikeCaseInsensitive.of(value);
}

static IsLikeCaseInsensitive isLikeCaseInsensitive(Supplier<String> valueSupplier) {
static IsLikeCaseInsensitive<String> isLikeCaseInsensitive(Supplier<String> valueSupplier) {
return isLikeCaseInsensitive(valueSupplier.get());
}

static IsLikeCaseInsensitive isLikeCaseInsensitiveWhenPresent(@Nullable String value) {
static IsLikeCaseInsensitive<String> isLikeCaseInsensitiveWhenPresent(@Nullable String value) {
return value == null ? IsLikeCaseInsensitive.empty() : IsLikeCaseInsensitive.of(value);
}

static IsLikeCaseInsensitive isLikeCaseInsensitiveWhenPresent(Supplier<@Nullable String> valueSupplier) {
static IsLikeCaseInsensitive<String> isLikeCaseInsensitiveWhenPresent(Supplier<@Nullable String> valueSupplier) {
return isLikeCaseInsensitiveWhenPresent(valueSupplier.get());
}

static IsNotLikeCaseInsensitive isNotLikeCaseInsensitive(String value) {
static IsNotLikeCaseInsensitive<String> isNotLikeCaseInsensitive(String value) {
return IsNotLikeCaseInsensitive.of(value);
}

static IsNotLikeCaseInsensitive isNotLikeCaseInsensitive(Supplier<String> valueSupplier) {
static IsNotLikeCaseInsensitive<String> isNotLikeCaseInsensitive(Supplier<String> valueSupplier) {
return isNotLikeCaseInsensitive(valueSupplier.get());
}

static IsNotLikeCaseInsensitive isNotLikeCaseInsensitiveWhenPresent(@Nullable String value) {
static IsNotLikeCaseInsensitive<String> isNotLikeCaseInsensitiveWhenPresent(@Nullable String value) {
return value == null ? IsNotLikeCaseInsensitive.empty() : IsNotLikeCaseInsensitive.of(value);
}

static IsNotLikeCaseInsensitive isNotLikeCaseInsensitiveWhenPresent(Supplier<@Nullable String> valueSupplier) {
static IsNotLikeCaseInsensitive<String> isNotLikeCaseInsensitiveWhenPresent(
Supplier<@Nullable String> valueSupplier) {
return isNotLikeCaseInsensitiveWhenPresent(valueSupplier.get());
}

static IsInCaseInsensitive isInCaseInsensitive(String... values) {
static IsInCaseInsensitive<String> isInCaseInsensitive(String... values) {
return IsInCaseInsensitive.of(values);
}

static IsInCaseInsensitive isInCaseInsensitive(Collection<String> values) {
static IsInCaseInsensitive<String> isInCaseInsensitive(Collection<String> values) {
return IsInCaseInsensitive.of(values);
}

static IsInCaseInsensitiveWhenPresent isInCaseInsensitiveWhenPresent(@Nullable String... values) {
static IsInCaseInsensitiveWhenPresent<String> isInCaseInsensitiveWhenPresent(@Nullable String... values) {
return IsInCaseInsensitiveWhenPresent.of(values);
}

static IsInCaseInsensitiveWhenPresent isInCaseInsensitiveWhenPresent(
static IsInCaseInsensitiveWhenPresent<String> isInCaseInsensitiveWhenPresent(
@Nullable Collection<@Nullable String> values) {
return values == null ? IsInCaseInsensitiveWhenPresent.empty() : IsInCaseInsensitiveWhenPresent.of(values);
}

static IsNotInCaseInsensitive isNotInCaseInsensitive(String... values) {
static IsNotInCaseInsensitive<String> isNotInCaseInsensitive(String... values) {
return IsNotInCaseInsensitive.of(values);
}

static IsNotInCaseInsensitive isNotInCaseInsensitive(Collection<String> values) {
static IsNotInCaseInsensitive<String> isNotInCaseInsensitive(Collection<String> values) {
return IsNotInCaseInsensitive.of(values);
}

static IsNotInCaseInsensitiveWhenPresent isNotInCaseInsensitiveWhenPresent(@Nullable String... values) {
static IsNotInCaseInsensitiveWhenPresent<String> isNotInCaseInsensitiveWhenPresent(@Nullable String... values) {
return IsNotInCaseInsensitiveWhenPresent.of(values);
}

static IsNotInCaseInsensitiveWhenPresent isNotInCaseInsensitiveWhenPresent(
static IsNotInCaseInsensitiveWhenPresent<String> isNotInCaseInsensitiveWhenPresent(
@Nullable Collection<@Nullable String> values) {
return values == null ? IsNotInCaseInsensitiveWhenPresent.empty() :
IsNotInCaseInsensitiveWhenPresent.of(values);
Original file line number Diff line number Diff line change
@@ -15,6 +15,8 @@
*/
package org.mybatis.dynamic.sql.util;

import java.util.function.Function;

import org.jspecify.annotations.Nullable;

public interface StringUtilities {
@@ -59,4 +61,8 @@ static String formatConstantForSQL(String in) {
String escaped = in.replace("'", "''"); //$NON-NLS-1$ //$NON-NLS-2$
return "'" + escaped + "'"; //$NON-NLS-1$ //$NON-NLS-2$
}

static Function<String, String> mapToUpperCase(Function<String, String> f) {
return f.andThen(String::toUpperCase);
}
}
Loading